168 lines
5.3 KiB
Go
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 = "docker.io"
|
|
)
|
|
|
|
// 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
|
|
}
|