diff --git a/cmd/machine-config-server/bootstrap.go b/cmd/machine-config-server/bootstrap.go index b25b490b6a..16ef1d5791 100644 --- a/cmd/machine-config-server/bootstrap.go +++ b/cmd/machine-config-server/bootstrap.go @@ -20,6 +20,7 @@ var ( bootstrapOpts struct { serverBaseDir string serverKubeConfig string + certificates []string } ) @@ -27,6 +28,7 @@ func init() { rootCmd.AddCommand(bootstrapCmd) bootstrapCmd.PersistentFlags().StringVar(&bootstrapOpts.serverBaseDir, "server-basedir", "/etc/mcs/bootstrap", "base directory on the host, relative to which machine-configs and pools can be found.") bootstrapCmd.PersistentFlags().StringVar(&bootstrapOpts.serverKubeConfig, "bootstrap-kubeconfig", "/etc/kubernetes/kubeconfig", "path to bootstrap kubeconfig served by the bootstrap server.") + bootstrapCmd.PersistentFlags().StringArrayVar(&bootstrapOpts.certificates, "bootstrap-certs", []string{}, "a certificate bundle formatted in a string array with the format key=value,key=value") } func runBootstrapCmd(_ *cobra.Command, _ []string) { @@ -36,7 +38,7 @@ func runBootstrapCmd(_ *cobra.Command, _ []string) { // To help debugging, immediately log version klog.Infof("Version: %+v (%s)", version.Raw, version.Hash) - bs, err := server.NewBootstrapServer(bootstrapOpts.serverBaseDir, bootstrapOpts.serverKubeConfig) + bs, err := server.NewBootstrapServer(bootstrapOpts.serverBaseDir, bootstrapOpts.serverKubeConfig, bootstrapOpts.certificates) if err != nil { klog.Exitf("Machine Config Server exited with error: %v", err) diff --git a/pkg/server/bootstrap_server.go b/pkg/server/bootstrap_server.go index 50cc3f6397..c8f3753dd6 100644 --- a/pkg/server/bootstrap_server.go +++ b/pkg/server/bootstrap_server.go @@ -26,17 +26,20 @@ type bootstrapServer struct { serverBaseDir string kubeconfigFunc kubeconfigFunc + + certs []string } // NewBootstrapServer initializes a new Bootstrap server that implements // the Server interface. -func NewBootstrapServer(dir, kubeconfig string) (Server, error) { +func NewBootstrapServer(dir, kubeconfig string, ircerts []string) (Server, error) { if _, err := os.Stat(kubeconfig); err != nil { return nil, fmt.Errorf("kubeconfig not found at location: %s", kubeconfig) } return &bootstrapServer{ serverBaseDir: dir, kubeconfigFunc: func() ([]byte, []byte, error) { return kubeconfigFromFile(kubeconfig) }, + certs: ircerts, }, nil } @@ -130,7 +133,7 @@ func (bsc *bootstrapServer) GetConfig(cr poolRequest) (*runtime.RawExtension, er addDataAndMaybeAppendToIgnition(caBundleFilePath, cc.Spec.KubeAPIServerServingCAData, &ignConf) addDataAndMaybeAppendToIgnition(cloudProviderCAPath, cc.Spec.CloudProviderCAData, &ignConf) addDataAndMaybeAppendToIgnition(additionalCAPath, cc.Spec.AdditionalTrustBundle, &ignConf) - appenders := getAppenders(currConf, nil, bsc.kubeconfigFunc) + appenders := getAppenders(currConf, nil, bsc.kubeconfigFunc, bsc.certs, bsc.serverBaseDir) for _, a := range appenders { if err := a(&ignConf, mc); err != nil { return nil, err diff --git a/pkg/server/cluster_server.go b/pkg/server/cluster_server.go index 8f9c8bec50..6978dcb469 100644 --- a/pkg/server/cluster_server.go +++ b/pkg/server/cluster_server.go @@ -137,7 +137,7 @@ func (cs *clusterServer) GetConfig(cr poolRequest) (*runtime.RawExtension, error addDataAndMaybeAppendToIgnition(caBundleFilePath, cc.Spec.KubeAPIServerServingCAData, &ignConf) addDataAndMaybeAppendToIgnition(cloudProviderCAPath, cc.Spec.CloudProviderCAData, &ignConf) addDataAndMaybeAppendToIgnition(additionalCAPath, cc.Spec.AdditionalTrustBundle, &ignConf) - appenders := getAppenders(currConf, cr.version, cs.kubeconfigFunc) + appenders := getAppenders(currConf, cr.version, cs.kubeconfigFunc, []string{}, "") for _, a := range appenders { if err := a(&ignConf, mc); err != nil { return nil, err diff --git a/pkg/server/server.go b/pkg/server/server.go index 5f416ef907..ed85870bc2 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -3,6 +3,9 @@ package server import ( "fmt" "net/url" + "os" + "path/filepath" + "strings" "github.com/clarketm/json" "github.com/coreos/go-semver/semver" @@ -43,7 +46,7 @@ type Server interface { GetConfig(poolRequest) (*runtime.RawExtension, error) } -func getAppenders(currMachineConfig string, version *semver.Version, f kubeconfigFunc) []appenderFunc { +func getAppenders(currMachineConfig string, version *semver.Version, f kubeconfigFunc, certs []string, serverDir string) []appenderFunc { appenders := []appenderFunc{ // append machine annotations file. func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error { @@ -53,6 +56,7 @@ func getAppenders(currMachineConfig string, version *semver.Version, f kubeconfi func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error { return appendKubeConfig(cfg, f) }, // append the machineconfig content appendInitialMachineConfig, + func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error { return appendCerts(cfg, certs, serverDir) }, // This has to come last!!! func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error { return appendEncapsulated(cfg, mc, version) @@ -61,6 +65,21 @@ func getAppenders(currMachineConfig string, version *semver.Version, f kubeconfi return appenders } +func appendCerts(cfg *ign3types.Config, certs []string, serverDir string) error { + for _, cert := range certs { + keyValue := strings.Split(cert, "=") + if len(keyValue) != 2 { + return fmt.Errorf("could not use cert, missing key or value %s", cert) + } + data, err := os.ReadFile(filepath.Join(serverDir, keyValue[1])) + if err != nil { + return fmt.Errorf("could not read cert file %w", err) + } + appendFileToIgnition(cfg, filepath.Join("/etc/docker/certs.d", keyValue[0], "ca.crt"), string(data)) + } + return nil +} + // appendEncapsulated empties out the ignition portion of a MachineConfig and adds // it to /etc/ignition-machine-config-encapsulated.json. This is used by // machine-config-daemon-firstboot.service to process the bits that the main Ignition (that runs in the initramfs) diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 42fc521f68..bcaa1e3a81 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -17,6 +17,7 @@ import ( ign3types "github.com/coreos/ignition/v2/config/v3_4/types" yaml "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -165,10 +166,14 @@ func TestBootstrapServer(t *testing.T) { t.Fatalf("unexpected error while appending file to ignition: %v", err) } + bytes := []byte("testing") + err = os.WriteFile(filepath.Join(testDir, "bar.crt"), bytes, 0o664) + require.Nil(t, err) // initialize bootstrap server and get config. bs := &bootstrapServer{ serverBaseDir: testDir, kubeconfigFunc: func() ([]byte, []byte, error) { return getKubeConfigContent(t) }, + certs: []string{"foo=bar.crt"}, } if err != nil { t.Fatal(err) @@ -189,6 +194,13 @@ func TestBootstrapServer(t *testing.T) { if err != nil { t.Fatal(err) } + foundCertFiles := false + for _, file := range resCfg.Storage.Files { + if strings.Contains(file.Path, filepath.Join("/etc/docker/certs.d", "foo")) { + foundCertFiles = true + } + } + require.True(t, foundCertFiles) validateIgnitionFiles(t, ignCfg.Storage.Files, resCfg.Storage.Files) validateIgnitionSystemd(t, ignCfg.Systemd.Units, resCfg.Systemd.Units)