92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
|
|
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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")
|
||
|
|
}
|
||
|
|
}
|