d0e2badbae
The result will still pass gofmtcheck and won't trigger additional changes if someone isn't using goimports, but it will avoid the piecemeal imports changes we've been seeing.
383 lines
11 KiB
Go
383 lines
11 KiB
Go
package vault
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
mathrand "math/rand"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/helper/jsonutil"
|
|
)
|
|
|
|
const (
|
|
// Storage path where the local cluster name and identifier are stored
|
|
coreLocalClusterInfoPath = "core/cluster/local/info"
|
|
|
|
corePrivateKeyTypeP521 = "p521"
|
|
corePrivateKeyTypeED25519 = "ed25519"
|
|
|
|
// Internal so as not to log a trace message
|
|
IntNoForwardingHeaderName = "X-Vault-Internal-No-Request-Forwarding"
|
|
)
|
|
|
|
var (
|
|
ErrCannotForward = errors.New("cannot forward request; no connection or address not known")
|
|
)
|
|
|
|
type ReplicatedClusters struct {
|
|
DR *ReplicatedCluster
|
|
Performance *ReplicatedCluster
|
|
}
|
|
|
|
// This can be one of a few key types so the different params may or may not be filled
|
|
type clusterKeyParams struct {
|
|
Type string `json:"type" structs:"type" mapstructure:"type"`
|
|
X *big.Int `json:"x" structs:"x" mapstructure:"x"`
|
|
Y *big.Int `json:"y" structs:"y" mapstructure:"y"`
|
|
D *big.Int `json:"d" structs:"d" mapstructure:"d"`
|
|
}
|
|
|
|
// Structure representing the storage entry that holds cluster information
|
|
type Cluster struct {
|
|
// Name of the cluster
|
|
Name string `json:"name" structs:"name" mapstructure:"name"`
|
|
|
|
// Identifier of the cluster
|
|
ID string `json:"id" structs:"id" mapstructure:"id"`
|
|
}
|
|
|
|
// Cluster fetches the details of the local cluster. This method errors out
|
|
// when Vault is sealed.
|
|
func (c *Core) Cluster(ctx context.Context) (*Cluster, error) {
|
|
var cluster Cluster
|
|
|
|
// Fetch the storage entry. This call fails when Vault is sealed.
|
|
entry, err := c.barrier.Get(ctx, coreLocalClusterInfoPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if entry == nil {
|
|
return &cluster, nil
|
|
}
|
|
|
|
// Decode the cluster information
|
|
if err = jsonutil.DecodeJSON(entry.Value, &cluster); err != nil {
|
|
return nil, errwrap.Wrapf("failed to decode cluster details: {{err}}", err)
|
|
}
|
|
|
|
// Set in config file
|
|
if c.clusterName != "" {
|
|
cluster.Name = c.clusterName
|
|
}
|
|
|
|
return &cluster, nil
|
|
}
|
|
|
|
// This sets our local cluster cert and private key based on the advertisement.
|
|
// It also ensures the cert is in our local cluster cert pool.
|
|
func (c *Core) loadLocalClusterTLS(adv activeAdvertisement) (retErr error) {
|
|
defer func() {
|
|
if retErr != nil {
|
|
c.localClusterCert.Store(([]byte)(nil))
|
|
c.localClusterParsedCert.Store((*x509.Certificate)(nil))
|
|
c.localClusterPrivateKey.Store((*ecdsa.PrivateKey)(nil))
|
|
|
|
c.requestForwardingConnectionLock.Lock()
|
|
c.clearForwardingClients()
|
|
c.requestForwardingConnectionLock.Unlock()
|
|
}
|
|
}()
|
|
|
|
switch {
|
|
case adv.ClusterAddr == "":
|
|
// Clustering disabled on the server, don't try to look for params
|
|
return nil
|
|
|
|
case adv.ClusterKeyParams == nil:
|
|
c.logger.Error("no key params found loading local cluster TLS information")
|
|
return fmt.Errorf("no local cluster key params found")
|
|
|
|
case adv.ClusterKeyParams.X == nil, adv.ClusterKeyParams.Y == nil, adv.ClusterKeyParams.D == nil:
|
|
c.logger.Error("failed to parse local cluster key due to missing params")
|
|
return fmt.Errorf("failed to parse local cluster key")
|
|
|
|
case adv.ClusterKeyParams.Type != corePrivateKeyTypeP521:
|
|
c.logger.Error("unknown local cluster key type", "key_type", adv.ClusterKeyParams.Type)
|
|
return fmt.Errorf("failed to find valid local cluster key type")
|
|
|
|
case adv.ClusterCert == nil || len(adv.ClusterCert) == 0:
|
|
c.logger.Error("no local cluster cert found")
|
|
return fmt.Errorf("no local cluster cert found")
|
|
|
|
}
|
|
|
|
c.localClusterPrivateKey.Store(&ecdsa.PrivateKey{
|
|
PublicKey: ecdsa.PublicKey{
|
|
Curve: elliptic.P521(),
|
|
X: adv.ClusterKeyParams.X,
|
|
Y: adv.ClusterKeyParams.Y,
|
|
},
|
|
D: adv.ClusterKeyParams.D,
|
|
})
|
|
|
|
locCert := make([]byte, len(adv.ClusterCert))
|
|
copy(locCert, adv.ClusterCert)
|
|
c.localClusterCert.Store(locCert)
|
|
|
|
cert, err := x509.ParseCertificate(adv.ClusterCert)
|
|
if err != nil {
|
|
c.logger.Error("failed parsing local cluster certificate", "error", err)
|
|
return errwrap.Wrapf("error parsing local cluster certificate: {{err}}", err)
|
|
}
|
|
|
|
c.localClusterParsedCert.Store(cert)
|
|
|
|
return nil
|
|
}
|
|
|
|
// setupCluster creates storage entries for holding Vault cluster information.
|
|
// Entries will be created only if they are not already present. If clusterName
|
|
// is not supplied, this method will auto-generate it.
|
|
func (c *Core) setupCluster(ctx context.Context) error {
|
|
// Prevent data races with the TLS parameters
|
|
c.clusterParamsLock.Lock()
|
|
defer c.clusterParamsLock.Unlock()
|
|
|
|
// Check if storage index is already present or not
|
|
cluster, err := c.Cluster(ctx)
|
|
if err != nil {
|
|
c.logger.Error("failed to get cluster details", "error", err)
|
|
return err
|
|
}
|
|
|
|
var modified bool
|
|
|
|
if cluster == nil {
|
|
cluster = &Cluster{}
|
|
}
|
|
|
|
if cluster.Name == "" {
|
|
// If cluster name is not supplied, generate one
|
|
if c.clusterName == "" {
|
|
c.logger.Debug("cluster name not found/set, generating new")
|
|
clusterNameBytes, err := uuid.GenerateRandomBytes(4)
|
|
if err != nil {
|
|
c.logger.Error("failed to generate cluster name", "error", err)
|
|
return err
|
|
}
|
|
|
|
c.clusterName = fmt.Sprintf("vault-cluster-%08x", clusterNameBytes)
|
|
}
|
|
|
|
cluster.Name = c.clusterName
|
|
if c.logger.IsDebug() {
|
|
c.logger.Debug("cluster name set", "name", cluster.Name)
|
|
}
|
|
modified = true
|
|
}
|
|
|
|
if cluster.ID == "" {
|
|
c.logger.Debug("cluster ID not found, generating new")
|
|
// Generate a clusterID
|
|
cluster.ID, err = uuid.GenerateUUID()
|
|
if err != nil {
|
|
c.logger.Error("failed to generate cluster identifier", "error", err)
|
|
return err
|
|
}
|
|
if c.logger.IsDebug() {
|
|
c.logger.Debug("cluster ID set", "id", cluster.ID)
|
|
}
|
|
modified = true
|
|
}
|
|
|
|
// If we're using HA, generate server-to-server parameters
|
|
if c.ha != nil {
|
|
// Create a private key
|
|
if c.localClusterPrivateKey.Load().(*ecdsa.PrivateKey) == nil {
|
|
c.logger.Debug("generating cluster private key")
|
|
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
|
if err != nil {
|
|
c.logger.Error("failed to generate local cluster key", "error", err)
|
|
return err
|
|
}
|
|
|
|
c.localClusterPrivateKey.Store(key)
|
|
}
|
|
|
|
// Create a certificate
|
|
if c.localClusterCert.Load().([]byte) == nil {
|
|
c.logger.Debug("generating local cluster certificate")
|
|
|
|
host, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
host = fmt.Sprintf("fw-%s", host)
|
|
template := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
DNSNames: []string{host},
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageServerAuth,
|
|
x509.ExtKeyUsageClientAuth,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign,
|
|
SerialNumber: big.NewInt(mathrand.Int63()),
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
|
// 30 years of single-active uptime ought to be enough for anybody
|
|
NotAfter: time.Now().Add(262980 * time.Hour),
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, c.localClusterPrivateKey.Load().(*ecdsa.PrivateKey).Public(), c.localClusterPrivateKey.Load().(*ecdsa.PrivateKey))
|
|
if err != nil {
|
|
c.logger.Error("error generating self-signed cert", "error", err)
|
|
return errwrap.Wrapf("unable to generate local cluster certificate: {{err}}", err)
|
|
}
|
|
|
|
parsedCert, err := x509.ParseCertificate(certBytes)
|
|
if err != nil {
|
|
c.logger.Error("error parsing self-signed cert", "error", err)
|
|
return errwrap.Wrapf("error parsing generated certificate: {{err}}", err)
|
|
}
|
|
|
|
c.localClusterCert.Store(certBytes)
|
|
c.localClusterParsedCert.Store(parsedCert)
|
|
}
|
|
}
|
|
|
|
if modified {
|
|
// Encode the cluster information into as a JSON string
|
|
rawCluster, err := json.Marshal(cluster)
|
|
if err != nil {
|
|
c.logger.Error("failed to encode cluster details", "error", err)
|
|
return err
|
|
}
|
|
|
|
// Store it
|
|
err = c.barrier.Put(ctx, &Entry{
|
|
Key: coreLocalClusterInfoPath,
|
|
Value: rawCluster,
|
|
})
|
|
if err != nil {
|
|
c.logger.Error("failed to store cluster details", "error", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// startClusterListener starts cluster request listeners during postunseal. It
|
|
// is assumed that the state lock is held while this is run. Right now this
|
|
// only starts forwarding listeners; it's TBD whether other request types will
|
|
// be built in the same mechanism or started independently.
|
|
func (c *Core) startClusterListener(ctx context.Context) error {
|
|
if c.clusterAddr == "" {
|
|
c.logger.Info("clustering disabled, not starting listeners")
|
|
return nil
|
|
}
|
|
|
|
if c.clusterListenerAddrs == nil || len(c.clusterListenerAddrs) == 0 {
|
|
c.logger.Warn("clustering not disabled but no addresses to listen on")
|
|
return fmt.Errorf("cluster addresses not found")
|
|
}
|
|
|
|
c.logger.Debug("starting cluster listeners")
|
|
|
|
err := c.startForwarding(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// stopClusterListener stops any existing listeners during preseal. It is
|
|
// assumed that the state lock is held while this is run.
|
|
func (c *Core) stopClusterListener() {
|
|
if c.clusterAddr == "" {
|
|
|
|
c.logger.Debug("clustering disabled, not stopping listeners")
|
|
return
|
|
}
|
|
|
|
if !c.clusterListenersRunning {
|
|
c.logger.Info("cluster listeners not running")
|
|
return
|
|
}
|
|
c.logger.Info("stopping cluster listeners")
|
|
|
|
// Tell the goroutine managing the listeners to perform the shutdown
|
|
// process
|
|
c.clusterListenerShutdownCh <- struct{}{}
|
|
|
|
// The reason for this loop-de-loop is that we may be unsealing again
|
|
// quickly, and if the listeners are not yet closed, we will get socket
|
|
// bind errors. This ensures proper ordering.
|
|
|
|
c.logger.Debug("waiting for success notification while stopping cluster listeners")
|
|
<-c.clusterListenerShutdownSuccessCh
|
|
c.clusterListenersRunning = false
|
|
|
|
c.logger.Info("cluster listeners successfully shut down")
|
|
}
|
|
|
|
// ClusterTLSConfig generates a TLS configuration based on the local/replicated
|
|
// cluster key and cert.
|
|
func (c *Core) ClusterTLSConfig(ctx context.Context, repClusters *ReplicatedClusters, perfStandbyCluster *ReplicatedCluster) (*tls.Config, error) {
|
|
// Using lookup functions allows just-in-time lookup of the current state
|
|
// of clustering as connections come and go
|
|
|
|
tlsConfig := &tls.Config{
|
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
|
GetCertificate: clusterTLSServerLookup(ctx, c, repClusters, perfStandbyCluster),
|
|
GetClientCertificate: clusterTLSClientLookup(ctx, c, repClusters, perfStandbyCluster),
|
|
GetConfigForClient: clusterTLSServerConfigLookup(ctx, c, repClusters, perfStandbyCluster),
|
|
MinVersion: tls.VersionTLS12,
|
|
CipherSuites: c.clusterCipherSuites,
|
|
}
|
|
|
|
parsedCert := c.localClusterParsedCert.Load().(*x509.Certificate)
|
|
currCert := c.localClusterCert.Load().([]byte)
|
|
localCert := make([]byte, len(currCert))
|
|
copy(localCert, currCert)
|
|
|
|
if parsedCert != nil {
|
|
tlsConfig.ServerName = parsedCert.Subject.CommonName
|
|
|
|
pool := x509.NewCertPool()
|
|
pool.AddCert(parsedCert)
|
|
tlsConfig.RootCAs = pool
|
|
tlsConfig.ClientCAs = pool
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
func (c *Core) SetClusterListenerAddrs(addrs []*net.TCPAddr) {
|
|
c.clusterListenerAddrs = addrs
|
|
if c.clusterAddr == "" && len(addrs) == 1 {
|
|
c.clusterAddr = fmt.Sprintf("https://%s", addrs[0].String())
|
|
}
|
|
}
|
|
|
|
func (c *Core) SetClusterHandler(handler http.Handler) {
|
|
c.clusterHandler = handler
|
|
}
|