diff --git a/agent/connect/ca/provider_vault.go b/agent/connect/ca/provider_vault.go index 8dbac7e22b..41d36ac8b2 100644 --- a/agent/connect/ca/provider_vault.go +++ b/agent/connect/ca/provider_vault.go @@ -3,6 +3,7 @@ package ca import ( "bytes" "crypto/x509" + "encoding/json" "encoding/pem" "fmt" "io/ioutil" @@ -11,6 +12,8 @@ import ( "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/logging" + "github.com/hashicorp/go-hclog" vaultapi "github.com/hashicorp/vault/api" "github.com/mitchellh/mapstructure" ) @@ -21,12 +24,15 @@ var ErrBackendNotMounted = fmt.Errorf("backend not mounted") var ErrBackendNotInitialized = fmt.Errorf("backend not initialized") type VaultProvider struct { - config *structs.VaultCAProviderConfig - client *vaultapi.Client + config *structs.VaultCAProviderConfig + client *vaultapi.Client + doneCh chan struct{} + isPrimary bool clusterID string spiffeID *connect.SpiffeIDSigning setupIntermediatePKIPathDone bool + logger hclog.Logger } func vaultTLSConfig(config *structs.VaultCAProviderConfig) *vaultapi.TLSConfig { @@ -65,10 +71,73 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error { v.isPrimary = cfg.IsPrimary v.clusterID = cfg.ClusterID v.spiffeID = connect.SpiffeIDSigningForCluster(&structs.CAConfiguration{ClusterID: v.clusterID}) + v.doneCh = make(chan struct{}, 0) + + // Look up the token to see if we can auto-renew its lease. + secret, err := client.Auth().Token().Lookup(config.Token) + if err != nil { + return err + } + var renewable bool + if v, ok := secret.Data["renewable"]; ok { + renewable, _ = v.(bool) + } + var increment int64 + if v, ok := secret.Data["ttl"]; ok { + if n, ok := v.(json.Number); ok { + increment, _ = n.Int64() + } + } + + // Set up a renewer to renew the token automatically, if supported. + if renewable { + renewer, err := client.NewRenewer(&vaultapi.RenewerInput{ + Secret: &vaultapi.Secret{ + Auth: &vaultapi.SecretAuth{ + ClientToken: config.Token, + Renewable: renewable, + }, + }, + Increment: int(increment), + }) + if err != nil { + return fmt.Errorf("Error beginning Vault provider token renewal: %v", err) + } + go v.renewToken(renewer) + } return nil } +// renewToken uses a vaultapi.Renewer to repeatedly renew our token's lease. +func (v *VaultProvider) renewToken(renewer *vaultapi.Renewer) { + go renewer.Renew() + + for { + select { + case <-v.doneCh: + renewer.Stop() + return + + case err := <-renewer.DoneCh(): + if err != nil { + v.logger.Error(fmt.Sprint("Error renewing token for Vault provider: %v", err)) + } + + case <-renewer.RenewCh(): + v.logger.Error("Successfully renewed token for Vault provider") + } + } +} + +// SetLogger implements the NeedsLogger interface so the provider can log important messages. +func (v *VaultProvider) SetLogger(logger hclog.Logger) { + v.logger = logger. + ResetNamed(logging.Connect). + Named(logging.CA). + Named(logging.Vault) +} + // State implements Provider. Vault provider needs no state other than the // user-provided config currently. func (v *VaultProvider) State() (map[string]string, error) { @@ -431,6 +500,10 @@ func (c *VaultProvider) SupportsCrossSigning() (bool, error) { // this down and recreate it on small config changes because the intermediate // certs get bundled with the leaf certs, so there's no cost to the CA changing. func (v *VaultProvider) Cleanup() error { + if v.doneCh != nil { + close(v.doneCh) + } + return v.client.Sys().Unmount(v.config.IntermediatePKIPath) } diff --git a/logging/names.go b/logging/names.go index 6ade11bf69..fcbdc23534 100644 --- a/logging/names.go +++ b/logging/names.go @@ -53,4 +53,5 @@ const ( Transaction string = "txn" WAN string = "wan" Watch string = "watch" + Vault string = "vault" )