@ -16,7 +16,6 @@ package crypto
import (
import (
"bytes"
"bytes"
"context"
"encoding/base64"
"encoding/base64"
"errors"
"errors"
"fmt"
"fmt"
@ -24,7 +23,6 @@ import (
"time"
"time"
vault "github.com/hashicorp/vault/api"
vault "github.com/hashicorp/vault/api"
"github.com/minio/minio/cmd/logger"
)
)
var (
var (
@ -64,6 +62,7 @@ type VaultConfig struct {
type vaultService struct {
type vaultService struct {
config * VaultConfig
config * VaultConfig
client * vault . Client
client * vault . Client
secret * vault . Secret
leaseDuration time . Duration
leaseDuration time . Duration
}
}
@ -123,86 +122,81 @@ func NewVault(config VaultConfig) (KMS, error) {
client . SetNamespace ( config . Namespace )
client . SetNamespace ( config . Namespace )
}
}
v := & vaultService { client : client , config : & config }
v := & vaultService { client : client , config : & config }
if err := v . authenticate ( ) ; err != nil {
if err := v . authenticate ( ) ; err != nil {
return nil , err
return nil , err
}
}
v . renewToken ( )
return v , nil
return v , nil
}
}
// renewSecret tries to renew the given secret. It blocks
// renewToken starts a new go-routine which renews
// until it receives either the new secret or encounters an error.
// the vault authentication token periodically and re-authenticates
func ( v * vaultService ) renewSecret ( secret * vault . Secret ) ( * vault . Secret , error ) {
// if the token renewal fails
renewer , err := v . client . NewRenewer ( & vault . RenewerInput {
func ( v * vaultService ) renewToken ( ) {
Secret : secret ,
retryDelay := v . leaseDuration / 2
} )
go func ( ) {
if err != nil {
logger . CriticalIf ( context . Background ( ) , fmt . Errorf ( "crypto: failed to create hashicorp vault renewer: %s" , err ) )
}
go renewer . Renew ( )
defer renewer . Stop ( )
for {
for {
select {
if v . secret == nil {
case err := <- renewer . DoneCh ( ) :
if err := v . authenticate ( ) ; err != nil {
if err != nil {
time . Sleep ( retryDelay )
return nil , err
continue
}
}
s , err := v . client . Auth ( ) . Token ( ) . RenewSelf ( int ( v . leaseDuration ) )
if err != nil || s == nil {
v . secret = nil
time . Sleep ( retryDelay )
continue
}
}
case renew := <- renewer . RenewCh ( ) :
if ok , err := s . TokenIsRenewable ( ) ; ! ok || err != nil {
if renew . Secret == nil || renew . Secret . Auth == nil {
v . secret = nil
return nil , ErrKMSAuthLogin
continue
}
}
return renew . Secret , nil
ttl , err := s . TokenTTL ( )
if err != nil {
v . secret = nil
continue
}
}
v . secret = s
retryDelay = ttl / 2
time . Sleep ( retryDelay )
}
}
} ( )
}
}
// login tries to authenticate the minio server to
// authenticate logs the app to vault, and starts the auto renewer
// the Vault KMS using the approle ID and secret.
// before secret expires
func ( v * vaultService ) login ( ) ( * vault . Secret , error ) {
func ( v * vaultService ) authenticate ( ) ( err 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 ,
}
}
secret , err := v . client . Logical ( ) . Write ( "auth/approle/login" , payload )
var tokenID string
var ttl time . Duration
var secret * vault . Secret
secret , err = v . client . Logical ( ) . Write ( "auth/approle/login" , payload )
if err != nil {
if err != nil {
return nil , err
return
}
}
if secret == nil || secret . Auth == nil {
if secret == nil {
return nil , ErrKMSAuthLogin
err = ErrKMSAuthLogin
return
}
}
return secret , nil
}
// authenticate tries to authenticate the minio server
tokenID , err = secret . TokenID ( )
// 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 {
if err != nil {
return err
err = ErrKMSAuthLogin
return
}
}
v . client . SetToken ( secret . Auth . ClientToken )
ttl , err = secret . TokenTTL ( )
v . leaseDuration = time . Duration ( secret . Auth . LeaseDuration )
// 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 {
if err != nil {
// Try to login again with app-ID and app-Secret
err = ErrKMSAuthLogin
if newSecret , err = v . login ( ) ; err != nil { // failed -> try again
return
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 )
v . client . SetToken ( tokenID )
return nil
v . secret = secret
v . leaseDuration = ttl
return
}
}
// GenerateKey returns a new plaintext key, generated by the KMS,
// GenerateKey returns a new plaintext key, generated by the KMS,