package vault import ( "encoding/base64" "encoding/json" "fmt" "os/exec" "strings" "github.com/Pingu-Studio/MaidnCLI/internal/utils" ) // Constants for Vault and Kubernetes const ( vaultNamespace = "vault" vaultPodLabelSelector = "app.kubernetes.io/name=vault" vaultRootTokenSecretName = "vault-unseal-keys" vaultRootTokenSecretKey = "vault-root" defaultVaultAddress = "https://127.0.0.1:8200" // Exported constants GhcrSecretPath = "secret/ghcr-auth" DockerHubSecretPath = "secret/dockerhub-auth" GithubAuthSecretPath = "secret/github-auth" OrgManagementSecretPath = "secret/org-management-pat" DockerConfigJSONKey = ".dockerconfigjson" OrgManagementPATKey = "ORG_MANAGEMENT_PAT" GhcrRegistry = "ghcr.io" DockerHubRegistry = "https://index.docker.io/v1/" ) // DockerConfig represents the structure of a Docker config.json type DockerConfig struct { Auths map[string]DockerAuth `json:"auths"` } // DockerAuth represents the authentication info for a Docker registry type DockerAuth struct { Username string `json:"username"` Password string `json:"password"` Auth string `json:"auth"` } // VaultManager handles all interactions with Kubernetes and Vault. type VaultManager struct { podName string rootToken string } // NewVaultManager creates and initializes a new VaultManager. func NewVaultManager() (*VaultManager, error) { vm := &VaultManager{} fmt.Println("[INFO] Checking dependencies (kubectl)...") if !utils.CommandExists("kubectl") { return nil, fmt.Errorf("'kubectl' must be installed and in your PATH") } fmt.Println("[INFO] Finding Vault pod...") podName, err := vm.getVaultPodName() if err != nil { return nil, err } vm.podName = podName fmt.Printf("[INFO] Using Vault pod: %s\n", vm.podName) fmt.Println("[INFO] Retrieving Vault root token...") rootToken, err := vm.getVaultRootToken() if err != nil { return nil, err } vm.rootToken = rootToken fmt.Println("[SUCCESS] Found Vault root token.") fmt.Println("[INFO] Verifying Vault connection and token...") if err := vm.verifyVaultToken(); err != nil { return nil, fmt.Errorf("failed to validate Vault token: %w", err) } fmt.Println("[SUCCESS] Vault token is valid.") return vm, nil } func (vm *VaultManager) getVaultPodName() (string, error) { commonNames := []string{"vault-0", "vault-1", "vault-2"} for _, name := range commonNames { if err := utils.RunCommandQuiet("kubectl", "get", "pod", name, "-n", vaultNamespace); err == nil { return name, nil } } cmd := exec.Command("kubectl", "get", "pods", "-n", vaultNamespace, "-l", vaultPodLabelSelector, "-o", "jsonpath={.items[0].metadata.name}") output, err := cmd.Output() if err == nil && len(output) > 0 { return string(output), nil } return "", fmt.Errorf("could not find any running Vault pods in the '%s' namespace", vaultNamespace) } func (vm *VaultManager) getVaultRootToken() (string, error) { cmd := exec.Command("kubectl", "get", "secret", vaultRootTokenSecretName, "-n", vaultNamespace, "-o", fmt.Sprintf("jsonpath={.data.%s}", vaultRootTokenSecretKey)) output, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed to get '%s' secret from '%s' namespace: %w", vaultRootTokenSecretName, vaultNamespace, err) } if len(output) == 0 { return "", fmt.Errorf("key '%s' not found in secret '%s'", vaultRootTokenSecretKey, vaultRootTokenSecretName) } decoded, err := base64.StdEncoding.DecodeString(string(output)) if err != nil { return "", fmt.Errorf("failed to decode base64 root token: %w", err) } return strings.TrimSpace(string(decoded)), nil } func (vm *VaultManager) runVaultCommandQuiet(command string) error { envSetup := fmt.Sprintf("export VAULT_SKIP_VERIFY=true && export VAULT_ADDR='%s' && export VAULT_TOKEN='%s'", defaultVaultAddress, vm.rootToken) fullCmd := fmt.Sprintf("%s && %s", envSetup, command) return utils.RunCommandQuiet("kubectl", "exec", "-n", vaultNamespace, vm.podName, "--", "sh", "-c", fullCmd) } func (vm *VaultManager) verifyVaultToken() error { return vm.runVaultCommandQuiet("vault token lookup") } // StoreSecret stores a key-value secret at the specified path in Vault quietly. func (vm *VaultManager) StoreSecret(path string, data map[string]string) error { fmt.Printf("[INFO] Storing secret in Vault at path: %s...\n", path) var kvPairs []string for key, value := range data { escapedValue := strings.ReplaceAll(value, "'", "'\"'\"'") kvPairs = append(kvPairs, fmt.Sprintf("%s='%s'", key, escapedValue)) } vaultCmd := fmt.Sprintf("vault kv put %s %s", path, strings.Join(kvPairs, " ")) if err := vm.runVaultCommandQuiet(vaultCmd); err != nil { return fmt.Errorf("failed to store secret in Vault: %w", err) } fmt.Printf("[SUCCESS] Secret at '%s' stored successfully in Vault!\n", path) return nil } // CreateDockerConfig creates a Docker config JSON string. func CreateDockerConfig(registry, username, token string) (string, error) { auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + token)) config := DockerConfig{ Auths: map[string]DockerAuth{ registry: { Username: username, Password: token, Auth: auth, }, }, } configJSON, err := json.Marshal(config) if err != nil { return "", fmt.Errorf("failed to marshal docker config: %w", err) } return string(configJSON), nil }