|
|
|
@ -23,6 +23,7 @@ import ( |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
vault "github.com/hashicorp/vault/api" |
|
|
|
|
"github.com/minio/minio/cmd/logger" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
@ -63,6 +64,7 @@ type vaultService struct { |
|
|
|
|
config *VaultConfig |
|
|
|
|
client *vault.Client |
|
|
|
|
leaseDuration time.Duration |
|
|
|
|
tokenRenewer *vault.Renewer |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ KMS = (*vaultService)(nil) // compiler check that *vaultService implements KMS
|
|
|
|
@ -120,42 +122,80 @@ func NewVault(config VaultConfig) (KMS, error) { |
|
|
|
|
if config.Namespace != "" { |
|
|
|
|
client.SetNamespace(config.Namespace) |
|
|
|
|
} |
|
|
|
|
v := &vaultService{client: client, config: &config} |
|
|
|
|
|
|
|
|
|
payload := map[string]interface{}{ |
|
|
|
|
"role_id": config.Auth.AppRole.ID, |
|
|
|
|
"secret_id": config.Auth.AppRole.Secret, |
|
|
|
|
} |
|
|
|
|
resp, err := client.Logical().Write("auth/approle/login", payload) |
|
|
|
|
if err != nil { |
|
|
|
|
if err := v.authenticate(); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
if resp.Auth == nil { |
|
|
|
|
return nil, ErrKMSAuthLogin |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
client.SetToken(resp.Auth.ClientToken) |
|
|
|
|
v := &vaultService{client: client, config: &config, leaseDuration: time.Duration(resp.Auth.LeaseDuration)} |
|
|
|
|
v.renewToken() |
|
|
|
|
return v, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// renewToken starts a new go-routine which renews
|
|
|
|
|
// the vault authentication token periodically.
|
|
|
|
|
func (v *vaultService) renewToken() { |
|
|
|
|
// reauthenticate() tries to login in 1 minute
|
|
|
|
|
// intervals until successful.
|
|
|
|
|
func (v *vaultService) reauthenticate() { |
|
|
|
|
retryDelay := 1 * time.Minute |
|
|
|
|
go func() { |
|
|
|
|
for { |
|
|
|
|
s, err := v.client.Auth().Token().RenewSelf(int(v.leaseDuration)) |
|
|
|
|
if err != nil { |
|
|
|
|
if err := v.authenticate(); err != nil { |
|
|
|
|
time.Sleep(retryDelay) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
nextRenew := s.Auth.LeaseDuration / 2 |
|
|
|
|
time.Sleep(time.Duration(nextRenew) * time.Second) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// renewer calls vault client's renewer that automatically
|
|
|
|
|
// renews secret periodically
|
|
|
|
|
func (v *vaultService) renewer(secret *vault.Secret) { |
|
|
|
|
renewer, err := v.client.NewRenewer(&vault.RenewerInput{ |
|
|
|
|
Secret: secret, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.FatalIf(err, "crypto: hashicorp vault token renewer could not be started") |
|
|
|
|
} |
|
|
|
|
v.tokenRenewer = renewer |
|
|
|
|
go renewer.Renew() |
|
|
|
|
defer renewer.Stop() |
|
|
|
|
|
|
|
|
|
for { |
|
|
|
|
select { |
|
|
|
|
case err := <-renewer.DoneCh(): |
|
|
|
|
if err != nil { |
|
|
|
|
v.reauthenticate() |
|
|
|
|
renewer.Stop() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Renewal is now over
|
|
|
|
|
case renewal := <-renewer.RenewCh(): |
|
|
|
|
v.leaseDuration = time.Duration(renewal.Secret.Auth.LeaseDuration) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// authenticate logs the app to vault, and starts the auto renewer
|
|
|
|
|
// before secret expires
|
|
|
|
|
func (v *vaultService) authenticate() (err error) { |
|
|
|
|
payload := map[string]interface{}{ |
|
|
|
|
"role_id": v.config.Auth.AppRole.ID, |
|
|
|
|
"secret_id": v.config.Auth.AppRole.Secret, |
|
|
|
|
} |
|
|
|
|
var secret *vault.Secret |
|
|
|
|
secret, err = v.client.Logical().Write("auth/approle/login", payload) |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if secret.Auth == nil { |
|
|
|
|
err = ErrKMSAuthLogin |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
v.client.SetToken(secret.Auth.ClientToken) |
|
|
|
|
v.leaseDuration = time.Duration(secret.Auth.LeaseDuration) |
|
|
|
|
go v.renewer(secret) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GenerateKey returns a new plaintext key, generated by the KMS,
|
|
|
|
|
// and a sealed version of this plaintext key encrypted using the
|
|
|
|
|
// named key referenced by keyID. It also binds the generated key
|
|
|
|
|