diff --git a/cmd/crypto/vault.go b/cmd/crypto/vault.go index 43b705bc9..3967289cf 100644 --- a/cmd/crypto/vault.go +++ b/cmd/crypto/vault.go @@ -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