Compare commits

..

No commits in common. "eba3df7bcda7ef19d9c21347826d2caf835ea9b8" and "31751d7eb795b14257cf72a56900933c49858137" have entirely different histories.

2 changed files with 20 additions and 93 deletions

View file

@ -53,8 +53,6 @@ var openBaoStatus = getStatus
var commandTimeout = time.Minute var commandTimeout = time.Minute
var openBaoMutationTimeout = 5 * time.Minute
func EnsureRecoveryIdentity(identityPath string) (string, error) { func EnsureRecoveryIdentity(identityPath string) (string, error) {
if _, err := os.Stat(identityPath); os.IsNotExist(err) { if _, err := os.Stat(identityPath); os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(identityPath), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(identityPath), 0700); err != nil {
@ -264,11 +262,8 @@ func writeSecret(kubeconfig, rootToken, secretPath string, values map[string]str
decodes = append(decodes, fmt.Sprintf("value%d=$(printf '%%s' \"$value%d_b64\" | base64 -d; printf x)\nvalue%d=${value%d%%x}", index, index, index, index)) decodes = append(decodes, fmt.Sprintf("value%d=$(printf '%%s' \"$value%d_b64\" | base64 -d; printf x)\nvalue%d=${value%d%%x}", index, index, index, index))
} }
script := "read -r root_token\n" + strings.Join(reads, "\n") + "\n" + strings.Join(decodes, "\n") + "\nexport BAO_TOKEN=\"$root_token\"\nbao kv put secret/" + secretPath + " " + strings.Join(arguments, " ") + " >/dev/null" script := "read -r root_token\n" + strings.Join(reads, "\n") + "\n" + strings.Join(decodes, "\n") + "\nexport BAO_TOKEN=\"$root_token\"\nbao kv put secret/" + secretPath + " " + strings.Join(arguments, " ") + " >/dev/null"
_, err := execInPodMutation(kubeconfig, []byte(input.String()), "sh", "-ec", script) _, err := execInPod(kubeconfig, []byte(input.String()), "sh", "-ec", script)
if err != nil { return err
return fmt.Errorf("write OpenBao secret %q", secretPath)
}
return nil
} }
func waitForPod(kubeconfig string) error { func waitForPod(kubeconfig string) error {
@ -364,13 +359,13 @@ path "secret/metadata/cicd/*" {
capabilities = ["list", "read"] capabilities = ["list", "read"]
} }
EOF EOF
bao policy write external-secrets /tmp/external-secrets.hcl >/dev/null || fail platform-external-secrets-policy bao policy write external-secrets /tmp/external-secrets.hcl >/dev/null 2>&1 || fail platform-external-secrets-policy
rm -f /tmp/external-secrets.hcl rm -f /tmp/external-secrets.hcl
bao write auth/kubernetes/role/external-secrets bound_service_account_names=external-secrets bound_service_account_namespaces=external-secrets policies=external-secrets ttl=1h >/dev/null 2>&1 || fail platform-external-secrets-role` bao write auth/kubernetes/role/external-secrets bound_service_account_names=external-secrets bound_service_account_namespaces=external-secrets policies=external-secrets ttl=1h >/dev/null 2>&1 || fail platform-external-secrets-role`
input := []byte(rootToken + "\n" + reviewerToken + "\n") input := []byte(rootToken + "\n" + reviewerToken + "\n")
output, err := execInPodMutation(kubeconfig, input, "sh", "-ec", script) output, err := execInPod(kubeconfig, input, "sh", "-ec", script)
if err != nil { if err != nil {
return openBaoMutationError("configure Kubernetes auth", err, output, rootToken, reviewerToken) return fmt.Errorf("configure Kubernetes auth: %w: %s", err, strings.TrimSpace(string(output)))
} }
return nil return nil
} }
@ -409,31 +404,8 @@ func ConfigureSecretGrants(kubeconfig, identityPath, bundlePath string, grants [
script.WriteString("rm -f /tmp/" + name + ".hcl\n") script.WriteString("rm -f /tmp/" + name + ".hcl\n")
script.WriteString("bao write auth/kubernetes/role/" + name + " bound_service_account_names=" + name + " bound_service_account_namespaces=" + namespace + " policies=" + name + " ttl=1h >/dev/null\n") script.WriteString("bao write auth/kubernetes/role/" + name + " bound_service_account_names=" + name + " bound_service_account_namespaces=" + namespace + " policies=" + name + " ttl=1h >/dev/null\n")
} }
output, err := execInPodMutation(kubeconfig, []byte(material.RootToken+"\n"), "sh", "-ec", script.String()) _, err = execInPod(kubeconfig, []byte(material.RootToken+"\n"), "sh", "-ec", script.String())
if err != nil { return err
return openBaoMutationError("configure OpenBao secret grants", err, output, material.RootToken)
}
return nil
}
func openBaoMutationError(action string, err error, output []byte, sensitive ...string) error {
diagnostic := redactOpenBaoDiagnostic(strings.TrimSpace(string(output)), sensitive...)
if diagnostic == "" && err != nil {
diagnostic = redactOpenBaoDiagnostic(err.Error(), sensitive...)
}
if diagnostic == "" {
return errors.New(action)
}
return fmt.Errorf("%s: %s", action, diagnostic)
}
func redactOpenBaoDiagnostic(diagnostic string, sensitive ...string) string {
for _, value := range sensitive {
if value != "" {
diagnostic = strings.ReplaceAll(diagnostic, value, "[REDACTED]")
}
}
return diagnostic
} }
func refreshExternalSecrets(kubeconfig string) error { func refreshExternalSecrets(kubeconfig string) error {
@ -481,11 +453,6 @@ var execInPod = func(kubeconfig string, input []byte, args ...string) ([]byte, e
return commandOutput(input, "kubectl", command...) return commandOutput(input, "kubectl", command...)
} }
var execInPodMutation = func(kubeconfig string, input []byte, args ...string) ([]byte, error) {
command := append([]string{"--kubeconfig", kubeconfig, "-n", "openbao", "exec", "-i", "openbao-0", "--"}, args...)
return commandOutputWithTimeout(input, openBaoMutationTimeout, "kubectl", command...)
}
var execInUnsealController = func(kubeconfig, script string) ([]byte, error) { var execInUnsealController = func(kubeconfig, script string) ([]byte, error) {
command := []string{"--kubeconfig", kubeconfig, "-n", "openbao", "exec", "deployment/openbao-unseal", "--", "sh", "-ec", script} command := []string{"--kubeconfig", kubeconfig, "-n", "openbao", "exec", "deployment/openbao-unseal", "--", "sh", "-ec", script}
return commandOutput(nil, "kubectl", command...) return commandOutput(nil, "kubectl", command...)
@ -497,17 +464,13 @@ var kubectlOutput = func(kubeconfig string, args ...string) ([]byte, error) {
} }
func commandOutput(input []byte, name string, args ...string) ([]byte, error) { func commandOutput(input []byte, name string, args ...string) ([]byte, error) {
return commandOutputWithTimeout(input, commandTimeout, name, args...) ctx, cancel := context.WithTimeout(context.Background(), commandTimeout)
}
func commandOutputWithTimeout(input []byte, timeout time.Duration, name string, args ...string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, name, args...) cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdin = bytes.NewReader(input) cmd.Stdin = bytes.NewReader(input)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if errors.Is(ctx.Err(), context.DeadlineExceeded) { if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return output, fmt.Errorf("%s timed out after %s", name, timeout) return output, fmt.Errorf("%s timed out after %s", name, commandTimeout)
} }
return output, err return output, err
} }

View file

@ -7,7 +7,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"time"
"github.com/Pingu-Studio/MaidnCLI/internal/config" "github.com/Pingu-Studio/MaidnCLI/internal/config"
) )
@ -65,10 +64,10 @@ func TestUnsealFallsBackToControllerSecret(t *testing.T) {
} }
func TestWriteSecretFramesMultilineValues(t *testing.T) { func TestWriteSecretFramesMultilineValues(t *testing.T) {
original := execInPodMutation original := execInPod
t.Cleanup(func() { execInPodMutation = original }) t.Cleanup(func() { execInPod = original })
var input, script string var input, script string
execInPodMutation = func(_ string, contents []byte, args ...string) ([]byte, error) { execInPod = func(_ string, contents []byte, args ...string) ([]byte, error) {
input, script = string(contents), args[len(args)-1] input, script = string(contents), args[len(args)-1]
return nil, nil return nil, nil
} }
@ -78,19 +77,6 @@ func TestWriteSecretFramesMultilineValues(t *testing.T) {
} }
} }
func TestWriteSecretRedactsMutationFailure(t *testing.T) {
original := execInPodMutation
t.Cleanup(func() { execInPodMutation = original })
const value = "must-not-leak"
execInPodMutation = func(_ string, _ []byte, _ ...string) ([]byte, error) {
return []byte(value), errors.New(value)
}
err := writeSecret("kubeconfig", "root-token", "cicd/demo", map[string]string{"password": value})
if err == nil || err.Error() != `write OpenBao secret "cicd/demo"` || strings.Contains(err.Error(), value) {
t.Fatalf("secret write error leaked a value: %v", err)
}
}
func TestRefreshExternalSecretsIsReadyGatedAndScoped(t *testing.T) { func TestRefreshExternalSecretsIsReadyGatedAndScoped(t *testing.T) {
original := kubectlOutput original := kubectlOutput
t.Cleanup(func() { kubectlOutput = original }) t.Cleanup(func() { kubectlOutput = original })
@ -127,13 +113,13 @@ func TestRefreshExternalSecretsSkipsWebhookBeforeTekton(t *testing.T) {
} }
func TestConfigureSecretGrantsScopesApplicationAndSharedPaths(t *testing.T) { func TestConfigureSecretGrantsScopesApplicationAndSharedPaths(t *testing.T) {
originalDecrypt, originalExec := decryptRecovery, execInPodMutation originalDecrypt, originalExec := decryptRecovery, execInPod
t.Cleanup(func() { decryptRecovery, execInPodMutation = originalDecrypt, originalExec }) t.Cleanup(func() { decryptRecovery, execInPod = originalDecrypt, originalExec })
decryptRecovery = func(_, _ string) ([]byte, error) { decryptRecovery = func(_, _ string) ([]byte, error) {
return []byte(`{"unseal_keys_b64":["share"],"unseal_threshold":1,"root_token":"root"}`), nil return []byte(`{"unseal_keys_b64":["share"],"unseal_threshold":1,"root_token":"root"}`), nil
} }
var script string var script string
execInPodMutation = func(_ string, input []byte, args ...string) ([]byte, error) { execInPod = func(_ string, input []byte, args ...string) ([]byte, error) {
if string(input) != "root\n" || len(args) != 3 || args[0] != "sh" || args[1] != "-ec" { if string(input) != "root\n" || len(args) != 3 || args[0] != "sh" || args[1] != "-ec" {
t.Fatal("secret grant did not use root token through stdin") t.Fatal("secret grant did not use root token through stdin")
} }
@ -164,27 +150,11 @@ func TestConfigureSecretGrantsScopesApplicationAndSharedPaths(t *testing.T) {
} }
} }
func TestConfigureSecretGrantsPreservesRedactedPolicyDiagnostics(t *testing.T) {
originalDecrypt, originalExec := decryptRecovery, execInPodMutation
t.Cleanup(func() { decryptRecovery, execInPodMutation = originalDecrypt, originalExec })
const rootToken = "must-not-leak"
decryptRecovery = func(_, _ string) ([]byte, error) {
return []byte(`{"unseal_keys_b64":["share"],"unseal_threshold":1,"root_token":"must-not-leak"}`), nil
}
execInPodMutation = func(_ string, _ []byte, _ ...string) ([]byte, error) {
return []byte("policy write denied for " + rootToken), errors.New("exit status 1")
}
err := ConfigureSecretGrants("kubeconfig", "identity", "bundle", []config.SecretGrant{{Application: "orders-api", Consumer: "publish"}})
if err == nil || !strings.Contains(err.Error(), "policy write denied") || strings.Contains(err.Error(), rootToken) {
t.Fatalf("policy diagnostics were not useful and redacted: %v", err)
}
}
func TestConfigureKubernetesAuthLimitsPlatformStore(t *testing.T) { func TestConfigureKubernetesAuthLimitsPlatformStore(t *testing.T) {
original := execInPodMutation original := execInPod
t.Cleanup(func() { execInPodMutation = original }) t.Cleanup(func() { execInPod = original })
var script string var script string
execInPodMutation = func(_ string, _ []byte, args ...string) ([]byte, error) { execInPod = func(_ string, _ []byte, args ...string) ([]byte, error) {
script = args[len(args)-1] script = args[len(args)-1]
return nil, nil return nil, nil
} }
@ -201,12 +171,6 @@ func TestConfigureKubernetesAuthLimitsPlatformStore(t *testing.T) {
} }
} }
func TestOpenBaoMutationTimeoutIsSeparateFromProbeTimeout(t *testing.T) {
if commandTimeout != time.Minute || openBaoMutationTimeout != 5*time.Minute {
t.Fatalf("probe timeout %s, mutation timeout %s", commandTimeout, openBaoMutationTimeout)
}
}
func TestReadRecoveryMaterialDecryptsAndValidatesBundle(t *testing.T) { func TestReadRecoveryMaterialDecryptsAndValidatesBundle(t *testing.T) {
original := decryptRecovery original := decryptRecovery
t.Cleanup(func() { decryptRecovery = original }) t.Cleanup(func() { decryptRecovery = original })