From efdd6d2b88c4cc053ef9c7ee1ac6a9fea031810c Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Thu, 15 Aug 2024 15:42:49 +0100 Subject: [PATCH] Ignition: move rke2-cis-script.sh to after RKE2 install Otherwise the script fails, this aligns with how the cloud-init script works. --- bootstrap/internal/ignition/butane/butane.go | 9 +- .../internal/ignition/butane/butane_test.go | 28 ------- bootstrap/internal/ignition/ignition.go | 6 ++ bootstrap/internal/ignition/ignition_test.go | 83 +++++++++++++++---- 4 files changed, 73 insertions(+), 53 deletions(-) diff --git a/bootstrap/internal/ignition/butane/butane.go b/bootstrap/internal/ignition/butane/butane.go index 52b3928e..3337e21b 100644 --- a/bootstrap/internal/ignition/butane/butane.go +++ b/bootstrap/internal/ignition/butane/butane.go @@ -38,9 +38,8 @@ import ( // The rke2-install.service unit is enabled and is executed only once during the boot process to run the /etc/rke2-install.sh script. // This script installs and deploys RKE2, and performs pre and post-installation commands. // The ntpd.service unit is enabled only if NTP servers are specified. -// The second section defines storage files for the system. It creates a file at /etc/rke2-install.sh. If CISEnabled is set to true, -// it runs an additional CIS script to enforce system security standards. If NTP servers are specified, -// it creates an NTP configuration file at /etc/ntp.conf. +// The second section defines storage files for the system. It creates a file at /etc/rke2-install.sh. +// If NTP servers are specified, it creates an NTP configuration file at /etc/ntp.conf. const ( butaneTemplate = ` variant: fcos @@ -114,10 +113,6 @@ storage: {{ . | Indent 10 }} {{- end }} - {{- if .CISEnabled }} - /etc/rancher/rke2/rke2-cis-script.sh - {{ end }} - {{ range .DeployRKE2Commands }} {{ . | Indent 10 }} {{- end }} diff --git a/bootstrap/internal/ignition/butane/butane_test.go b/bootstrap/internal/ignition/butane/butane_test.go index c6d4e64e..b0e0a7f2 100644 --- a/bootstrap/internal/ignition/butane/butane_test.go +++ b/bootstrap/internal/ignition/butane/butane_test.go @@ -18,8 +18,6 @@ limitations under the License. package butane import ( - "encoding/base64" - "strings" "testing" . "github.com/onsi/ginkgo/v2" @@ -72,7 +70,6 @@ var _ = Describe("Render", func() { "test", }, RKE2Version: "v1.21.3+rke2r1", - CISEnabled: false, WriteFiles: []bootstrapv1.File{ { Path: "/test/file", @@ -123,12 +120,6 @@ var _ = Describe("Render", func() { Expect(ign.Systemd.Units).To(HaveLen(3)) Expect(ign.Systemd.Units[0].Name).To(Equal("rke2-install.service")) - // Check rke2-install.sh does not contain the call to rke2-cis-script.sh - scriptContentsEnc := strings.Split(*ign.Storage.Files[3].Contents.Source, ",")[1] - scriptContents, err := base64.StdEncoding.DecodeString(scriptContentsEnc) - Expect(err).ToNot(HaveOccurred()) - Expect(scriptContents).ToNot(ContainSubstring("/etc/rancher/rke2/rke2-cis-script.sh")) - Expect(ign.Systemd.Units[0].Contents).To(Equal(pointer.String("[Unit]\nDescription=rke2-install\nWants=network-online.target\nAfter=network-online.target network.target\nConditionPathExists=!/etc/cluster-api/bootstrap-success.complete\n[Service]\nUser=root\n# To not restart the unit when it exits, as it is expected.\nType=oneshot\nExecStart=/etc/rke2-install.sh\n[Install]\nWantedBy=multi-user.target\n"))) Expect(ign.Systemd.Units[0].Enabled).To(Equal(pointer.Bool(true))) @@ -140,25 +131,6 @@ var _ = Describe("Render", func() { Expect(ign.Systemd.Units[2].Enabled).To(Equal(pointer.Bool(true))) }) - It("should render a valid ignition config with CISEnabled", func() { - input.CISEnabled = true - ignitionJson, err := Render(input, additionalConfig) - Expect(err).ToNot(HaveOccurred()) - - ign, reports, err := ignition.Parse(ignitionJson) - Expect(err).ToNot(HaveOccurred()) - Expect(reports.IsFatal()).To(BeFalse()) - - Expect(ign.Storage.Files).To(HaveLen(5)) - Expect(ign.Storage.Files[3].Path).To(Equal("/etc/rke2-install.sh")) - - // Check rke2-install.sh contains the call to rke2-cis-script.sh - scriptContentsEnc := strings.Split(*ign.Storage.Files[3].Contents.Source, ",")[1] - scriptContents, err := base64.StdEncoding.DecodeString(scriptContentsEnc) - Expect(err).ToNot(HaveOccurred()) - Expect(scriptContents).To(ContainSubstring("/etc/rancher/rke2/rke2-cis-script.sh")) - }) - It("accepts empty additional config", func() { additionalConfig = nil _, err := Render(input, additionalConfig) diff --git a/bootstrap/internal/ignition/ignition.go b/bootstrap/internal/ignition/ignition.go index 64996ddc..c8b36f8b 100644 --- a/bootstrap/internal/ignition/ignition.go +++ b/bootstrap/internal/ignition/ignition.go @@ -29,6 +29,7 @@ const ( controlPlaneCommand = "curl -sfL https://get.rke2.io | INSTALL_RKE2_VERSION=%[1]s sh -s - server" airGappedWorkerCommand = "INSTALL_RKE2_ARTIFACT_PATH=/opt/rke2-artifacts INSTALL_RKE2_TYPE=\"agent\" sh /opt/install.sh" workerCommand = "curl -sfL https://get.rke2.io | INSTALL_RKE2_VERSION=%[1]s INSTALL_RKE2_TYPE=\"agent\" sh -s -" + cisPreparationCommand = "/etc/rancher/rke2/rke2-cis-script.sh" ) var ( @@ -171,6 +172,11 @@ func getRKE2Commands(baseUserData *cloudinit.BaseUserData, command, airgappedCom rke2Commands = append(rke2Commands, fmt.Sprintf(command, baseUserData.RKE2Version)) } + // If CISEnabled is set to true we run an additional script for CIS mode pre-requisite config + if baseUserData.CISEnabled { + rke2Commands = append(rke2Commands, cisPreparationCommand) + } + rke2Commands = append(rke2Commands, systemdServices...) return rke2Commands, nil diff --git a/bootstrap/internal/ignition/ignition_test.go b/bootstrap/internal/ignition/ignition_test.go index 1336ac00..7d30e753 100644 --- a/bootstrap/internal/ignition/ignition_test.go +++ b/bootstrap/internal/ignition/ignition_test.go @@ -18,11 +18,18 @@ package ignition import ( "fmt" + "compress/gzip" + "bytes" + "io/ioutil" + "encoding/base64" + "strings" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + ignition "github.com/coreos/ignition/v2/config/v3_3" + bootstrapv1 "github.com/rancher/cluster-api-provider-rke2/bootstrap/api/v1beta1" "github.com/rancher/cluster-api-provider-rke2/bootstrap/internal/cloudinit" ) @@ -75,24 +82,64 @@ var _ = Describe("NewJoinWorker", func() { }) It("should return ignition data for worker", func() { - ignition, err := NewJoinWorker(input) + ignitionJson, err := NewJoinWorker(input) + Expect(err).ToNot(HaveOccurred()) + Expect(ignitionJson).ToNot(BeNil()) + + ign, reports, err := ignition.Parse(ignitionJson) Expect(err).ToNot(HaveOccurred()) - Expect(ignition).ToNot(BeNil()) + Expect(reports.IsFatal()).To(BeFalse()) + + Expect(ign.Storage.Files).To(HaveLen(4)) + Expect(ign.Storage.Files[0].Path).To(Equal("/etc/ssh/sshd_config")) + Expect(ign.Storage.Files[1].Path).To(Equal("/test/file")) + Expect(ign.Storage.Files[2].Path).To(Equal("/test/config")) + Expect(ign.Storage.Files[3].Path).To(Equal("/etc/rke2-install.sh")) }) It("should return error if input is nil", func() { input = nil - ignition, err := NewJoinWorker(input) + ignitionJson, err := NewJoinWorker(input) Expect(err).To(HaveOccurred()) - Expect(ignition).To(BeNil()) + Expect(ignitionJson).To(BeNil()) }) It("should return error if base userdata is nil", func() { input.BaseUserData = nil - ignition, err := NewJoinWorker(input) + ignitionJson, err := NewJoinWorker(input) Expect(err).To(HaveOccurred()) - Expect(ignition).To(BeNil()) + Expect(ignitionJson).To(BeNil()) }) + + It("should add preparation script with CISEnabled", func() { + input.CISEnabled = true + ignitionJson, err := NewJoinWorker(input) + Expect(err).ToNot(HaveOccurred()) + Expect(ignitionJson).ToNot(BeNil()) + + ign, reports, err := ignition.Parse(ignitionJson) + Expect(err).ToNot(HaveOccurred()) + Expect(reports.IsFatal()).To(BeFalse()) + + Expect(ign.Storage.Files).To(HaveLen(4)) + Expect(ign.Storage.Files[0].Path).To(Equal("/etc/ssh/sshd_config")) + Expect(ign.Storage.Files[1].Path).To(Equal("/test/file")) + Expect(ign.Storage.Files[2].Path).To(Equal("/test/config")) + Expect(ign.Storage.Files[3].Path).To(Equal("/etc/rke2-install.sh")) + + // Check rke2-install.sh contains the call to rke2-cis-script.sh + // The ignition file contents is gzipped and base64 encoded, so unpack it first + scriptContentsEnc := strings.Split(*ign.Storage.Files[3].Contents.Source, ",")[1] + scriptContentsGzip, err := base64.StdEncoding.DecodeString(scriptContentsEnc) + Expect(err).ToNot(HaveOccurred()) + reader := bytes.NewReader(scriptContentsGzip) + gzreader, err := gzip.NewReader(reader); + Expect(err).ToNot(HaveOccurred()) + scriptContents, err := ioutil.ReadAll(gzreader) + Expect(err).ToNot(HaveOccurred()) + Expect(scriptContents).To(ContainSubstring("/etc/rancher/rke2/rke2-cis-script.sh")) + }) + }) var _ = Describe("NewJoinControlPlane", func() { @@ -125,23 +172,23 @@ var _ = Describe("NewJoinControlPlane", func() { }) It("should return ignition data for control plane", func() { - ignition, err := NewJoinControlPlane(input) + ignitionJson, err := NewJoinControlPlane(input) Expect(err).ToNot(HaveOccurred()) - Expect(ignition).ToNot(BeNil()) + Expect(ignitionJson).ToNot(BeNil()) }) It("should return error if input is nil", func() { input = nil - ignition, err := NewJoinControlPlane(input) + ignitionJson, err := NewJoinControlPlane(input) Expect(err).To(HaveOccurred()) - Expect(ignition).To(BeNil()) + Expect(ignitionJson).To(BeNil()) }) It("should return error if control plane input is nil", func() { input.ControlPlaneInput = nil - ignition, err := NewJoinControlPlane(input) + ignitionJson, err := NewJoinControlPlane(input) Expect(err).To(HaveOccurred()) - Expect(ignition).To(BeNil()) + Expect(ignitionJson).To(BeNil()) }) }) @@ -175,23 +222,23 @@ var _ = Describe("NewInitControlPlane", func() { }) It("should return ignition data for control plane", func() { - ignition, err := NewInitControlPlane(input) + ignitionJson, err := NewInitControlPlane(input) Expect(err).ToNot(HaveOccurred()) - Expect(ignition).ToNot(BeNil()) + Expect(ignitionJson).ToNot(BeNil()) }) It("should return error if input is nil", func() { input = nil - ignition, err := NewInitControlPlane(input) + ignitionJson, err := NewInitControlPlane(input) Expect(err).To(HaveOccurred()) - Expect(ignition).To(BeNil()) + Expect(ignitionJson).To(BeNil()) }) It("should return error if control plane input is nil", func() { input.ControlPlaneInput = nil - ignition, err := NewInitControlPlane(input) + ignitionJson, err := NewInitControlPlane(input) Expect(err).To(HaveOccurred()) - Expect(ignition).To(BeNil()) + Expect(ignitionJson).To(BeNil()) }) })