@ -16,6 +16,7 @@ package crypto
import (
import (
"bytes"
"bytes"
"context"
"encoding/base64"
"encoding/base64"
"errors"
"errors"
"fmt"
"fmt"
@ -64,7 +65,6 @@ type vaultService struct {
config * VaultConfig
config * VaultConfig
client * vault . Client
client * vault . Client
leaseDuration time . Duration
leaseDuration time . Duration
tokenRenewer * vault . Renewer
}
}
var _ KMS = ( * vaultService ) ( nil ) // compiler check that *vaultService implements KMS
var _ KMS = ( * vaultService ) ( nil ) // compiler check that *vaultService implements KMS
@ -130,31 +130,15 @@ func NewVault(config VaultConfig) (KMS, error) {
return v , nil
return v , nil
}
}
// reauthenticate() tries to login in 1 minute
// renewSecret tries to renew the given secret. It blocks
// intervals until successful.
// until it receives either the new secret or encounters an error.
func ( v * vaultService ) reauthenticate ( ) {
func ( v * vaultService ) renewSecret ( secret * vault . Secret ) ( * vault . Secret , error ) {
retryDelay := 1 * time . Minute
go func ( ) {
for {
if err := v . authenticate ( ) ; err != nil {
time . Sleep ( retryDelay )
continue
}
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 {
renewer , err := v . client . NewRenewer ( & vault . RenewerInput {
Secret : secret ,
Secret : secret ,
} )
} )
if err != nil {
if err != nil {
logger . FatalIf ( err , "crypto: hashicorp vault token renewer could not be started" )
logger . CriticalIf ( context . Background ( ) , fmt . Errorf ( "crypto: failed to create hashicorp vault renewer: %s" , err ) )
}
}
v . tokenRenewer = renewer
go renewer . Renew ( )
go renewer . Renew ( )
defer renewer . Stop ( )
defer renewer . Stop ( )
@ -162,38 +146,63 @@ func (v *vaultService) renewer(secret *vault.Secret) {
select {
select {
case err := <- renewer . DoneCh ( ) :
case err := <- renewer . DoneCh ( ) :
if err != nil {
if err != nil {
v . reauthenticate ( )
return nil , err
renewer . Stop ( )
return
}
}
case renew := <- renewer . RenewCh ( ) :
// Renewal is now over
if renew . Secret == nil || renew . Secret . Auth == nil {
case renewal := <- renewer . RenewCh ( ) :
return nil , ErrKMSAuthLogin
v . leaseDuration = time . Duration ( renewal . Secret . Auth . LeaseDuration )
}
return renew . Secret , nil
}
}
}
}
}
}
// authenticate logs the app to vault, and starts the auto renewer
// login tries to authenticate the minio server to
// before secret expires
// the Vault KMS using the approle ID and secret.
func ( v * vaultService ) authenticate ( ) ( err error ) {
func ( v * vaultService ) login ( ) ( * vault . Secret , error ) {
payload := map [ string ] interface { } {
payload := map [ string ] interface { } {
"role_id" : v . config . Auth . AppRole . ID ,
"role_id" : v . config . Auth . AppRole . ID ,
"secret_id" : v . config . Auth . AppRole . Secret ,
"secret_id" : v . config . Auth . AppRole . Secret ,
}
}
var secret * vault . Secret
secret , err := v . client . Logical ( ) . Write ( "auth/approle/login" , payload )
secret , err = v . client . Logical ( ) . Write ( "auth/approle/login" , payload )
if err != nil {
if err != nil {
return
return nil , err
}
if secret == nil || secret . Auth == nil {
return nil , ErrKMSAuthLogin
}
}
if secret . Auth == nil {
return secret , nil
err = ErrKMSAuthLogin
}
return
// authenticate tries to authenticate the minio server
// to the Vault KMS and starts a background job to renew
// the login.
func ( v * vaultService ) authenticate ( ) error {
secret , err := v . login ( )
if err != nil {
return err
}
}
v . client . SetToken ( secret . Auth . ClientToken )
v . client . SetToken ( secret . Auth . ClientToken )
v . leaseDuration = time . Duration ( secret . Auth . LeaseDuration )
v . leaseDuration = time . Duration ( secret . Auth . LeaseDuration )
go v . renewer ( secret )
return
// Start background job trying to renew the token
// or (if this fails) try to login again with app-ID and app-Secret.
go func ( secret * vault . Secret ) {
for {
newSecret , err := v . renewSecret ( secret ) // try to renew the secret (blocking)
if err != nil {
// Try to login again with app-ID and app-Secret
if newSecret , err = v . login ( ) ; err != nil { // failed -> try again
time . Sleep ( 1 * time . Minute ) // retry delay
continue
}
}
secret = newSecret // Now newSecret contains a valid, non-nil *vault.Secret
v . client . SetToken ( secret . Auth . ClientToken )
v . leaseDuration = time . Duration ( secret . Auth . LeaseDuration )
}
} ( secret )
return nil
}
}
// GenerateKey returns a new plaintext key, generated by the KMS,
// GenerateKey returns a new plaintext key, generated by the KMS,