Go e HashiCorp Vault: Gerenciamento de Segredos
HashiCorp Vault é a solução líder para gerenciamento de segredos em ambientes cloud-native. Sua integração com Go permite proteger credenciais, APIs keys, certificados e dados sensíveis de forma segura e auditável.
Neste guia completo, você aprenderá a integrar Vault com aplicações Go, implementar padrões de segurança, usar secrets dinâmicos e automatizar rotação de credenciais.
Índice
- Fundamentos do Vault
- Instalação e Configuração
- Cliente Go do Vault
- Autenticação
- Leitura e Escrita de Secrets
- Secrets Dinâmicos
- Rotação Automática
- Padrões de Produção
Fundamentos do Vault
Arquitetura
┌─────────────────────────────────────────────────────────────┐
│ Aplicação Go │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Vault Client Go │ │
│ │ • Autenticação │ │
│ │ • Cache de tokens │ │
│ │ • Renovação automática │ │
│ └─────────────────────────┬───────────────────────────┘ │
└────────────────────────────┼────────────────────────────────┘
│ HTTPS/TLS
▼
┌─────────────────────────────────────────────────────────────┐
│ Vault Server │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Autenticação│ │ Policies │ │ Secret │ │
│ │ Methods │ │ (ACL) │ │ Engines │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Transit │ │ Database │ │ PKI │ │
│ │ (Encryption)│ │ (Dynamic) │ │ (Certificates) │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key Features
1. Secrets Estáticos Key-Value store para credenciais, API keys, certificados.
2. Secrets Dinâmicos Credenciais geradas sob demanda com TTL automático (AWS, DB, etc).
3. Encryption as a Service Criptografe dados sem gerenciar chaves via Transit secret engine.
4. PKI as a Service Emita e gerencie certificados TLS automaticamente.
Instalação e Configuração
Instalação do Cliente Go
go get github.com/hashicorp/vault/api
Configuração Básica
package vault
import (
"context"
"fmt"
"time"
vault "github.com/hashicorp/vault/api"
)
// Config representa a configuração do cliente Vault
type Config struct {
Address string // Vault server URL
Token string // Token de autenticação
Timeout time.Duration // Timeout para requisições
MaxRetries int
RetryDelay time.Duration
}
// Client é um wrapper seguro para o cliente Vault
type Client struct {
client *vault.Client
config Config
}
// NewClient cria um novo cliente Vault
func NewClient(cfg Config) (*Client, error) {
config := vault.DefaultConfig()
config.Address = cfg.Address
config.Timeout = cfg.Timeout
// Configura retry
if cfg.MaxRetries > 0 {
config.MaxRetries = cfg.MaxRetries
}
client, err := vault.NewClient(config)
if err != nil {
return nil, fmt.Errorf("falha ao criar cliente Vault: %w", err)
}
// Configura token inicial
if cfg.Token != "" {
client.SetToken(cfg.Token)
}
return &Client{
client: client,
config: cfg,
}, nil
}
// SetToken atualiza o token de autenticação
func (c *Client) SetToken(token string) {
c.client.SetToken(token)
c.config.Token = token
}
// Health verifica o status do servidor Vault
func (c *Client) Health(ctx context.Context) (*vault.HealthResponse, error) {
health, err := c.client.Sys().Health()
if err != nil {
return nil, fmt.Errorf("falha ao verificar health: %w", err)
}
return health, nil
}
Autenticação
Múltiplos Métodos de Autenticação
package vault
import (
"context"
"fmt"
"os"
vault "github.com/hashicorp/vault/api"
)
// AuthManager gerencia autenticação com Vault
type AuthManager struct {
client *vault.Client
}
func NewAuthManager(client *vault.Client) *AuthManager {
return &AuthManager{client: client}
}
// ============================================================
// 1. Token Authentication (Desenvolvimento)
// ============================================================
func (am *AuthManager) LoginWithToken(token string) error {
if token == "" {
// Tenta obter do ambiente
token = os.Getenv("VAULT_TOKEN")
}
if token == "" {
return fmt.Errorf("token não fornecido")
}
am.client.SetToken(token)
// Valida token
_, err := am.client.Auth().Token().LookupSelf()
if err != nil {
return fmt.Errorf("token inválido: %w", err)
}
return nil
}
// ============================================================
// 2. AppRole Authentication (Production)
// ============================================================
type AppRoleCredentials struct {
RoleID string
SecretID string
}
func (am *AuthManager) LoginWithAppRole(ctx context.Context, creds AppRoleCredentials) (*vault.Secret, error) {
data := map[string]interface{}{
"role_id": creds.RoleID,
"secret_id": creds.SecretID,
}
secret, err := am.client.Logical().WriteWithContext(ctx, "auth/approle/login", data)
if err != nil {
return nil, fmt.Errorf("falha na autenticação AppRole: %w", err)
}
if secret == nil || secret.Auth == nil {
return nil, fmt.Errorf("resposta de autenticação inválida")
}
// Define o token obtido
am.client.SetToken(secret.Auth.ClientToken)
return secret, nil
}
// ============================================================
// 3. Kubernetes Authentication (K8s clusters)
// ============================================================
func (am *AuthManager) LoginWithKubernetes(ctx context.Context, role string, jwtPath string) (*vault.Secret, error) {
if jwtPath == "" {
jwtPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
}
// Lê JWT do service account
jwt, err := os.ReadFile(jwtPath)
if err != nil {
return nil, fmt.Errorf("falha ao ler JWT: %w", err)
}
data := map[string]interface{}{
"jwt": string(jwt),
"role": role,
}
secret, err := am.client.Logical().WriteWithContext(ctx, "auth/kubernetes/login", data)
if err != nil {
return nil, fmt.Errorf("falha na autenticação Kubernetes: %w", err)
}
am.client.SetToken(secret.Auth.ClientToken)
return secret, nil
}
// ============================================================
// 4. AWS IAM Authentication
// ============================================================
func (am *AuthManager) LoginWithAWS(ctx context.Context, role string) (*vault.Secret, error) {
// Usa AWS SDK para obter headers assinados
// ou assume role credentials
// Exemplo simplificado - em produção use aws-sdk-go-v2
data := map[string]interface{}{
"role": role,
"iam_http_request_method": "POST",
"iam_request_url": "...",
"iam_request_body": "...",
"iam_request_headers": "...",
}
secret, err := am.client.Logical().WriteWithContext(ctx, "auth/aws/login", data)
if err != nil {
return nil, fmt.Errorf("falha na autenticação AWS: %w", err)
}
am.client.SetToken(secret.Auth.ClientToken)
return secret, nil
}
// ============================================================
// 3. Token Renewal
// ============================================================
type TokenRenewer struct {
client *vault.Client
secret *vault.Secret
stopCh chan struct{}
isRenewing bool
}
func NewTokenRenewer(client *vault.Client, secret *vault.Secret) *TokenRenewer {
return &TokenRenewer{
client: client,
secret: secret,
stopCh: make(chan struct{}),
}
}
func (tr *TokenRenewer) Start() {
if tr.isRenewing {
return
}
tr.isRenewing = true
go tr.renewLoop()
}
func (tr *TokenRenewer) Stop() {
close(tr.stopCh)
tr.isRenewing = false
}
func (tr *TokenRenewer) renewLoop() {
// Renova em 2/3 do tempo de expiração
renewInterval := tr.secret.Auth.LeaseDuration * 2 / 3
ticker := time.NewTicker(time.Duration(renewInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
_, err := tr.client.Auth().Token().RenewSelf(tr.secret.Auth.LeaseDuration)
if err != nil {
fmt.Printf("Falha ao renovar token: %v\n", err)
// Em produção: alertar e re-autenticar
}
case <-tr.stopCh:
return
}
}
}
Leitura e Escrita de Secrets
KV Secrets Engine v2
package vault
import (
"context"
"encoding/json"
"fmt"
"path"
vault "github.com/hashicorp/vault/api"
)
// SecretsManager gerencia operações de secrets
type SecretsManager struct {
client *vault.Client
mountPath string // e.g., "secret" ou "kv"
}
func NewSecretsManager(client *vault.Client, mountPath string) *SecretsManager {
if mountPath == "" {
mountPath = "secret"
}
return &SecretsManager{
client: client,
mountPath: mountPath,
}
}
// ============================================================
// Leitura de Secrets
// ============================================================
// SecretData contém dados deserializados
type SecretData map[string]interface{}
func (sm *SecretsManager) GetSecret(ctx context.Context, path string) (SecretData, error) {
fullPath := sm.buildPath(path)
secret, err := sm.client.Logical().ReadWithContext(ctx, fullPath)
if err != nil {
return nil, fmt.Errorf("falha ao ler secret: %w", err)
}
if secret == nil {
return nil, fmt.Errorf("secret não encontrado: %s", path)
}
// Extrai dados (KV v2 tem estrutura diferente)
data, ok := secret.Data["data"].(map[string]interface{})
if !ok {
// Pode ser KV v1
data = secret.Data
}
return SecretData(data), nil
}
// GetSecretString retorna um valor específico como string
func (sm *SecretsManager) GetSecretString(ctx context.Context, path, key string) (string, error) {
data, err := sm.GetSecret(ctx, path)
if err != nil {
return "", err
}
value, ok := data[key].(string)
if !ok {
return "", fmt.Errorf("chave %s não encontrada ou não é string", key)
}
return value, nil
}
// GetSecretAs deserializa secret em struct
func (sm *SecretsManager) GetSecretAs(ctx context.Context, path string, target interface{}) error {
data, err := sm.GetSecret(ctx, path)
if err != nil {
return err
}
// Converte para JSON e depois para struct
jsonBytes, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("falha ao serializar dados: %w", err)
}
if err := json.Unmarshal(jsonBytes, target); err != nil {
return fmt.Errorf("falha ao deserializar dados: %w", err)
}
return nil
}
// ============================================================
// Escrita de Secrets
// ============================================================
func (sm *SecretsManager) PutSecret(ctx context.Context, path string, data SecretData) error {
fullPath := sm.buildPath(path)
// KV v2 usa data wrapper
payload := map[string]interface{}{
"data": data,
}
_, err := sm.client.Logical().WriteWithContext(ctx, fullPath, payload)
if err != nil {
return fmt.Errorf("falha ao escrever secret: %w", err)
}
return nil
}
// PutSecretStruct serializa struct e salva
func (sm *SecretsManager) PutSecretStruct(ctx context.Context, path string, data interface{}) error {
jsonBytes, err := json.Marshal(data)
if err != nil {
return err
}
var jsonData map[string]interface{}
if err := json.Unmarshal(jsonBytes, &jsonData); err != nil {
return err
}
return sm.PutSecret(ctx, path, jsonData)
}
// ============================================================
// Deleção e Versionamento
// ============================================================
func (sm *SecretsManager) DeleteSecret(ctx context.Context, path string) error {
fullPath := sm.buildPath(path)
_, err := sm.client.Logical().DeleteWithContext(ctx, fullPath)
return err
}
// DeleteSecretVersion deleta versão específica (KV v2)
func (sm *SecretsManager) DeleteSecretVersion(ctx context.Context, path string, version int) error {
fullPath := sm.buildPath(path)
payload := map[string]interface{}{
"versions": []int{version},
}
_, err := sm.client.Logical().DeleteWithDataWithContext(ctx, fullPath, payload)
return err
}
// ListSecrets lista secrets em um path
func (sm *SecretsManager) ListSecrets(ctx context.Context, path string) ([]string, error) {
fullPath := sm.buildPath(path)
secret, err := sm.client.Logical().ListWithContext(ctx, fullPath)
if err != nil {
return nil, err
}
if secret == nil {
return []string{}, nil
}
keys, ok := secret.Data["keys"].([]interface{})
if !ok {
return []string{}, nil
}
var result []string
for _, key := range keys {
if str, ok := key.(string); ok {
result = append(result, str)
}
}
return result, nil
}
// Helper para construir path
func (sm *SecretsManager) buildPath(p string) string {
// KV v2: secret/data/path
return path.Join(sm.mountPath, "data", p)
}
Secrets Dinâmicos
Database Secrets Engine
package vault
import (
"context"
"database/sql"
"fmt"
"time"
_ "github.com/lib/pq"
vault "github.com/hashicorp/vault/api"
)
// DatabaseCredentials representa credenciais dinâmicas
type DatabaseCredentials struct {
Username string
Password string
Host string
Port int
Database string
LeaseID string
LeaseDuration int
Renewable bool
}
// ConnectionString retorna string de conexão
func (dc *DatabaseCredentials) ConnectionString() string {
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=require",
dc.Username, dc.Password, dc.Host, dc.Port, dc.Database)
}
// DatabaseManager gerencia credenciais dinâmicas de DB
type DatabaseManager struct {
client *vault.Client
dbHost string
dbPort int
dbName string
}
func NewDatabaseManager(client *vault.Client, host string, port int, dbName string) *DatabaseManager {
return &DatabaseManager{
client: client,
dbHost: host,
dbPort: port,
dbName: dbName,
}
}
// GenerateCredentials obtém credenciais dinâmicas do Vault
func (dm *DatabaseManager) GenerateCredentials(ctx context.Context, role string) (*DatabaseCredentials, error) {
path := fmt.Sprintf("database/creds/%s", role)
secret, err := dm.client.Logical().ReadWithContext(ctx, path)
if err != nil {
return nil, fmt.Errorf("falha ao gerar credenciais: %w", err)
}
if secret == nil {
return nil, fmt.Errorf("role não encontrado: %s", role)
}
creds := &DatabaseCredentials{
Username: secret.Data["username"].(string),
Password: secret.Data["password"].(string),
Host: dm.dbHost,
Port: dm.dbPort,
Database: dm.dbName,
LeaseID: secret.LeaseID,
LeaseDuration: secret.LeaseDuration,
Renewable: secret.Renewable,
}
return creds, nil
}
// CreateConnection cria conexão SQL com credenciais dinâmicas
func (dm *DatabaseManager) CreateConnection(ctx context.Context, role string) (*sql.DB, *DatabaseCredentials, error) {
creds, err := dm.GenerateCredentials(ctx, role)
if err != nil {
return nil, nil, err
}
db, err := sql.Open("postgres", creds.ConnectionString())
if err != nil {
return nil, nil, err
}
if err := db.PingContext(ctx); err != nil {
return nil, nil, fmt.Errorf("falha ao conectar: %w", err)
}
return db, creds, nil
}
// ============================================================
// AWS Dynamic Credentials
// ============================================================
type AWSCredentials struct {
AccessKeyID string
SecretAccessKey string
SessionToken string
LeaseID string
LeaseDuration int
}
func (dm *DatabaseManager) GenerateAWSCredentials(ctx context.Context, role string) (*AWSCredentials, error) {
path := fmt.Sprintf("aws/creds/%s", role)
secret, err := dm.client.Logical().ReadWithContext(ctx, path)
if err != nil {
return nil, err
}
return &AWSCredentials{
AccessKeyID: secret.Data["access_key"].(string),
SecretAccessKey: secret.Data["secret_key"].(string),
SessionToken: secret.Data["security_token"].(string),
LeaseID: secret.LeaseID,
LeaseDuration: secret.LeaseDuration,
}, nil
}
Rotação Automática
Lease Renewal System
package vault
import (
"context"
"fmt"
"sync"
"time"
vault "github.com/hashicorp/vault/api"
)
// LeaseManager gerencia renovação automática de leases
type LeaseManager struct {
client *vault.Client
leases map[string]*ManagedLease
mu sync.RWMutex
stopCh chan struct{}
wg sync.WaitGroup
}
type ManagedLease struct {
LeaseID string
Renewable bool
LeaseDuration int
LastRenewal time.Time
onRenewal func() error
onExpiry func()
}
func NewLeaseManager(client *vault.Client) *LeaseManager {
lm := &LeaseManager{
client: client,
leases: make(map[string]*ManagedLease),
stopCh: make(chan struct{}),
}
go lm.renewalLoop()
return lm
}
func (lm *LeaseManager) Stop() {
close(lm.stopCh)
lm.wg.Wait()
}
// RegisterLease adiciona lease para gerenciamento
func (lm *LeaseManager) RegisterLease(lease *ManagedLease) {
lm.mu.Lock()
defer lm.mu.Unlock()
lm.leases[lease.LeaseID] = lease
}
// UnregisterLease remove lease do gerenciamento
func (lm *LeaseManager) UnregisterLease(leaseID string) {
lm.mu.Lock()
defer lm.mu.Unlock()
delete(lm.leases, leaseID)
}
func (lm *LeaseManager) renewalLoop() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
lm.checkAndRenewLeases()
case <-lm.stopCh:
return
}
}
}
func (lm *LeaseManager) checkAndRenewLeases() {
lm.mu.RLock()
leases := make([]*ManagedLease, 0, len(lm.leases))
for _, lease := range lm.leases {
leases = append(leases, lease)
}
lm.mu.RUnlock()
for _, lease := range leases {
deadline := lease.LastRenewal.Add(
time.Duration(lease.LeaseDuration*2/3) * time.Second,
)
if time.Now().After(deadline) && lease.Renewable {
lm.wg.Add(1)
go func(l *ManagedLease) {
defer lm.wg.Done()
lm.renewLease(l)
}(lease)
}
}
}
func (lm *LeaseManager) renewLease(lease *ManagedLease) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
secret, err := lm.client.Sys().RenewWithContext(ctx, lease.LeaseID, lease.LeaseDuration)
if err != nil {
fmt.Printf("Falha ao renovar lease %s: %v\n", lease.LeaseID, err)
if lease.onExpiry != nil {
lease.onExpiry()
}
lm.UnregisterLease(lease.LeaseID)
return err
}
lease.LeaseDuration = secret.LeaseDuration
lease.LastRenewal = time.Now()
fmt.Printf("Lease %s renovado. Nova duração: %ds\n", lease.LeaseID, lease.LeaseDuration)
if lease.onRenewal != nil {
if err := lease.onRenewal(); err != nil {
fmt.Printf("Callback de renovação falhou: %v\n", err)
}
}
return nil
}
// RevokeLease revoga lease imediatamente
func (lm *LeaseManager) RevokeLease(leaseID string) error {
err := lm.client.Sys().Revoke(leaseID)
if err != nil {
return err
}
lm.UnregisterLease(leaseID)
return nil
}
// RevokeAll revoga todos os leases
func (lm *LeaseManager) RevokeAll() error {
lm.mu.Lock()
leases := make([]string, 0, len(lm.leases))
for leaseID := range lm.leases {
leases = append(leases, leaseID)
}
lm.leases = make(map[string]*ManagedLease)
lm.mu.Unlock()
for _, leaseID := range leases {
if err := lm.client.Sys().Revoke(leaseID); err != nil {
fmt.Printf("Falha ao revogar lease %s: %v\n", leaseID, err)
}
}
return nil
}
Padrões de Produção
1. Secure Configuration
package config
import (
"context"
"fmt"
"os"
"golang-site/internal/vault"
)
// DatabaseConfig carrega configuração segura do Vault
type DatabaseConfig struct {
Host string `vault:"host"`
Port int `vault:"port"`
Database string `vault:"database"`
Username string `vault:"username"`
Password string `vault:"password"`
SSLMode string `vault:"sslmode"`
}
// LoadFromVault carrega configuração do Vault
func LoadFromVault(ctx context.Context, path string) (*DatabaseConfig, error) {
client, err := vault.NewClient(vault.Config{
Address: os.Getenv("VAULT_ADDR"),
Token: os.Getenv("VAULT_TOKEN"),
})
if err != nil {
return nil, err
}
sm := vault.NewSecretsManager(client.Client(), "secret")
var config DatabaseConfig
if err := sm.GetSecretAs(ctx, path, &config); err != nil {
return nil, err
}
return &config, nil
}
2. Connection Pooling com Credenciais Dinâmicas
package database
import (
"context"
"database/sql"
"sync"
"time"
_ "github.com/lib/pq"
"golang-site/internal/vault"
)
type DynamicDB struct {
vaultManager *vault.DatabaseManager
role string
db *sql.DB
creds *vault.DatabaseCredentials
mu sync.RWMutex
leaseManager *vault.LeaseManager
}
func NewDynamicDB(vaultClient *vault.Client, role string, host string, port int, dbName string) *DynamicDB {
return &DynamicDB{
vaultManager: vault.NewDatabaseManager(vaultClient, host, port, dbName),
role: role,
leaseManager: vault.NewLeaseManager(vaultClient),
}
}
func (ddb *DynamicDB) Initialize(ctx context.Context) error {
return ddb.rotateCredentials(ctx)
}
func (ddb *DynamicDB) GetDB() *sql.DB {
ddb.mu.RLock()
defer ddb.mu.RUnlock()
return ddb.db
}
func (ddb *DynamicDB) rotateCredentials(ctx context.Context) error {
// Fecha conexão anterior
ddb.mu.Lock()
if ddb.db != nil {
ddb.db.Close()
}
ddb.mu.Unlock()
// Gera novas credenciais
db, creds, err := ddb.vaultManager.CreateConnection(ctx, ddb.role)
if err != nil {
return err
}
// Configura pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Duration(creds.LeaseDuration) * time.Second)
ddb.mu.Lock()
ddb.db = db
ddb.creds = creds
ddb.mu.Unlock()
// Registra lease para renovação automática
if creds.Renewable {
ddb.leaseManager.RegisterLease(&vault.ManagedLease{
LeaseID: creds.LeaseID,
Renewable: creds.Renewable,
LeaseDuration: creds.LeaseDuration,
LastRenewal: time.Now(),
onExpiry: func() {
// Rotaciona credenciais quando expirar
ddb.rotateCredentials(context.Background())
},
})
}
return nil
}
func (ddb *DynamicDB) Close() {
ddb.leaseManager.Stop()
ddb.mu.Lock()
if ddb.db != nil {
ddb.db.Close()
}
ddb.mu.Unlock()
}
3. Observabilidade
package vault
import (
"context"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
vaultRequests = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "vault_requests_total",
Help: "Total de requisições ao Vault",
},
[]string{"operation", "status"},
)
vaultRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "vault_request_duration_seconds",
Help: "Duração das requisições ao Vault",
Buckets: prometheus.DefBuckets,
},
[]string{"operation"},
)
vaultLeases = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "vault_managed_leases",
Help: "Número de leases gerenciados",
},
)
vaultAuthRenewal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "vault_auth_renewal_total",
Help: "Total de renovações de autenticação",
},
[]string{"result"},
)
)
// instrumented wrapper para métricas
func instrumentOperation(operation string, fn func() error) error {
start := time.Now()
err := fn()
duration := time.Since(start).Seconds()
status := "success"
if err != nil {
status = "error"
}
vaultRequests.WithLabelValues(operation, status).Inc()
vaultRequestDuration.WithLabelValues(operation).Observe(duration)
return err
}
4. Error Handling
package vault
import (
"errors"
"fmt"
)
var (
ErrSecretNotFound = errors.New("secret não encontrado")
ErrAuthFailed = errors.New("autenticação falhou")
ErrLeaseExpired = errors.New("lease expirado")
ErrPermissionDenied = errors.New("permissão negada")
)
// VaultError encapsula erros do Vault
type VaultError struct {
Op string
Path string
Err error
Wrapped error
}
func (ve *VaultError) Error() string {
return fmt.Sprintf("vault %s %s: %v", ve.Op, ve.Path, ve.Err)
}
func (ve *VaultError) Unwrap() error {
return ve.Wrapped
}
func isPermissionError(err error) bool {
if err == nil {
return false
}
// Verifica mensagens comuns de permissão
msg := err.Error()
return contains(msg, "permission denied") ||
contains(msg, "forbidden") ||
contains(msg, "403")
}
func contains(s, substr string) bool {
return len(s) >= len(substr) &&
(s == substr ||
len(s) > len(substr) &&
(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
hasSubstr(s, substr)))
}
func hasSubstr(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
Conclusão
Neste guia completo, você aprendeu:
✅ Fundamentos: Arquitetura e princípios do Vault ✅ Autenticação: Token, AppRole, Kubernetes, AWS ✅ Secrets: KV estático, leitura/escrita, versionamento ✅ Dinâmicos: Credenciais de banco de dados e cloud automáticas ✅ Rotação: Renovação automática de leases e tokens ✅ Produção: Pools dinâmicos, observabilidade, tratamento de erros
Próximos Passos
- Go e etcd - Distributed key-value store
- Go e Consul - Service mesh e discovery
- Go Security - Boas práticas de segurança
Recursos Adicionais
FAQ
Q: Por que usar Vault ao invés de variáveis de ambiente? R: Vault fornece rotação automática, audit logging, controle de acesso granular e secrets dinâmicos - impossível com env vars.
Q: Vault é production-ready? R: Sim. Usado em produção por empresas como HashiCorp, GitHub, Barclays, e muitas outras Fortune 500.
Q: Qual método de autenticação usar em Kubernetes? R: Kubernetes Auth é o ideal - usa service accounts nativamente, sem secrets para gerenciar.
Q: Como lidar com Vault indisponível? R: Implemente cache local, circuit breakers e fallback para credenciais temporárias. Sempre priorize resiliência.
Q: Secrets dinâmicos têm overhead? R: Mínimo (milisegundos). Benefícios de segurança superam amplamente o custo de criação.