diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e8ad6ef..7eba42a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,4 +17,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.61 + version: v1.63 diff --git a/Makefile b/Makefile index 076198e..9038769 100644 --- a/Makefile +++ b/Makefile @@ -223,10 +223,10 @@ ADDLICENSE ?= $(LOCALBIN)/addlicense-$(ADDLICENSE_VERSION) ## Tool Versions KUSTOMIZE_VERSION ?= v5.3.0 -CONTROLLER_TOOLS_VERSION ?= v0.16.3 +CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= latest -GOLANGCI_LINT_VERSION ?= v1.61.0 -GOIMPORTS_VERSION ?= v0.25.0 +GOLANGCI_LINT_VERSION ?= v1.63.0 +GOIMPORTS_VERSION ?= v0.29.0 GEN_CRD_API_REFERENCE_DOCS_VERSION ?= v0.3.0 ADDLICENSE_VERSION ?= v1.1.1 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b923657..f764e86 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -8,7 +8,7 @@ package v1alpha1 import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/config/crd/bases/metal.ironcore.dev_bmcs.yaml b/config/crd/bases/metal.ironcore.dev_bmcs.yaml index d310379..be9de9f 100644 --- a/config/crd/bases/metal.ironcore.dev_bmcs.yaml +++ b/config/crd/bases/metal.ironcore.dev_bmcs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.17.1 name: bmcs.metal.ironcore.dev spec: group: metal.ironcore.dev diff --git a/config/crd/bases/metal.ironcore.dev_bmcsecrets.yaml b/config/crd/bases/metal.ironcore.dev_bmcsecrets.yaml index e0a7bb6..3097e32 100644 --- a/config/crd/bases/metal.ironcore.dev_bmcsecrets.yaml +++ b/config/crd/bases/metal.ironcore.dev_bmcsecrets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.17.1 name: bmcsecrets.metal.ironcore.dev spec: group: metal.ironcore.dev diff --git a/config/crd/bases/metal.ironcore.dev_endpoints.yaml b/config/crd/bases/metal.ironcore.dev_endpoints.yaml index b7787b5..a8c53de 100644 --- a/config/crd/bases/metal.ironcore.dev_endpoints.yaml +++ b/config/crd/bases/metal.ironcore.dev_endpoints.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.17.1 name: endpoints.metal.ironcore.dev spec: group: metal.ironcore.dev diff --git a/config/crd/bases/metal.ironcore.dev_serverbootconfigurations.yaml b/config/crd/bases/metal.ironcore.dev_serverbootconfigurations.yaml index b8423fb..9a9f841 100644 --- a/config/crd/bases/metal.ironcore.dev_serverbootconfigurations.yaml +++ b/config/crd/bases/metal.ironcore.dev_serverbootconfigurations.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.17.1 name: serverbootconfigurations.metal.ironcore.dev spec: group: metal.ironcore.dev diff --git a/config/crd/bases/metal.ironcore.dev_serverclaims.yaml b/config/crd/bases/metal.ironcore.dev_serverclaims.yaml index fe97561..c94d7b5 100644 --- a/config/crd/bases/metal.ironcore.dev_serverclaims.yaml +++ b/config/crd/bases/metal.ironcore.dev_serverclaims.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.17.1 name: serverclaims.metal.ironcore.dev spec: group: metal.ironcore.dev diff --git a/config/crd/bases/metal.ironcore.dev_servers.yaml b/config/crd/bases/metal.ironcore.dev_servers.yaml index be46967..9935d33 100644 --- a/config/crd/bases/metal.ironcore.dev_servers.yaml +++ b/config/crd/bases/metal.ironcore.dev_servers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.17.1 name: servers.metal.ironcore.dev spec: group: metal.ironcore.dev diff --git a/go.mod b/go.mod index 8145bf9..2ca1f37 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stmcginnis/gofish v0.20.0 golang.org/x/crypto v0.32.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.1 k8s.io/apiextensions-apiserver v0.31.0 k8s.io/apimachinery v0.31.1 @@ -69,7 +70,6 @@ require ( google.golang.org/protobuf v1.36.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/internal/controller/helper.go b/internal/controller/helper.go index 8365584..4fa360e 100644 --- a/internal/controller/helper.go +++ b/internal/controller/helper.go @@ -4,10 +4,18 @@ package controller import ( + "crypto/rand" + "fmt" + "math/big" + metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + fieldOwner = client.FieldOwner("metal.ironcore.dev/controller-manager") +) + func shouldIgnoreReconciliation(obj client.Object) bool { val, found := obj.GetAnnotations()[metalv1alpha1.OperationAnnotation] if !found { @@ -15,3 +23,16 @@ func shouldIgnoreReconciliation(obj client.Object) bool { } return val == metalv1alpha1.OperationAnnotationIgnore } + +func GenerateRandomPassword(length int) ([]byte, error) { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + result := make([]byte, length) + for i := 0; i < length; i++ { + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + return nil, fmt.Errorf("failed to generate random password: %w", err) + } + result[i] = letters[n.Int64()] + } + return result, nil +} diff --git a/internal/controller/server_controller.go b/internal/controller/server_controller.go index ed117a4..baf91ad 100644 --- a/internal/controller/server_controller.go +++ b/internal/controller/server_controller.go @@ -5,13 +5,20 @@ package controller import ( "context" + "crypto/rand" + "crypto/rsa" "encoding/json" + "encoding/pem" "fmt" "io" "net/http" "sort" "time" + "golang.org/x/crypto/bcrypt" + + "golang.org/x/crypto/ssh" + "github.com/ironcore-dev/metal-operator/internal/bmcutils" "github.com/go-logr/logr" @@ -37,9 +44,13 @@ import ( ) const ( - DefaultIgnitionSecretKeyName = "ignition" - DefaultIgnitionFormatKey = "format" - DefaultIgnitionFormatValue = "fcos" + DefaultIgnitionSecretKeyName = "ignition" + DefaultIgnitionFormatKey = "format" + DefaultIgnitionFormatValue = "fcos" + SSHKeyPairSecretPrivateKeyName = "pem" + SSHKeyPairSecretPublicKeyName = "pub" + SShKeyPairSecretPasswordKeyName = "password" + ServerFinalizer = "metal.ironcore.dev/server" InternalAnnotationTypeKeyName = "metal.ironcore.dev/type" InternalAnnotationTypeValue = "Internal" @@ -406,11 +417,7 @@ func (r *ServerReconciler) ensureServerBootConfigRef(ctx context.Context, server APIVersion: "metal.ironcore.dev/v1alpha1", Kind: "ServerBootConfiguration", } - if err := r.Patch(ctx, server, client.MergeFrom(serverBase)); err != nil { - return err - } - - return nil + return r.Patch(ctx, server, client.MergeFrom(serverBase)) } func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logger, server *metalv1alpha1.Server) error { @@ -492,34 +499,104 @@ func (r *ServerReconciler) applyBootConfigurationAndIgnitionForDiscovery(ctx con } func (r *ServerReconciler) applyDefaultIgnitionForServer(ctx context.Context, log logr.Logger, server *metalv1alpha1.Server, bootConfig *metalv1alpha1.ServerBootConfiguration, registryURL string) error { + sshPrivateKey, sshPublicKey, password, err := generateSSHKeyPairAndPassword() + if err != nil { + return fmt.Errorf("failed to generate SSH keypair: %w", err) + } + + sshSecret := &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.ManagerNamespace, + Name: fmt.Sprintf("%s-ssh", bootConfig.Name), + }, + Data: map[string][]byte{ + SSHKeyPairSecretPublicKeyName: sshPublicKey, + SSHKeyPairSecretPrivateKeyName: sshPrivateKey, + SShKeyPairSecretPasswordKeyName: password, + }, + } + if err := controllerutil.SetControllerReference(bootConfig, sshSecret, r.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + if err := r.Patch(ctx, sshSecret, client.Apply, fieldOwner, client.ForceOwnership); err != nil { + return fmt.Errorf("failed to apply default SSH keypair: %w", err) + } + log.V(1).Info("Applied SSH keypair secret", "SSHKeyPair", client.ObjectKeyFromObject(sshSecret)) + probeFlags := fmt.Sprintf("--registry-url=%s --server-uuid=%s", registryURL, server.Spec.SystemUUID) - ignitionData, err := r.generateDefaultIgnitionDataForServer(probeFlags) + ignitionData, err := r.generateDefaultIgnitionDataForServer(probeFlags, sshPublicKey, password) if err != nil { return fmt.Errorf("failed to generate default ignitionSecret data: %w", err) } - ignitionSecret := &v1.Secret{} - ignitionSecret.Name = bootConfig.Name - ignitionSecret.Namespace = r.ManagerNamespace - opResult, err := controllerutil.CreateOrPatch(ctx, r.Client, ignitionSecret, func() error { - ignitionSecret.Data = map[string][]byte{ + ignitionSecret := &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.ManagerNamespace, + Name: bootConfig.Name, + }, + Data: map[string][]byte{ DefaultIgnitionFormatKey: []byte(DefaultIgnitionFormatValue), DefaultIgnitionSecretKeyName: ignitionData, - } - return controllerutil.SetControllerReference(bootConfig, ignitionSecret, r.Client.Scheme()) - }) - if err != nil { - return fmt.Errorf("failed to create or patch Ignition Secret: %w", err) + }, + } + + if err := controllerutil.SetControllerReference(bootConfig, ignitionSecret, r.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) } - log.V(1).Info("Created or patched Ignition Secret", "Secret", ignitionSecret.Name, "Operation", opResult) + + if err := r.Patch(ctx, ignitionSecret, client.Apply, fieldOwner, client.ForceOwnership); err != nil { + return fmt.Errorf("failed to apply default ignition secret: %w", err) + } + log.V(1).Info("Applied Ignition Secret") return nil } -func (r *ServerReconciler) generateDefaultIgnitionDataForServer(flags string) ([]byte, error) { - ignitionData, err := ignition.GenerateDefaultIgnitionData(ignition.ContainerConfig{ - Image: r.ProbeImage, - Flags: flags, +func generateSSHKeyPairAndPassword() ([]byte, []byte, []byte, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to generate private key: %w", err) + } + + privateKeyBlock, err := ssh.MarshalPrivateKey(privateKey, "") + if err != nil { + return nil, nil, nil, err + } + privateKeyPem := pem.EncodeToMemory(privateKeyBlock) + + sshPubKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to create SSH public key: %w", err) + } + publicKeyAuthorized := ssh.MarshalAuthorizedKey(sshPubKey) + + password, err := GenerateRandomPassword(20) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to generate password: %w", err) + } + + return privateKeyPem, publicKeyAuthorized, password, nil +} + +func (r *ServerReconciler) generateDefaultIgnitionDataForServer(flags string, sshPublicKey []byte, password []byte) ([]byte, error) { + passwordHash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) + if err != nil { + return nil, fmt.Errorf("failed to generate password hash: %w", err) + } + + ignitionData, err := ignition.GenerateDefaultIgnitionData(ignition.Config{ + Image: r.ProbeImage, + Flags: flags, + SSHPublicKey: string(sshPublicKey), + PasswordHash: string(passwordHash), }) if err != nil { return nil, fmt.Errorf("failed to generate default ignition data: %w", err) diff --git a/internal/controller/server_controller_test.go b/internal/controller/server_controller_test.go index d4c7016..9b02fe0 100644 --- a/internal/controller/server_controller_test.go +++ b/internal/controller/server_controller_test.go @@ -8,15 +8,18 @@ import ( "net/http" "time" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" + "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/ssh" + "gopkg.in/yaml.v3" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" - "github.com/ironcore-dev/metal-operator/internal/controller/testdata" + "github.com/ironcore-dev/metal-operator/internal/ignition" "github.com/ironcore-dev/metal-operator/internal/probe" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" @@ -104,6 +107,31 @@ var _ = Describe("Server Controller", func() { HaveField("Status.State", metalv1alpha1.ServerBootConfigurationStatePending), )) + By("Ensuring that the SSH keypair has been created") + sshSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: bootConfig.Name + "-ssh", + }, + } + Eventually(Object(sshSecret)).Should(SatisfyAll( + HaveField("OwnerReferences", ContainElement(metav1.OwnerReference{ + APIVersion: "metal.ironcore.dev/v1alpha1", + Kind: "ServerBootConfiguration", + Name: bootConfig.Name, + UID: bootConfig.UID, + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + })), + HaveField("Data", HaveKeyWithValue(SSHKeyPairSecretPrivateKeyName, Not(BeNil()))), + HaveField("Data", HaveKeyWithValue(SSHKeyPairSecretPublicKeyName, Not(BeEmpty()))), + HaveField("Data", HaveKeyWithValue(SShKeyPairSecretPasswordKeyName, Not(BeNil()))), + )) + _, err := ssh.ParsePrivateKey(sshSecret.Data[SSHKeyPairSecretPrivateKeyName]) + Expect(err).NotTo(HaveOccurred()) + _, _, _, _, err = ssh.ParseAuthorizedKey(sshSecret.Data[SSHKeyPairSecretPublicKeyName]) + Expect(err).NotTo(HaveOccurred()) + By("Ensuring that the default ignition configuration has been created") ignitionSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -111,6 +139,37 @@ var _ = Describe("Server Controller", func() { Name: bootConfig.Name, }, } + Eventually(Get(ignitionSecret)).Should(Succeed()) + + // Since the bycrypted password hash is not deterministic we will extract if from the actual secret and + // add it to our ignition data which we want to compare the rest with. + var parsedData map[string]interface{} + Expect(yaml.Unmarshal(ignitionSecret.Data[DefaultIgnitionSecretKeyName], &parsedData)).ToNot(HaveOccurred()) + + passwd, ok := parsedData["passwd"].(map[string]interface{}) + Expect(ok).To(BeTrue()) + + users, _ := passwd["users"].([]interface{}) + Expect(users).To(HaveLen(1)) + + user, ok := users[0].(map[string]interface{}) + Expect(ok).To(BeTrue()) + Expect(user).To(HaveKeyWithValue("name", "metal")) + + passwordHash, ok := user["password_hash"].(string) + Expect(ok).To(BeTrue(), "password_hash should be a string") + + err = bcrypt.CompareHashAndPassword([]byte(passwordHash), sshSecret.Data[SShKeyPairSecretPasswordKeyName]) + Expect(err).ToNot(HaveOccurred(), "passwordHash should match the expected password") + + ignitionData, err := ignition.GenerateDefaultIgnitionData(ignition.Config{ + Image: "foo:latest", + Flags: "--registry-url=http://localhost:30000 --server-uuid=38947555-7742-3448-3784-823347823834", + SSHPublicKey: string(sshSecret.Data[SSHKeyPairSecretPublicKeyName]), + PasswordHash: passwordHash, + }) + Expect(err).NotTo(HaveOccurred()) + Eventually(Object(ignitionSecret)).Should(SatisfyAll( HaveField("OwnerReferences", ContainElement(metav1.OwnerReference{ APIVersion: "metal.ironcore.dev/v1alpha1", @@ -120,7 +179,8 @@ var _ = Describe("Server Controller", func() { Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true), })), - HaveField("Data", HaveKeyWithValue("ignition", MatchYAML(testdata.DefaultIgnition))), + HaveField("Data", HaveKeyWithValue(DefaultIgnitionFormatKey, []byte("fcos"))), + HaveField("Data", HaveKeyWithValue(DefaultIgnitionSecretKeyName, MatchYAML(ignitionData))), )) By("Patching the boot configuration to a Ready state") @@ -226,6 +286,31 @@ var _ = Describe("Server Controller", func() { HaveField("Status.State", metalv1alpha1.ServerBootConfigurationStatePending), )) + By("Ensuring that the SSH keypair has been created") + sshSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: bootConfig.Name + "-ssh", + }, + } + Eventually(Object(sshSecret)).Should(SatisfyAll( + HaveField("OwnerReferences", ContainElement(metav1.OwnerReference{ + APIVersion: "metal.ironcore.dev/v1alpha1", + Kind: "ServerBootConfiguration", + Name: bootConfig.Name, + UID: bootConfig.UID, + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + })), + HaveField("Data", HaveKeyWithValue(SSHKeyPairSecretPublicKeyName, Not(BeEmpty()))), + HaveField("Data", HaveKeyWithValue(SSHKeyPairSecretPrivateKeyName, Not(BeEmpty()))), + HaveField("Data", HaveKeyWithValue(SShKeyPairSecretPasswordKeyName, Not(BeEmpty()))), + )) + _, err := ssh.ParsePrivateKey(sshSecret.Data[SSHKeyPairSecretPrivateKeyName]) + Expect(err).NotTo(HaveOccurred()) + _, _, _, _, err = ssh.ParseAuthorizedKey(sshSecret.Data[SSHKeyPairSecretPublicKeyName]) + Expect(err).NotTo(HaveOccurred()) + By("Ensuring that the default ignition configuration has been created") ignitionSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -233,6 +318,36 @@ var _ = Describe("Server Controller", func() { Name: bootConfig.Name, }, } + Eventually(Get(ignitionSecret)).Should(Succeed()) + + // Since the bycrypted password hash is not deterministic we will extract if from the actual secret and + // add it to our ignition data which we want to compare the rest with. + var parsedData map[string]interface{} + Expect(yaml.Unmarshal(ignitionSecret.Data[DefaultIgnitionSecretKeyName], &parsedData)).ToNot(HaveOccurred()) + + passwd, ok := parsedData["passwd"].(map[string]interface{}) + Expect(ok).To(BeTrue()) + + users, _ := passwd["users"].([]interface{}) + Expect(users).To(HaveLen(1)) + + user, ok := users[0].(map[string]interface{}) + Expect(ok).To(BeTrue()) + Expect(user).To(HaveKeyWithValue("name", "metal")) + + passwordHash, ok := user["password_hash"].(string) + Expect(ok).To(BeTrue(), "password_hash should be a string") + + Expect(bcrypt.CompareHashAndPassword([]byte(passwordHash), sshSecret.Data[SShKeyPairSecretPasswordKeyName])).Should(Succeed()) + + ignitionData, err := ignition.GenerateDefaultIgnitionData(ignition.Config{ + Image: "foo:latest", + Flags: "--registry-url=http://localhost:30000 --server-uuid=38947555-7742-3448-3784-823347823834", + SSHPublicKey: string(sshSecret.Data[SSHKeyPairSecretPublicKeyName]), + PasswordHash: passwordHash, + }) + Expect(err).NotTo(HaveOccurred()) + Eventually(Object(ignitionSecret)).Should(SatisfyAll( HaveField("OwnerReferences", ContainElement(metav1.OwnerReference{ APIVersion: "metal.ironcore.dev/v1alpha1", @@ -242,7 +357,8 @@ var _ = Describe("Server Controller", func() { Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true), })), - HaveField("Data", HaveKeyWithValue("ignition", MatchYAML(testdata.DefaultIgnition))), + HaveField("Data", HaveKeyWithValue(DefaultIgnitionFormatKey, []byte("fcos"))), + HaveField("Data", HaveKeyWithValue(DefaultIgnitionSecretKeyName, MatchYAML(ignitionData))), )) By("Patching the boot configuration to a Ready state") diff --git a/internal/controller/testdata/ignition.go b/internal/controller/testdata/ignition.go deleted file mode 100644 index 89a928e..0000000 --- a/internal/controller/testdata/ignition.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - -package testdata - -var ( - DefaultIgnition = `variant: fcos -version: "1.3.0" -systemd: - units: - - name: docker-install.service - enabled: true - contents: |- - [Unit] - Description=Install Docker - Before=metalprobe.service - [Service] - Restart=on-failure - RestartSec=20 - Type=oneshot - RemainAfterExit=yes - ExecStart=/usr/bin/apt-get update - ExecStart=/usr/bin/apt-get install docker.io -y - [Install] - WantedBy=multi-user.target - - name: docker.service - enabled: true - - name: metalprobe.service - enabled: true - contents: |- - [Unit] - Description=Run My Docker Container - [Service] - Restart=on-failure - RestartSec=20 - ExecStartPre=-/usr/bin/docker stop metalprobe - ExecStartPre=-/usr/bin/docker rm metalprobe - ExecStartPre=/usr/bin/docker pull foo:latest - ExecStart=/usr/bin/docker run --network host --privileged --name metalprobe foo:latest --registry-url=http://localhost:30000 --server-uuid=38947555-7742-3448-3784-823347823834 - ExecStop=/usr/bin/docker stop metalprobe - [Install] - WantedBy=multi-user.target -storage: - files: [] -passwd: {} -` -) diff --git a/internal/ignition/default.go b/internal/ignition/default.go index 14d2773..5cbb338 100644 --- a/internal/ignition/default.go +++ b/internal/ignition/default.go @@ -9,10 +9,12 @@ import ( "text/template" ) -// ContainerConfig holds the Docker image and flags. -type ContainerConfig struct { - Image string - Flags string +// Config holds the Docker image and flags. +type Config struct { + Image string + Flags string + SSHPublicKey string + PasswordHash string } // defaultIgnitionTemplate is a Go template for the default Ignition configuration. @@ -54,11 +56,16 @@ systemd: WantedBy=multi-user.target storage: files: [] -passwd: {} +passwd: + users: + - name: metal + password_hash: {{.PasswordHash}} + groups: [ "wheel" ] + ssh_authorized_keys: [ {{.SSHPublicKey}} ] ` -// GenerateDefaultIgnitionData renders the defaultIgnitionTemplate with the given ContainerConfig. -func GenerateDefaultIgnitionData(config ContainerConfig) ([]byte, error) { +// GenerateDefaultIgnitionData renders the defaultIgnitionTemplate with the given Config. +func GenerateDefaultIgnitionData(config Config) ([]byte, error) { tmpl, err := template.New("defaultIgnition").Parse(defaultIgnitionTemplate) if err != nil { return nil, fmt.Errorf("parsing template failed: %w", err)