Files
certmanager/internal/config/config_test.go
T
unkin-agent b0aad96673
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/integration Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline failed
Source the CA from /ca_chain, not from issuance
Deriving ca_certificate from an issue response welds CA distribution to
per-node renewal, so a rotation takes ~90 days to reach the estate, and
issuing_ca is the intermediate alone, which strict path builders reject
as a CAfile.

- add a `ca` subcommand printing {mount}/ca_chain as PEM, unauthenticated
- drop ca_certificate from the issue document and its file output
- order full_chain leaf first
- require only vault.addr for the CA path via config.LoadCA
2026-09-19 13:27:47 +10:00

188 lines
6.4 KiB
Go

package config
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func writeConfig(t *testing.T, body string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
return path
}
const approleBody = `vault:
addr: https://vault.service.consul:8200
approle_path: approle
role_id: abc-123
mount_point: pki_int
role_name: servers_default
output_path: /tmp/certmanager
`
func TestLoadAppRoleDefaults(t *testing.T) {
cfg, err := Load(writeConfig(t, approleBody))
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Vault.AuthMethod != AuthMethodAppRole {
t.Errorf("auth_method = %q, want %q (default)", cfg.Vault.AuthMethod, AuthMethodAppRole)
}
if cfg.Vault.JWTPath != DefaultJWTPath {
t.Errorf("jwt_path = %q, want %q", cfg.Vault.JWTPath, DefaultJWTPath)
}
if cfg.Vault.OutputPath != "/tmp/certmanager" {
t.Errorf("output_path = %q, want it read from under vault:", cfg.Vault.OutputPath)
}
if !cfg.Vault.SkipTLSVerify() {
t.Error("tls_skip_verify must default to true")
}
d, err := cfg.Vault.RequestTimeout()
if err != nil || d != DefaultTimeout {
t.Errorf("RequestTimeout() = %v, %v, want %v", d, err, DefaultTimeout)
}
}
func TestLoadOverrides(t *testing.T) {
cfg, err := Load(writeConfig(t, approleBody+" tls_skip_verify: false\n timeout: 5s\n"))
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Vault.SkipTLSVerify() {
t.Error("tls_skip_verify: false must disable skipping")
}
d, err := cfg.Vault.RequestTimeout()
if err != nil || d != 5*time.Second {
t.Errorf("RequestTimeout() = %v, %v, want 5s", d, err)
}
}
func TestLoadKubernetes(t *testing.T) {
cfg, err := Load(writeConfig(t, `vault:
addr: https://vault.service.consul:8200
auth_method: kubernetes
k8s_mount: k8s/au/syd1
k8s_role: puppet_certmanager
mount_point: pki_int
role_name: servers_default
`))
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Vault.K8sMount != "k8s/au/syd1" || cfg.Vault.K8sRole != "puppet_certmanager" {
t.Errorf("kubernetes keys not parsed: %+v", cfg.Vault)
}
}
func TestLoadErrors(t *testing.T) {
tests := []struct {
name string
body string
wantErr string
}{
{"missing addr", "vault:\n mount_point: pki_int\n role_name: r\n role_id: x\n approle_path: approle\n", "vault.addr"},
{"missing mount_point", "vault:\n addr: https://v:8200\n role_name: r\n role_id: x\n approle_path: approle\n", "vault.mount_point"},
{"missing role_name", "vault:\n addr: https://v:8200\n mount_point: pki_int\n role_id: x\n approle_path: approle\n", "vault.role_name"},
{"missing role_id", "vault:\n addr: https://v:8200\n mount_point: pki_int\n role_name: r\n approle_path: approle\n", "vault.role_id"},
{"missing approle_path", "vault:\n addr: https://v:8200\n mount_point: pki_int\n role_name: r\n role_id: x\n", "vault.approle_path"},
{"missing k8s_mount", "vault:\n addr: https://v:8200\n auth_method: kubernetes\n k8s_role: r\n mount_point: pki_int\n role_name: r\n", "vault.k8s_mount"},
{"missing k8s_role", "vault:\n addr: https://v:8200\n auth_method: kubernetes\n k8s_mount: m\n mount_point: pki_int\n role_name: r\n", "vault.k8s_role"},
{"unknown auth method", "vault:\n addr: https://v:8200\n auth_method: token\n mount_point: pki_int\n role_name: r\n", `unknown vault.auth_method "token"`},
{"bad timeout", approleBody + " timeout: soon\n", "not a valid duration"},
{"zero timeout", approleBody + " timeout: 0s\n", "must be positive"},
{"malformed yaml", "vault: [\n", "parse config"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := Load(writeConfig(t, tc.body))
if err == nil {
t.Fatal("Load succeeded, want error")
}
if !strings.Contains(err.Error(), tc.wantErr) {
t.Errorf("error = %v, want it to mention %q", err, tc.wantErr)
}
})
}
}
func TestLoadMissingFile(t *testing.T) {
_, err := Load(filepath.Join(t.TempDir(), "absent.yaml"))
if err == nil || !strings.Contains(err.Error(), "open config") {
t.Fatalf("error = %v, want an open config failure", err)
}
}
func TestDefaultPath(t *testing.T) {
if got := DefaultPath(); got != "/opt/certmanager/config.yaml" {
t.Errorf("DefaultPath() = %q, want the Puppet-managed path", got)
}
}
// The CA chain endpoint is unauthenticated, so a config with nothing but addr
// and mount_point has to load.
func TestLoadCAWithoutCredentials(t *testing.T) {
cfg, err := LoadCA(writeConfig(t, "vault:\n addr: https://vault.service.consul:8200\n mount_point: pki_int\n"))
if err != nil {
t.Fatalf("LoadCA: %v", err)
}
if cfg.Vault.Addr != "https://vault.service.consul:8200" {
t.Errorf("addr = %q", cfg.Vault.Addr)
}
if cfg.Vault.MountPoint != "pki_int" {
t.Errorf("mount_point = %q", cfg.Vault.MountPoint)
}
if cfg.Vault.RoleID != "" || cfg.Vault.RoleName != "" {
t.Errorf("LoadCA invented credentials: %+v", cfg.Vault)
}
}
// Load, by contrast, still demands everything issuance needs.
func TestLoadRejectsWhatLoadCAAccepts(t *testing.T) {
path := writeConfig(t, "vault:\n addr: https://vault.service.consul:8200\n mount_point: pki_int\n")
if _, err := Load(path); err == nil {
t.Fatal("Load accepted a config with no role_name or credentials")
}
}
func TestLoadCAErrors(t *testing.T) {
tests := []struct {
name string
body string
wantErr string
}{
{"no addr", "vault:\n mount_point: pki_int\n", "vault.addr is required"},
{"bad timeout", "vault:\n addr: https://v:8200\n timeout: soon\n", "not a valid duration"},
{"negative timeout", "vault:\n addr: https://v:8200\n timeout: -5s\n", "must be positive"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := LoadCA(writeConfig(t, tc.body))
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Errorf("error = %v, want it to mention %q", err, tc.wantErr)
}
})
}
}
func TestLoadCAMissingFile(t *testing.T) {
_, err := LoadCA(filepath.Join(t.TempDir(), "absent.yaml"))
if err == nil || !strings.Contains(err.Error(), "open config") {
t.Errorf("error = %v, want an open failure", err)
}
}
// An unknown auth_method is fatal for issuance but irrelevant to the CA path.
func TestLoadCAIgnoresAuthMethod(t *testing.T) {
if _, err := LoadCA(writeConfig(t, "vault:\n addr: https://v:8200\n auth_method: ldap\n mount_point: pki_int\n")); err != nil {
t.Errorf("LoadCA: %v", err)
}
}