package plan import ( "os" "os/exec" "path/filepath" "strings" "testing" "metis/pkg/config" ) // Exercise the generated script with synthetic credentials and fake host tools. func TestIdentityFailureAndRetry(t *testing.T) { root := t.TempDir() etc := filepath.Join(root, "etc/metis") state := filepath.Join(root, "var/lib/metis") bin := filepath.Join(root, "bin") for _, dir := range []string{etc, state, bin, filepath.Join(root, "etc/sudoers.d")} { if err := os.MkdirAll(dir, 0700); err != nil { t.Fatal(err) } } envPath := filepath.Join(etc, "firstboot.env") synthetic := "METIS_HOSTNAME='test'\nMETIS_ATLAS_PASSWORD='synthetic-atlas'\nMETIS_ROOT_PASSWORD='synthetic-root'\n" if err := os.WriteFile(envPath, []byte(synthetic), 0600); err != nil { t.Fatal(err) } commands := map[string]string{ "id": "exit 0", "getent": "exit 1", "visudo": "exit 0", "chpasswd": "cat >/dev/null\n[ \"${FAIL_PASSWORD:-0}\" = 0 ]", } for name, body := range commands { if err := os.WriteFile(filepath.Join(bin, name), []byte("#!/bin/sh\n"+body+"\n"), 0700); err != nil { t.Fatal(err) } } script := strings.NewReplacer("/etc/metis", etc, "/var/lib/metis", state, "/etc/sudoers.d", filepath.Join(root, "etc/sudoers.d")).Replace(nodeIdentityScriptContent()) path := filepath.Join(root, "identity.sh") if err := os.WriteFile(path, []byte(script), 0700); err != nil { t.Fatal(err) } run := func(fail string) error { cmd := exec.Command("bash", path) cmd.Env = append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "FAIL_PASSWORD="+fail) out, err := cmd.CombinedOutput() if strings.Contains(string(out), "synthetic-") { t.Fatal("password in output") } return err } if run("1") == nil { t.Fatal("password failure must propagate") } marker := filepath.Join(state, "node-identity-applied.done") if _, err := os.Stat(marker); !os.IsNotExist(err) { t.Fatal("failed setup marked complete") } if err := run("0"); err != nil { t.Fatal(err) } if _, err := os.Stat(marker); err != nil { t.Fatal(err) } clean, err := os.ReadFile(envPath) if err != nil { t.Fatal(err) } if strings.Contains(string(clean), "PASSWORD") || strings.Contains(string(clean), "synthetic-") { t.Fatal("password persisted after application") } if err := run("1"); err != nil { t.Fatal("completed setup should not reset passwords", err) } // Re-injection into a previously booted image must not trust its old marker. if err := os.WriteFile(filepath.Join(etc, "node-identity.pending"), []byte("pending"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(envPath, []byte(synthetic), 0600); err != nil { t.Fatal(err) } if run("1") == nil { t.Fatal("old marker suppressed newly injected credentials") } if err := run("0"); err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(etc, "node-identity.pending")); !os.IsNotExist(err) { t.Fatal("successful identity left a pending marker") } } func TestIdentityEnabledWithoutCloudInit(t *testing.T) { files, err := buildFiles(&config.NodeConfig{Hostname: "test", SSHUser: "atlas"}, nil) if err != nil { t.Fatal(err) } enabled := false for _, f := range files { if f.Path == "etc/systemd/system/multi-user.target.wants/metis-node-identity.service" { enabled = f.Symlink == "../metis-node-identity.service" } } if !enabled { t.Fatal("identity service is not enabled in image") } }