From 959e0e987b5b5731fd40fde45b88c0ba4b4e773b Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Mon, 2 May 2022 11:12:04 +0200 Subject: [PATCH 1/5] bootstrap/kubeadm/api/v1beta1: reject gzip file encoding with Ignition gzip is not supported by Ignition version 2.3 currently supported by CABPK, so let's reject it at the webhook level. Refs #6470 Signed-off-by: Mateusz Gozdek --- .../api/v1beta1/kubeadmconfig_webhook.go | 12 +++++++ .../api/v1beta1/kubeadmconfig_webhook_test.go | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go index a1284256ac9b..c77d6b59484e 100644 --- a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go +++ b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go @@ -254,6 +254,18 @@ func (c *KubeadmConfigSpec) validateIgnition(pathPrefix *field.Path) field.Error ) } + for i, file := range c.Files { + if file.Encoding == Gzip || file.Encoding == GzipBase64 { + allErrs = append( + allErrs, + field.Forbidden( + pathPrefix.Child("files").Index(i).Child("encoding"), + cannotUseWithIgnition, + ), + ) + } + } + if c.DiskSetup == nil { return allErrs } diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go index fb842d45f9b2..2ca38444a7a5 100644 --- a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go +++ b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go @@ -416,6 +416,42 @@ func TestKubeadmConfigValidate(t *testing.T) { }, expectErr: true, }, + "file encoding gzip specified with Ignition": { + enableIgnitionFeature: true, + in: &KubeadmConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "baz", + Namespace: "default", + }, + Spec: KubeadmConfigSpec{ + Format: Ignition, + Files: []File{ + { + Encoding: Gzip, + }, + }, + }, + }, + expectErr: true, + }, + "file encoding gzip+base64 specified with Ignition": { + enableIgnitionFeature: true, + in: &KubeadmConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "baz", + Namespace: "default", + }, + Spec: KubeadmConfigSpec{ + Format: Ignition, + Files: []File{ + { + Encoding: GzipBase64, + }, + }, + }, + }, + expectErr: true, + }, } for name, tt := range cases { From 9b89f71ff62f5851cfc654e9d8c366ce86dbf446 Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Mon, 2 May 2022 11:28:10 +0200 Subject: [PATCH 2/5] bootstrap/kubeadm/internal/ignition/clc: fix parallel tests Without this assignment in the scope of for loop effectively only last test case is being executed, which is incorrect. Signed-off-by: Mateusz Gozdek --- bootstrap/kubeadm/internal/ignition/clc/clc_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go index a87c9882f811..ce1f52706bd3 100644 --- a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go +++ b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go @@ -328,6 +328,8 @@ func TestRender(t *testing.T) { } for _, tt := range tc { + tt := tt + t.Run(tt.desc, func(t *testing.T) { t.Parallel() From 136b9fc23a80d426e95e9479b9230dce6bbeba46 Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Mon, 2 May 2022 18:13:12 +0200 Subject: [PATCH 3/5] bootstrap/kubeadm/internal/ignition/clc: add test for file rendering We should have a one test which excerices all possible input options to make sure we always generate right output. Signed-off-by: Mateusz Gozdek --- .../kubeadm/internal/ignition/clc/clc_test.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go index ce1f52706bd3..dd4cb145233a 100644 --- a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go +++ b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go @@ -123,6 +123,14 @@ func TestRender(t *testing.T) { "test_disk", "/var/lib/testdir", "foo", }, }, + WriteFiles: []bootstrapv1.File{ + { + Path: "/etc/testfile.yaml", + Encoding: bootstrapv1.Base64, + Content: "Zm9vCg==", + Permissions: "0600", + }, + }, }, wantIgnition: types.Config{ Ignition: types.Ignition{ @@ -169,6 +177,18 @@ func TestRender(t *testing.T) { Mode: pointer.IntPtr(384), }, }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/testfile.yaml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{ + Source: "data:,foo%0A", + }, + Mode: pointer.IntPtr(384), + }, + }, { Node: types.Node{ Filesystem: "root", From a7deeff3cc1450ae0c032e551e51df77a1f47835 Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Mon, 2 May 2022 18:13:54 +0200 Subject: [PATCH 4/5] bootstrap/kubeadm/internal/ignition/clc: support base64 content encoding Refs #6403 Signed-off-by: Mateusz Gozdek --- .../kubeadm/internal/ignition/clc/clc.go | 12 +-- .../kubeadm/internal/ignition/clc/clc_test.go | 84 +++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/bootstrap/kubeadm/internal/ignition/clc/clc.go b/bootstrap/kubeadm/internal/ignition/clc/clc.go index 7c2c5d327d1b..0ddd2afafa8a 100644 --- a/bootstrap/kubeadm/internal/ignition/clc/clc.go +++ b/bootstrap/kubeadm/internal/ignition/clc/clc.go @@ -185,19 +185,15 @@ storage: {{- range .WriteFiles }} - path: {{ .Path }} # Owner - # - # If Encoding == gzip+base64 || Encoding == gzip - # compression: true - # - # If Encoding == gzip+base64 || Encoding == "base64" - # Put "!!binary" notation before the content to let YAML decoder treat data as - # base64 data. - # {{ if ne .Permissions "" -}} mode: {{ .Permissions }} {{ end -}} contents: + {{ if eq .Encoding "base64" -}} + inline: !!binary | + {{- else -}} inline: | + {{- end }} {{ .Content | Indent 10 }} {{- end }} - path: /etc/kubeadm.sh diff --git a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go index dd4cb145233a..4b4c73194ec2 100644 --- a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go +++ b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go @@ -129,6 +129,7 @@ func TestRender(t *testing.T) { Encoding: bootstrapv1.Base64, Content: "Zm9vCg==", Permissions: "0600", + Owner: "nobody:nobody", }, }, }, @@ -345,6 +346,89 @@ func TestRender(t *testing.T) { }, }, }, + { + desc: "base64 encoded content", + input: &cloudinit.BaseUserData{ + PreKubeadmCommands: preKubeadmCommands, + PostKubeadmCommands: postKubeadmCommands, + KubeadmCommand: "kubeadm join", + WriteFiles: []bootstrapv1.File{ + { + Path: "/etc/base64encodedcontent.yaml", + Encoding: bootstrapv1.Base64, + Content: "Zm9vCg==", + Permissions: "0600", + }, + { + Path: "/etc/plaincontent.yaml", + Content: "foo", + Permissions: "0600", + }, + }, + }, + wantIgnition: types.Config{ + Ignition: types.Ignition{ + Version: "2.3.0", + }, + Storage: types.Storage{ + Files: []types.File{ + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/base64encodedcontent.yaml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{Source: "data:,foo%0A"}, + Mode: pointer.IntPtr(384), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/plaincontent.yaml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{Source: "data:,foo%0A"}, + Mode: pointer.IntPtr(384), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/kubeadm.sh", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{ + Source: "data:,%23!%2Fbin%2Fbash%0Aset%20-e%0A%0Apre-command%0Aanother-pre-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A%0A%0Akubeadm%20join%0Amkdir%20-p%20%2Frun%2Fcluster-api%20%26%26%20echo%20success%20%3E%20%2Frun%2Fcluster-api%2Fbootstrap-success.complete%0Amv%20%2Fetc%2Fkubeadm.yml%20%2Ftmp%2F%0A%0Apost-kubeadm-command%0Aanother-post-kubeamd-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A", + }, + Mode: pointer.IntPtr(448), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/kubeadm.yml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{ + Source: "data:,---%0Afoo%0A", + }, + Mode: pointer.IntPtr(384), + }, + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Contents: "[Unit]\nDescription=kubeadm\n# Run only once. After successful run, this file is moved to /tmp/.\nConditionPathExists=/etc/kubeadm.yml\n[Service]\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/kubeadm.sh\n[Install]\nWantedBy=multi-user.target\n", + Enabled: pointer.BoolPtr(true), + Name: "kubeadm.service", + }, + }, + }, + }, + }, } for _, tt := range tc { From eeb4d23c02ed08d3f69659f155e137109cc0b4ab Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Mon, 2 May 2022 18:11:04 +0200 Subject: [PATCH 5/5] bootstrap/kubeadm/internal/ignition/clc: implement file owner support Refs #6469 Signed-off-by: Mateusz Gozdek --- .../kubeadm/internal/ignition/clc/clc.go | 44 ++++++ .../kubeadm/internal/ignition/clc/clc_test.go | 129 ++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/bootstrap/kubeadm/internal/ignition/clc/clc.go b/bootstrap/kubeadm/internal/ignition/clc/clc.go index 0ddd2afafa8a..f906276323ae 100644 --- a/bootstrap/kubeadm/internal/ignition/clc/clc.go +++ b/bootstrap/kubeadm/internal/ignition/clc/clc.go @@ -184,6 +184,15 @@ storage: {{- end }} {{- range .WriteFiles }} - path: {{ .Path }} + {{- $owner := ParseOwner .Owner }} + {{ if $owner.User -}} + user: + name: {{ $owner.User }} + {{- end }} + {{ if $owner.Group -}} + group: + name: {{ $owner.Group }} + {{- end }} # Owner {{ if ne .Permissions "" -}} mode: {{ .Permissions }} @@ -259,6 +268,7 @@ func defaultTemplateFuncMap() template.FuncMap { "Split": strings.Split, "Join": strings.Join, "MountpointName": mountpointName, + "ParseOwner": parseOwner, } } @@ -272,6 +282,40 @@ func templateYAMLIndent(i int, input string) string { return strings.Join(split, ident) } +type owner struct { + User *string + Group *string +} + +func parseOwner(ownerRaw string) owner { + if ownerRaw == "" { + return owner{} + } + + ownerSlice := strings.Split(ownerRaw, ":") + + parseEntity := func(entity string) *string { + if entity == "" { + return nil + } + + entityTrimmed := strings.TrimSpace(entity) + + return &entityTrimmed + } + + if len(ownerSlice) == 1 { + return owner{ + User: parseEntity(ownerSlice[0]), + } + } + + return owner{ + User: parseEntity(ownerSlice[0]), + Group: parseEntity(ownerSlice[1]), + } +} + func renderCLC(input *cloudinit.BaseUserData, kubeadmConfig string) ([]byte, error) { t := template.Must(template.New("template").Funcs(defaultTemplateFuncMap()).Parse(clcTemplate)) diff --git a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go index 4b4c73194ec2..c9415a7fe1f1 100644 --- a/bootstrap/kubeadm/internal/ignition/clc/clc_test.go +++ b/bootstrap/kubeadm/internal/ignition/clc/clc_test.go @@ -182,6 +182,8 @@ func TestRender(t *testing.T) { Node: types.Node{ Filesystem: "root", Path: "/etc/testfile.yaml", + User: &types.NodeUser{Name: "nobody"}, + Group: &types.NodeGroup{Name: "nobody"}, }, FileEmbedded1: types.FileEmbedded1{ Contents: types.FileContents{ @@ -429,6 +431,133 @@ func TestRender(t *testing.T) { }, }, }, + { + desc: "all file ownership combinations", + input: &cloudinit.BaseUserData{ + PreKubeadmCommands: preKubeadmCommands, + PostKubeadmCommands: postKubeadmCommands, + KubeadmCommand: "kubeadm join", + WriteFiles: []bootstrapv1.File{ + { + Path: "/etc/username-group-name-owner.yaml", + Owner: "nobody:nobody", + Permissions: "0600", + }, + { + Path: "/etc/user-only-owner.yaml", + Owner: "nobody", + Permissions: "0600", + }, + { + Path: "/etc/user-only-with-colon-owner.yaml", + Owner: "nobody:", + Permissions: "0600", + }, + { + Path: "/etc/group-only-owner.yaml", + Owner: ":nobody", + Permissions: "0600", + }, + }, + }, + wantIgnition: types.Config{ + Ignition: types.Ignition{ + Version: "2.3.0", + }, + Storage: types.Storage{ + Files: []types.File{ + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/username-group-name-owner.yaml", + User: &types.NodeUser{ + Name: "nobody", + }, + Group: &types.NodeGroup{ + Name: "nobody", + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{Source: "data:,"}, + Mode: pointer.IntPtr(384), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/user-only-owner.yaml", + User: &types.NodeUser{ + Name: "nobody", + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{Source: "data:,"}, + Mode: pointer.IntPtr(384), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/user-only-with-colon-owner.yaml", + User: &types.NodeUser{ + Name: "nobody", + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{Source: "data:,"}, + Mode: pointer.IntPtr(384), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/group-only-owner.yaml", + Group: &types.NodeGroup{ + Name: "nobody", + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{Source: "data:,"}, + Mode: pointer.IntPtr(384), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/kubeadm.sh", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{ + Source: "data:,%23!%2Fbin%2Fbash%0Aset%20-e%0A%0Apre-command%0Aanother-pre-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A%0A%0Akubeadm%20join%0Amkdir%20-p%20%2Frun%2Fcluster-api%20%26%26%20echo%20success%20%3E%20%2Frun%2Fcluster-api%2Fbootstrap-success.complete%0Amv%20%2Fetc%2Fkubeadm.yml%20%2Ftmp%2F%0A%0Apost-kubeadm-command%0Aanother-post-kubeamd-command%0Acat%20%3C%3CEOF%20%3E%20%2Fetc%2Fmodules-load.d%2Fcontainerd.conf%0Aoverlay%0Abr_netfilter%0AEOF%0A", + }, + Mode: pointer.IntPtr(448), + }, + }, + { + Node: types.Node{ + Filesystem: "root", + Path: "/etc/kubeadm.yml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.FileContents{ + Source: "data:,---%0Afoo%0A", + }, + Mode: pointer.IntPtr(384), + }, + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Contents: "[Unit]\nDescription=kubeadm\n# Run only once. After successful run, this file is moved to /tmp/.\nConditionPathExists=/etc/kubeadm.yml\n[Service]\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/kubeadm.sh\n[Install]\nWantedBy=multi-user.target\n", + Enabled: pointer.BoolPtr(true), + Name: "kubeadm.service", + }, + }, + }, + }, + }, } for _, tt := range tc {