Automatically renew the token used by the Vault CA provider

This commit is contained in:
Kyle Havlovitz 2020-08-25 10:34:49 -07:00
parent 8d66456880
commit 6f7152841f
2 changed files with 76 additions and 2 deletions

View File

@ -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)
}

View File

@ -53,4 +53,5 @@ const (
Transaction string = "txn"
WAN string = "wan"
Watch string = "watch"
Vault string = "vault"
)