Skip to content

Commit

Permalink
Merge pull request #339 from saqibali-2k/pr/grub-user-sugar
Browse files Browse the repository at this point in the history
v1_5_exp: Add sugar to create user.cfg
  • Loading branch information
bgilbert committed Jun 22, 2022
2 parents 4573a64 + 460bee4 commit 87ce0d3
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 0 deletions.
4 changes: 4 additions & 0 deletions config/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ var (

// Extensions
ErrExtensionNameRequired = errors.New("field \"name\" is required")

// Grub
ErrGrubUserNameNotSpecified = errors.New("field \"name\" is required")
ErrGrubPasswordNotSpecified = errors.New("field \"password_hash\" is required")
)
10 changes: 10 additions & 0 deletions config/fcos/v1_5_exp/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Config struct {
base.Config `yaml:",inline"`
BootDevice BootDevice `yaml:"boot_device"`
Extensions []Extension `yaml:"extensions"`
Grub Grub `yaml:"grub"`
}

type BootDevice struct {
Expand All @@ -43,3 +44,12 @@ type BootDeviceMirror struct {
type Extension struct {
Name string `yaml:"name"`
}

type Grub struct {
Users []GrubUser `yaml:"users"`
}

type GrubUser struct {
Name string `yaml:"name"`
PasswordHash *string `yaml:"password_hash"`
}
66 changes: 66 additions & 0 deletions config/fcos/v1_5_exp/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"

baseutil "github.com/coreos/butane/base/util"
"github.com/coreos/butane/config/common"
Expand Down Expand Up @@ -86,6 +87,11 @@ func (c Config) ToIgn3_4Unvalidated(options common.TranslateOptions) (types.Conf
retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
ret = retConfig.(types.Config)
r.Merge(rp)

retp, tsp, rp = c.handleUserGrubCfg(options)
retConfig, ts = baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts)
ret = retConfig.(types.Config)
r.Merge(rp)
return ret, ts, r
}

Expand Down Expand Up @@ -348,3 +354,63 @@ func (c Config) processPackages(options common.TranslateOptions) (types.Config,
ts.AddFromCommonSource(yamlPath, path.New("json", "storage"), ret.Storage)
return ret, ts, r
}

func (c Config) handleUserGrubCfg(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
rendered := types.Config{}
ts := translate.NewTranslationSet("yaml", "json")
var r report.Report
yamlPath := path.New("yaml", "grub", "users")
if len(c.Grub.Users) == 0 {
// No users
return rendered, ts, r
}

// create boot filesystem
rendered.Storage.Filesystems = append(rendered.Storage.Filesystems,
types.Filesystem{
Device: "/dev/disk/by-label/boot",
Format: util.StrToPtr("ext4"),
Path: util.StrToPtr("/boot"),
})

userCfgContent := []byte(buildGrubConfig(c.Grub))
src, compression, err := baseutil.MakeDataURL(userCfgContent, nil, !options.NoResourceAutoCompression)
if err != nil {
r.AddOnError(yamlPath, err)
return rendered, ts, r
}

// Create user.cfg file and add it to rendered config
rendered.Storage.Files = append(rendered.Storage.Files,
types.File{
Node: types.Node{
Path: "/boot/grub2/user.cfg",
},
FileEmbedded1: types.FileEmbedded1{
Append: []types.Resource{
{
Source: util.StrToPtr(src),
Compression: compression,
},
},
},
})

ts.AddFromCommonSource(yamlPath, path.New("json", "storage"), rendered.Storage)
return rendered, ts, r
}

func buildGrubConfig(gb Grub) string {
// Process super users and corresponding passwords
allUsers := []string{}
cmds := []string{}

for _, user := range gb.Users {
// We have already validated that user.Name and user.PasswordHash are non-empty
allUsers = append(allUsers, user.Name)
// Command for setting users password
cmds = append(cmds, fmt.Sprintf("password_pbkdf2 %s %s", user.Name, *user.PasswordHash))
}
superUserCmd := fmt.Sprintf("set superusers=\"%s\"\n", strings.Join(allUsers, " "))
return "# Generated by Butane\n\n" + superUserCmd + strings.Join(cmds, "\n") + "\n"
}
130 changes: 130 additions & 0 deletions config/fcos/v1_5_exp/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1487,3 +1487,133 @@ func TestTranslateExtensions(t *testing.T) {
})
}
}

// TestTranslateGrub tests translating the Butane config Grub section.
func TestTranslateGrub(t *testing.T) {
// Some tests below have the same translations
translations := []translate.Translation{
{path.New("yaml", "version"), path.New("json", "ignition", "version")},
{path.New("yaml", "grub", "users"), path.New("json", "storage")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "filesystems")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "filesystems", 0)},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "filesystems", 0, "path")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "filesystems", 0, "device")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "filesystems", 0, "format")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files", 0)},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files", 0, "path")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files", 0, "append")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files", 0, "append", 0)},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files", 0, "append", 0, "source")},
{path.New("yaml", "grub", "users"), path.New("json", "storage", "files", 0, "append", 0, "compression")},
}
tests := []struct {
in Config
out types.Config
exceptions []translate.Translation
report report.Report
}{
// config with 1 user
{
Config{
Grub: Grub{
Users: []GrubUser{
{
Name: "root",
PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874A958E526409..."),
},
},
},
},
types.Config{
Ignition: types.Ignition{
Version: "3.4.0-experimental",
},
Storage: types.Storage{
Filesystems: []types.Filesystem{
{
Device: "/dev/disk/by-label/boot",
Format: util.StrToPtr("ext4"),
Path: util.StrToPtr("/boot"),
},
},
Files: []types.File{
{
Node: types.Node{
Path: "/boot/grub2/user.cfg",
},
FileEmbedded1: types.FileEmbedded1{
Append: []types.Resource{
{
Source: util.StrToPtr("data:,%23%20Generated%20by%20Butane%0A%0Aset%20superusers%3D%22root%22%0Apassword_pbkdf2%20root%20grub.pbkdf2.sha512.10000.874A958E526409...%0A"),
Compression: util.StrToPtr(""),
},
},
},
},
},
},
},
translations,
report.Report{},
},
// config with 2 users (and 2 different hashes)
{
Config{
Grub: Grub{
Users: []GrubUser{
{
Name: "root1",
PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874A958E526409..."),
},
{
Name: "root2",
PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874B829D126209..."),
},
},
},
},
types.Config{
Ignition: types.Ignition{
Version: "3.4.0-experimental",
},
Storage: types.Storage{
Filesystems: []types.Filesystem{
{
Device: "/dev/disk/by-label/boot",
Format: util.StrToPtr("ext4"),
Path: util.StrToPtr("/boot"),
},
},
Files: []types.File{
{
Node: types.Node{
Path: "/boot/grub2/user.cfg",
},
FileEmbedded1: types.FileEmbedded1{
Append: []types.Resource{
{
Source: util.StrToPtr("data:;base64,H4sIAAAAAAAC/3zMsQrCMBDG8b1PcdT9SI62JoODRfExJCGngtCEuwTx7UWyiss3fH/47eDCG0uonCC+YW01bDwMyhW0FZamLHoYJedq4bs0DiWovrKka4nPdCPo8S4tYn9QH2G2hNYYY9Dtp6Of3XmmZTIeEX8C9BdYHfmTpYU68AkAAP//Mp8bt7YAAAA="),
Compression: util.StrToPtr("gzip"),
},
},
},
},
},
},
},
translations,
report.Report{},
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) {
actual, translations, r := test.in.ToIgn3_4Unvalidated(common.TranslateOptions{})
assert.Equal(t, test.out, actual, "translation mismatch")
assert.Equal(t, test.report, r, "report mismatch")
baseutil.VerifyTranslations(t, translations, test.exceptions)
assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage")
})
}
}
12 changes: 12 additions & 0 deletions config/fcos/v1_5_exp/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package v1_5_exp

import (
"github.com/coreos/butane/config/common"
"github.com/coreos/ignition/v2/config/util"

"github.com/coreos/vcontext/path"
"github.com/coreos/vcontext/report"
Expand Down Expand Up @@ -46,3 +47,14 @@ func (e Extension) Validate(c path.ContextPath) (r report.Report) {
}
return
}

func (user GrubUser) Validate(c path.ContextPath) (r report.Report) {
if user.Name == "" {
r.AddOnError(c.Append("name"), common.ErrGrubUserNameNotSpecified)
}

if !util.NotEmpty(user.PasswordHash) {
r.AddOnError(c.Append("password_hash"), common.ErrGrubPasswordNotSpecified)
}
return
}
44 changes: 44 additions & 0 deletions config/fcos/v1_5_exp/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,47 @@ func TestValidateExtension(t *testing.T) {
})
}
}

func TestValidateGrubUser(t *testing.T) {
tests := []struct {
in GrubUser
out error
errPath path.ContextPath
}{
// valid user
{
in: GrubUser{
Name: "name",
PasswordHash: util.StrToPtr("pkcs5-pass"),
},
out: nil,
errPath: path.New("yaml"),
},
// username is not specified
{
in: GrubUser{
Name: "",
PasswordHash: util.StrToPtr("pkcs5-pass"),
},
out: common.ErrGrubUserNameNotSpecified,
errPath: path.New("yaml", "name"),
},
// password is not specified
{
in: GrubUser{
Name: "name",
},
out: common.ErrGrubPasswordNotSpecified,
errPath: path.New("yaml", "password_hash"),
},
}

for i, test := range tests {
t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) {
actual := test.in.Validate(path.New("yaml"))
expected := report.Report{}
expected.AddOnError(test.errPath, test.out)
assert.Equal(t, expected, actual, "bad report")
})
}
}
24 changes: 24 additions & 0 deletions config/openshift/v4_12_exp/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (c Config) ToMachineConfig4_12Unvalidated(options common.TranslateOptions)
if r.IsFatal() {
return result.MachineConfig{}, ts, r
}
ts = translateUserGrubCfg(&cfg, &ts)

// wrap
ts = ts.PrefixPaths(path.New("yaml"), path.New("json", "spec", "config"))
Expand Down Expand Up @@ -295,3 +296,26 @@ func validateMCOSupport(mc result.MachineConfig, ts translate.TranslationSet) re
}
return cutil.TranslateReportPaths(r, ts)
}

// fcos config generates a user.cfg file using append; however, OpenShift config
// does not support append (since MCO does not support it). Let change the file to use contents
func translateUserGrubCfg(config *types.Config, ts *translate.TranslationSet) translate.TranslationSet {
newMappings := translate.NewTranslationSet("json", "json")
for i, file := range config.Storage.Files {
if file.Path == "/boot/grub2/user.cfg" {
if len(file.Append) != 1 {
// The number of append objects was different from expected, this file
// was created by the user and not via butane GRUB sugar
return *ts
}
fromPath := path.New("json", "storage", "files", i, "append", 0)
translatedPath := path.New("json", "storage", "files", i, "contents")
config.Storage.Files[i].FileEmbedded1.Contents = file.Append[0]
config.Storage.Files[i].FileEmbedded1.Append = nil
newMappings.AddFromCommonObject(fromPath, translatedPath, config.Storage.Files[i].FileEmbedded1.Contents)

return ts.Map(newMappings)
}
}
return *ts
}
Loading

0 comments on commit 87ce0d3

Please sign in to comment.