maidn-cli/internal/vault/client.go
2025-10-28 08:08:05 +01:00

168 lines
5.3 KiB
Go

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
}