package proxmox import ( "context" "strings" "testing" ) func TestSudoersContent(t *testing.T) { content := sudoersContent("orca") // Must contain NOPASSWD and NOEXEC for pct and qm. if !strings.Contains(content, "NOPASSWD: NOEXEC: /usr/bin/pct") { t.Error("missing NOEXEC on pct (AD-020)") } if !strings.Contains(content, "NOPASSWD: NOEXEC: /usr/bin/qm") { t.Error("missing NOEXEC on qm (AD-020)") } // apt-get and dpkg must have NOPASSWD but NOT NOEXEC (they need exec). if !strings.Contains(content, "NOPASSWD: /usr/bin/apt-get") { t.Error("missing NOPASSWD on apt-get") } if !strings.Contains(content, "NOPASSWD: /usr/bin/dpkg") { t.Error("missing NOPASSWD on dpkg") } if strings.Contains(content, "NOEXEC: /usr/bin/apt-get") { t.Error("apt-get must NOT have NOEXEC (breaks maintainer scripts)") } if strings.Contains(content, "NOEXEC: /usr/bin/dpkg") { t.Error("dpkg must NOT have NOEXEC (breaks maintainer scripts)") } // pvesh must be EXCLUDED from the sudoers command lines (AD-020). // Comments may mention pvesh for documentation, but no command line // should grant sudo access to the pvesh binary. for _, line := range strings.Split(content, "\n") { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "#") || trimmed == "" { continue // skip comments and blank lines } if strings.Contains(trimmed, "pvesh") { t.Errorf("pvesh must be EXCLUDED from sudoers command lines (AD-020): %s", trimmed) } } // Must use the orca user. if !strings.HasPrefix(content, "# /etc/sudoers.d/orca") { t.Error("missing managed-by-orca header") } if !strings.Contains(content, "orca ALL=(root)") { t.Error("missing orca user in sudoers") } } func TestSudoersContent_CustomUser(t *testing.T) { content := sudoersContent("custom-orca") if !strings.Contains(content, "custom-orca ALL=(root)") { t.Error("missing custom-orca user in sudoers") } } func TestOrcaOperatorPrivileges(t *testing.T) { // D-033: VM.Audit, Datastore.AllocateSpace, SDN.Use (space-separated). privs := strings.Fields(OrcaOperatorPrivileges) expected := map[string]bool{ "VM.Audit": true, "Datastore.AllocateSpace": true, "SDN.Use": true, } if len(privs) != 3 { t.Errorf("expected 3 privileges, got %d: %v", len(privs), privs) } for _, p := range privs { if !expected[p] { t.Errorf("unexpected privilege %q", p) } } } func TestBootstrapProxmox_Validation(t *testing.T) { ctx := context.Background() // Missing host. _, err := BootstrapProxmox(ctx, Options{Password: "pw"}) if err == nil || !strings.Contains(err.Error(), "host is required") { t.Errorf("expected host-required error, got %v", err) } // Missing password. _, err = BootstrapProxmox(ctx, Options{Host: "10.0.0.1"}) if err == nil || !strings.Contains(err.Error(), "password is required") { t.Errorf("expected password-required error, got %v", err) } } func TestDefaultOptions(t *testing.T) { // Verify the defaults are applied when zero-value options are passed // (we can't test the full flow without a real SSH server, but we can // test that the defaults are set by checking the validation path). opts := Options{Host: "10.0.0.1", Password: "pw"} // These would be set inside BootstrapProxmox; we test the constants // are the expected defaults. if DefaultProxmoxUser != "orca" { t.Errorf("DefaultProxmoxUser = %q, want orca", DefaultProxmoxUser) } if DefaultProxmoxRole != "OrcaOperator" { t.Errorf("DefaultProxmoxRole = %q, want OrcaOperator", DefaultProxmoxRole) } if DefaultSSHPort != 22 { t.Errorf("DefaultSSHPort = %d, want 22", DefaultSSHPort) } _ = opts }