package main import ( "bufio" "encoding/base64" "encoding/json" "fmt" "os" "os/exec" "strings" "syscall" "golang.org/x/term" ) // 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"` } // VaultSecret represents the structure for storing in Vault type VaultSecret struct { DockerConfigJSON string `json:".dockerconfigjson"` } // createDockerRegistrySecret creates a Docker registry secret in Vault func createDockerRegistrySecret() error { // Check dependencies fmt.Println("[INFO] Checking dependencies (kubectl and vault)...") if !commandExists("kubectl") { return fmt.Errorf("'kubectl' must be installed and in your PATH") } // Check kubectl context fmt.Println("[INFO] Checking Kubernetes context...") if err := runCommandQuiet("kubectl", "get", "nodes"); err != nil { return fmt.Errorf("unable to connect to Kubernetes cluster. Please check your kubectl configuration") } // Check if vault namespace exists fmt.Println("[INFO] Checking if 'vault' namespace exists...") if err := runCommandQuiet("kubectl", "get", "namespace", "vault"); err != nil { fmt.Println("[WARNING] 'vault' namespace does not exist. Please ensure Vault is properly installed in your cluster.") return fmt.Errorf("vault namespace not found") } // Get user input username, token, err := getUserInput() if err != nil { return fmt.Errorf("failed to get user input: %v", err) } // Create Docker config dockerConfig, err := createDockerConfig(username, token) if err != nil { return fmt.Errorf("failed to create Docker config: %v", err) } // Store in Vault fmt.Println("[INFO] Storing secret in Vault...") if err := storeInVault(dockerConfig); err != nil { return fmt.Errorf("failed to store secret in Vault: %v", err) } return nil } // getUserInput prompts the user for their GitHub username and token func getUserInput() (string, string, error) { reader := bufio.NewReader(os.Stdin) // Get username fmt.Print("Enter your GitHub username: ") username, err := reader.ReadString('\n') if err != nil { return "", "", err } username = strings.TrimSpace(username) if username == "" { return "", "", fmt.Errorf("username cannot be empty") } // Get token (hidden input) fmt.Print("Enter your GitHub personal access token: ") tokenBytes, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return "", "", err } fmt.Println() // Print newline after hidden input token := string(tokenBytes) if token == "" { return "", "", fmt.Errorf("token cannot be empty") } // Validate token format (GitHub tokens start with ghp_, gho_, ghu_, ghs_, or ghr_) if !strings.HasPrefix(token, "ghp_") && !strings.HasPrefix(token, "gho_") && !strings.HasPrefix(token, "ghu_") && !strings.HasPrefix(token, "ghs_") && !strings.HasPrefix(token, "ghr_") { fmt.Println("[WARNING] The token doesn't appear to be a GitHub personal access token (should start with ghp_, gho_, ghu_, ghs_, or ghr_)") fmt.Print("Do you want to continue anyway? (y/N): ") confirm, err := reader.ReadString('\n') if err != nil { return "", "", err } confirm = strings.TrimSpace(strings.ToLower(confirm)) if confirm != "y" && confirm != "yes" { return "", "", fmt.Errorf("operation cancelled by user") } } return username, token, nil } // createDockerConfig creates a Docker config JSON structure func createDockerConfig(username, token string) (string, error) { // Create base64 encoded auth string auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + token)) // Create Docker config structure config := DockerConfig{ Auths: map[string]DockerAuth{ "ghcr.io": { Username: username, Password: token, Auth: auth, }, }, } // Marshal to JSON configJSON, err := json.Marshal(config) if err != nil { return "", err } return string(configJSON), nil } // storeInVault stores the Docker config in Vault KV v2 func storeInVault(dockerConfigJSON string) error { fmt.Println("[INFO] Setting up Vault connection...") // Get the vault pod name fmt.Println("[INFO] Finding Vault pod...") vaultPod, err := getVaultPodName() if err != nil { return fmt.Errorf("failed to find Vault pod: %v", err) } fmt.Printf("[INFO] Using Vault pod: %s\n", vaultPod) // Get the root token fmt.Println("[INFO] Retrieving Vault root token...") rootToken, err := getVaultRootToken() if err != nil { return fmt.Errorf("failed to get Vault root token: %v", err) } // Set environment and authenticate fmt.Println("[INFO] Setting up Vault environment...") envSetupCmd := fmt.Sprintf("export VAULT_SKIP_VERIFY=true && export VAULT_ADDR='https://127.0.0.1:8200' && export VAULT_TOKEN='%s'", rootToken) // Test authentication authTestCmd := fmt.Sprintf("%s && vault token lookup", envSetupCmd) if err := runCommand("kubectl", "exec", "-n", "vault", vaultPod, "--", "sh", "-c", authTestCmd); err != nil { return fmt.Errorf("failed to validate Vault token: %v", err) } fmt.Println("[SUCCESS] Vault token is valid") // Store the secret using kubectl exec to vault pod fmt.Println("[INFO] Storing secret in Vault KV store...") // Use kubectl exec to run vault command inside the pod with proper env vars and token vaultCmd := fmt.Sprintf("%s && vault kv put secret/ghcr-auth .dockerconfigjson='%s'", envSetupCmd, dockerConfigJSON) if err := runCommand("kubectl", "exec", "-n", "vault", vaultPod, "--", "sh", "-c", vaultCmd); err != nil { return fmt.Errorf("failed to store secret in Vault: %v", err) } // Verify the secret was stored fmt.Println("[INFO] Verifying secret storage...") verifyCmd := fmt.Sprintf("%s && vault kv get secret/ghcr-auth", envSetupCmd) if err := runCommand("kubectl", "exec", "-n", "vault", vaultPod, "--", "sh", "-c", verifyCmd); err != nil { fmt.Println("[WARNING] Could not verify secret storage, but it may have been created successfully") fmt.Printf("[INFO] You can manually verify with: kubectl exec -n vault %s -- sh -c \"%s\"\n", vaultPod, verifyCmd) } else { fmt.Println("[SUCCESS] Secret stored and verified in Vault!") } return nil } // getVaultPodName gets the name of a running Vault pod func getVaultPodName() (string, error) { // Try to get vault-0 first (common StatefulSet naming) if err := runCommandQuiet("kubectl", "get", "pod", "vault-0", "-n", "vault"); err == nil { return "vault-0", nil } // Try other common Vault pod names commonNames := []string{"vault-0", "vault-1", "vault-2"} for _, name := range commonNames { if err := runCommandQuiet("kubectl", "get", "pod", name, "-n", "vault"); err == nil { return name, nil } } // Try to find any vault pod using labels cmd := exec.Command("kubectl", "get", "pods", "-n", "vault", "-l", "app.kubernetes.io/name=vault", "-o", "jsonpath={.items[0].metadata.name}") output, err := cmd.Output() if err == nil && len(output) > 0 { return string(output), nil } // Last resort: try to find any running pod in vault namespace cmd = exec.Command("kubectl", "get", "pods", "-n", "vault", "--field-selector=status.phase=Running", "-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 'vault' namespace. Please ensure Vault is properly deployed and running") } // getVaultRootToken retrieves the Vault root token from Kubernetes secrets func getVaultRootToken() (string, error) { fmt.Println("[INFO] Looking for Vault root token in vault-unseal-keys secret...") // Primary location: secret/vault-unseal-keys key vault-root in vault namespace cmd := exec.Command("kubectl", "get", "secret", "vault-unseal-keys", "-n", "vault", "-o", "jsonpath={.data.vault-root}") output, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed to get vault-unseal-keys secret from vault namespace: %v. Please ensure Vault is properly initialized", err) } if len(output) == 0 { return "", fmt.Errorf("vault-root key not found in vault-unseal-keys secret") } // The token is base64 encoded in the Kubernetes secret, decode it decoded, err := base64.StdEncoding.DecodeString(string(output)) if err != nil { return "", fmt.Errorf("failed to decode base64 root token: %v", err) } if len(decoded) == 0 { return "", fmt.Errorf("decoded root token is empty") } token := strings.TrimSpace(string(decoded)) fmt.Println("[SUCCESS] Found Vault root token") return token, nil }