From 21e9a640991aeb90d90ddee788f6824b60efeebd Mon Sep 17 00:00:00 2001 From: Charlie Doern Date: Thu, 20 Jul 2023 09:53:17 -0400 Subject: [PATCH] add Passwd to bootstrap served ignition This ensures that the first nodes always know about core, and the authorized ssh keys Signed-off-by: Charlie Doern --- pkg/controller/common/helpers.go | 6 +- pkg/daemon/update.go | 4 ++ pkg/server/api.go | 2 +- pkg/server/server.go | 6 ++ test/e2e/mcd_test.go | 104 +++++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 2 deletions(-) diff --git a/pkg/controller/common/helpers.go b/pkg/controller/common/helpers.go index 1ad0e2eb82..cdbbe12226 100644 --- a/pkg/controller/common/helpers.go +++ b/pkg/controller/common/helpers.go @@ -370,9 +370,13 @@ func ConvertRawExtIgnitionToV3_1(inRawExtIgn *runtime.RawExtension) (runtime.Raw return outRawExt, nil } +func ConvertV3ToV2Ignition(cfg ign3types.Config) (ign2types.Config, error) { + return convertIgnition3to2(cfg) +} + // ConvertRawExtIgnitionToV2 ensures that the Ignition config in // the RawExtension is spec v2.2, or translates to it. -func ConvertRawExtIgnitionToV2(inRawExtIgn *runtime.RawExtension) (runtime.RawExtension, error) { +func ConvertRawExtIgnitionToV2Raw(inRawExtIgn *runtime.RawExtension) (runtime.RawExtension, error) { ignCfg, rpt, err := ign3.Parse(inRawExtIgn.Raw) if err != nil || rpt.IsFatal() { return runtime.RawExtension{}, fmt.Errorf("parsing Ignition config spec v3.2 failed with error: %w\nReport: %v", err, rpt) diff --git a/pkg/daemon/update.go b/pkg/daemon/update.go index 70202a0845..e4e7d9085c 100644 --- a/pkg/daemon/update.go +++ b/pkg/daemon/update.go @@ -1547,6 +1547,10 @@ func (dn *Daemon) useNewSSHKeyPath() bool { func (dn *Daemon) updateSSHKeys(newUsers, oldUsers []ign3types.PasswdUser) error { klog.Info("updating SSH keys") + for _, u := range newUsers { + klog.Infof("Provided User: %s with %d keys", u.Name, len(u.SSHAuthorizedKeys)) + } + // Checking to see if absent users need to be deconfigured deconfigureAbsentUsers(newUsers, oldUsers) diff --git a/pkg/server/api.go b/pkg/server/api.go index 646d753731..7091bb3bf5 100644 --- a/pkg/server/api.go +++ b/pkg/server/api.go @@ -169,7 +169,7 @@ func (sh *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { serveConf = &converted31 } else { // Can only be 2.2 here - converted2, err := ctrlcommon.ConvertRawExtIgnitionToV2(conf) + converted2, err := ctrlcommon.ConvertRawExtIgnitionToV2Raw(conf) if err != nil { w.Header().Set("Content-Length", "0") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/server/server.go b/pkg/server/server.go index 2f7cd1925e..76ad2a2b8c 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -76,15 +76,21 @@ func appendEncapsulated(conf *igntypes.Config, mc *mcfgv1.MachineConfig, version // requires an empty Ignition version. if version == nil || version.Slice()[0] == 3 { tmpIgnCfg := ctrlcommon.NewIgnConfig() + tmpIgnCfg.Passwd = conf.Passwd rawTmpIgnCfg, err = json.Marshal(tmpIgnCfg) if err != nil { return fmt.Errorf("error marshalling Ignition config: %w", err) } } else { + v2, err := ctrlcommon.ConvertV3ToV2Ignition(*conf) + if err != nil { + return err + } tmpIgnCfg := ign2types.Config{ Ignition: ign2types.Ignition{ Version: ign2types.MaxVersion.String(), }, + Passwd: v2.Passwd, } rawTmpIgnCfg, err = json.Marshal(tmpIgnCfg) if err != nil { diff --git a/test/e2e/mcd_test.go b/test/e2e/mcd_test.go index f2b1721340..6b8b605c41 100644 --- a/test/e2e/mcd_test.go +++ b/test/e2e/mcd_test.go @@ -1,8 +1,10 @@ package e2e_test import ( + "bytes" "context" "fmt" + "os/exec" "path/filepath" "strconv" "strings" @@ -1007,6 +1009,108 @@ func TestMCDRotatesCertsOnPausedPool(t *testing.T) { } +func TestFirstBootHasSSHKeys(t *testing.T) { + cs := framework.NewClientSet("") + outNodeYoungest := bytes.NewBuffer([]byte{}) + outErr := bytes.NewBuffer([]byte{}) + // get nodes by newest + cmdCombined := "oc get nodes --sort-by .metadata.creationTimestamp | tail -n 1" + cmd := exec.Command("bash", "-c", cmdCombined) + cmd.Stdout = outNodeYoungest + cmd.Stderr = outErr + err := cmd.Run() + require.Nil(t, err, fmt.Sprintf("Got stdout: %s and stderr: %s", outNodeYoungest.String(), outErr)) + // get top machineset + cmdCombined = "oc -n openshift-machine-api -o name get machinesets | head -n 1" + cmd = exec.Command("bash", "-c", cmdCombined) + outMSet := bytes.NewBuffer([]byte{}) + outErr = bytes.NewBuffer([]byte{}) + cmd.Stdout = outMSet + cmd.Stderr = outErr + err = cmd.Run() + require.Nil(t, err, fmt.Sprintf("Got stdout: %s and stderr: %s", outMSet.String(), outErr)) + mset := strings.Trim(strings.Split(outMSet.String(), "/")[1], "\n") + // scale a 2nd machine + cmdCombined = "oc scale --replicas=2 machineset " + mset + " -n openshift-machine-api" + cmd = exec.Command("bash", "-c", cmdCombined) + outScale := bytes.NewBuffer([]byte{}) + outErr = bytes.NewBuffer([]byte{}) + cmd.Stdout = outMSet + cmd.Stderr = outErr + err = cmd.Run() + require.Nil(t, err, fmt.Sprintf("Got stdout: %s and stderr: %s", outScale.String(), outErr)) + outNodeYoungestNew := &bytes.Buffer{} + nodeStr := strings.Split(outNodeYoungest.String(), " ")[0] + t.Cleanup(func() { + if len(outNodeYoungestNew.String()) > 0 && strings.Split(outNodeYoungestNew.String(), " ")[0] != nodeStr { + // scale down + cmdCombined = "oc scale --replicas=1 machineset " + mset + " -n openshift-machine-api" + cmd = exec.Command("bash", "-c", cmdCombined) + outScale := bytes.NewBuffer([]byte{}) + outErr = bytes.NewBuffer([]byte{}) + cmd.Stdout = outMSet + cmd.Stderr = outErr + err = cmd.Run() + splitNodes := []string{} + require.Nil(t, err, fmt.Sprintf("Got stdout: %s and stderr: %s", outScale.String(), outErr)) + if err := wait.PollUntilContextTimeout(context.TODO(), 2*time.Second, 20*time.Minute, false, func(ctx context.Context) (bool, error) { + outNodeYoungestNew = bytes.NewBuffer([]byte{}) + outErr = bytes.NewBuffer([]byte{}) + // get all nodes + cmdCombined = "oc get nodes" + cmd := exec.Command("bash", "-c", cmdCombined) + cmd.Stdout = outNodeYoungestNew + cmd.Stderr = outErr + err := cmd.Run() + require.Nil(t, err, fmt.Sprintf("Got stdout: %s and stderr: %s", outNodeYoungestNew.String(), outErr)) + splitNodes = strings.Split(outNodeYoungestNew.String(), "\n") + for _, n := range splitNodes { + // find the one with scheduling disabled and delete it + if strings.Contains(n, "SchedulingDisabled") { + return false, nil + } + } + return true, nil + }); err != nil { + t.Fatalf("did not get old node upon cleanup: %s", splitNodes) + } + } + + }) + nodeSplit := []string{"", ""} + if err := wait.PollUntilContextTimeout(context.TODO(), 2*time.Second, 20*time.Minute, false, func(ctx context.Context) (bool, error) { + outNodeYoungestNew = bytes.NewBuffer([]byte{}) + outErr = bytes.NewBuffer([]byte{}) + // get nodes over and over + cmdCombined = "oc get nodes --sort-by .metadata.creationTimestamp | tail -n 1" + cmd := exec.Command("bash", "-c", cmdCombined) + cmd.Stdout = outNodeYoungestNew + cmd.Stderr = outErr + err := cmd.Run() + require.Nil(t, err, fmt.Sprintf("Got stdout: %s and stderr: %s", outNodeYoungestNew.String(), outErr)) + nodeSplit = strings.SplitN(outNodeYoungestNew.String(), " ", 2) + // if node name != first node name and it is ready, we have a node + if nodeSplit[0] != nodeStr && strings.Contains(nodeSplit[1], "Ready") && !strings.Contains(nodeSplit[1], "NotReady") { + return true, nil + } + return false, nil + }); err != nil { + t.Fatal("did not get new node") + } + + nodes, err := helpers.GetNodesByRole(cs, "worker") + require.Nil(t, err) + foundNode := false + for _, node := range nodes { + if node.Name == nodeSplit[0] && strings.Contains(nodeSplit[1], "Ready") && !strings.Contains(nodeSplit[1], "NotReady") { + foundNode = true + out := helpers.ExecCmdOnNode(t, cs, node, "cat", "/rootfs/home/core/.ssh/authorized_keys.d/ignition") + t.Logf("Got ssh key file data: %s", out) + require.NotEmpty(t, out) + } + } + require.True(t, foundNode) +} func createMCToAddFileForRole(name, role, filename, data string) *mcfgv1.MachineConfig { mcadd := helpers.CreateMC(fmt.Sprintf("%s-%s", name, uuid.NewUUID()), role)