mirror of https://github.com/status-im/consul.git
281 lines
7.8 KiB
Go
281 lines
7.8 KiB
Go
|
package autoconf
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
|
||
|
"github.com/hashicorp/consul/agent/cache"
|
||
|
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
||
|
"github.com/hashicorp/consul/agent/connect"
|
||
|
"github.com/hashicorp/consul/agent/structs"
|
||
|
"github.com/hashicorp/consul/proto/pbautoconf"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// ID of the roots watch
|
||
|
rootsWatchID = "roots"
|
||
|
|
||
|
// ID of the leaf watch
|
||
|
leafWatchID = "leaf"
|
||
|
|
||
|
unknownTrustDomain = "unknown"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
defaultDNSSANs = []string{"localhost"}
|
||
|
|
||
|
defaultIPSANs = []net.IP{{127, 0, 0, 1}, net.ParseIP("::1")}
|
||
|
)
|
||
|
|
||
|
func extractPEMs(roots *structs.IndexedCARoots) []string {
|
||
|
var pems []string
|
||
|
for _, root := range roots.Roots {
|
||
|
pems = append(pems, root.RootCert)
|
||
|
}
|
||
|
return pems
|
||
|
}
|
||
|
|
||
|
// updateTLSFromResponse will update the TLS certificate and roots in the shared
|
||
|
// TLS configurator.
|
||
|
func (ac *AutoConfig) updateTLSFromResponse(resp *pbautoconf.AutoConfigResponse) error {
|
||
|
var pems []string
|
||
|
for _, root := range resp.GetCARoots().GetRoots() {
|
||
|
pems = append(pems, root.RootCert)
|
||
|
}
|
||
|
|
||
|
err := ac.acConfig.TLSConfigurator.UpdateAutoTLS(
|
||
|
resp.ExtraCACertificates,
|
||
|
pems,
|
||
|
resp.Certificate.GetCertPEM(),
|
||
|
resp.Certificate.GetPrivateKeyPEM(),
|
||
|
resp.Config.GetTLS().GetVerifyServerHostname(),
|
||
|
)
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Failed to update the TLS configurator with new certificates: %w", err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) setInitialTLSCertificates(certs *structs.SignedResponse) error {
|
||
|
if certs == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if err := ac.populateCertificateCache(certs); err != nil {
|
||
|
return fmt.Errorf("error populating cache with certificates: %w", err)
|
||
|
}
|
||
|
|
||
|
connectCAPems := extractPEMs(&certs.ConnectCARoots)
|
||
|
|
||
|
err := ac.acConfig.TLSConfigurator.UpdateAutoTLS(
|
||
|
certs.ManualCARoots,
|
||
|
connectCAPems,
|
||
|
certs.IssuedCert.CertPEM,
|
||
|
certs.IssuedCert.PrivateKeyPEM,
|
||
|
certs.VerifyServerHostname,
|
||
|
)
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("error updating TLS configurator with certificates: %w", err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) populateCertificateCache(certs *structs.SignedResponse) error {
|
||
|
cert, err := connect.ParseCert(certs.IssuedCert.CertPEM)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Failed to parse certificate: %w", err)
|
||
|
}
|
||
|
|
||
|
// prepolutate roots cache
|
||
|
rootRes := cache.FetchResult{Value: &certs.ConnectCARoots, Index: certs.ConnectCARoots.QueryMeta.Index}
|
||
|
rootsReq := ac.caRootsRequest()
|
||
|
// getting the roots doesn't require a token so in order to potentially share the cache with another
|
||
|
if err := ac.acConfig.Cache.Prepopulate(cachetype.ConnectCARootName, rootRes, ac.config.Datacenter, "", rootsReq.CacheInfo().Key); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
leafReq := ac.leafCertRequest()
|
||
|
|
||
|
// prepolutate leaf cache
|
||
|
certRes := cache.FetchResult{
|
||
|
Value: &certs.IssuedCert,
|
||
|
Index: certs.IssuedCert.RaftIndex.ModifyIndex,
|
||
|
State: cachetype.ConnectCALeafSuccess(connect.EncodeSigningKeyID(cert.AuthorityKeyId)),
|
||
|
}
|
||
|
if err := ac.acConfig.Cache.Prepopulate(cachetype.ConnectCALeafName, certRes, leafReq.Datacenter, leafReq.Token, leafReq.Key()); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) setupCertificateCacheWatches(ctx context.Context) (context.CancelFunc, error) {
|
||
|
notificationCtx, cancel := context.WithCancel(ctx)
|
||
|
|
||
|
rootsReq := ac.caRootsRequest()
|
||
|
err := ac.acConfig.Cache.Notify(notificationCtx, cachetype.ConnectCARootName, &rootsReq, rootsWatchID, ac.cacheUpdates)
|
||
|
if err != nil {
|
||
|
cancel()
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
leafReq := ac.leafCertRequest()
|
||
|
err = ac.acConfig.Cache.Notify(notificationCtx, cachetype.ConnectCALeafName, &leafReq, leafWatchID, ac.cacheUpdates)
|
||
|
if err != nil {
|
||
|
cancel()
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return cancel, nil
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) updateCARoots(roots *structs.IndexedCARoots) error {
|
||
|
switch {
|
||
|
case ac.config.AutoConfig.Enabled:
|
||
|
ac.Lock()
|
||
|
defer ac.Unlock()
|
||
|
var err error
|
||
|
ac.autoConfigResponse.CARoots, err = translateCARootsToProtobuf(roots)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := ac.updateTLSFromResponse(ac.autoConfigResponse); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return ac.persistAutoConfig(ac.autoConfigResponse)
|
||
|
case ac.config.AutoEncryptTLS:
|
||
|
pems := extractPEMs(roots)
|
||
|
|
||
|
if err := ac.acConfig.TLSConfigurator.UpdateAutoTLSCA(pems); err != nil {
|
||
|
return fmt.Errorf("failed to update Connect CA certificates: %w", err)
|
||
|
}
|
||
|
return nil
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) updateLeafCert(cert *structs.IssuedCert) error {
|
||
|
switch {
|
||
|
case ac.config.AutoConfig.Enabled:
|
||
|
ac.Lock()
|
||
|
defer ac.Unlock()
|
||
|
var err error
|
||
|
ac.autoConfigResponse.Certificate, err = translateIssuedCertToProtobuf(cert)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := ac.updateTLSFromResponse(ac.autoConfigResponse); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return ac.persistAutoConfig(ac.autoConfigResponse)
|
||
|
case ac.config.AutoEncryptTLS:
|
||
|
if err := ac.acConfig.TLSConfigurator.UpdateAutoTLSCert(cert.CertPEM, cert.PrivateKeyPEM); err != nil {
|
||
|
return fmt.Errorf("failed to update the agent leaf cert: %w", err)
|
||
|
}
|
||
|
return nil
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) caRootsRequest() structs.DCSpecificRequest {
|
||
|
return structs.DCSpecificRequest{Datacenter: ac.config.Datacenter}
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) leafCertRequest() cachetype.ConnectCALeafRequest {
|
||
|
return cachetype.ConnectCALeafRequest{
|
||
|
Datacenter: ac.config.Datacenter,
|
||
|
Agent: ac.config.NodeName,
|
||
|
DNSSAN: ac.getDNSSANs(),
|
||
|
IPSAN: ac.getIPSANs(),
|
||
|
Token: ac.acConfig.Tokens.AgentToken(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// generateCSR will generate a CSR for an Agent certificate. This should
|
||
|
// be sent along with the AutoConfig.InitialConfiguration RPC or the
|
||
|
// AutoEncrypt.Sign RPC. The generated CSR does NOT have a real trust domain
|
||
|
// as when generating this we do not yet have the CA roots. The server will
|
||
|
// update the trust domain for us though.
|
||
|
func (ac *AutoConfig) generateCSR() (csr string, key string, err error) {
|
||
|
// We don't provide the correct host here, because we don't know any
|
||
|
// better at this point. Apart from the domain, we would need the
|
||
|
// ClusterID, which we don't have. This is why we go with
|
||
|
// unknownTrustDomain the first time. Subsequent CSRs will have the
|
||
|
// correct TrustDomain.
|
||
|
id := &connect.SpiffeIDAgent{
|
||
|
// will be replaced
|
||
|
Host: unknownTrustDomain,
|
||
|
Datacenter: ac.config.Datacenter,
|
||
|
Agent: ac.config.NodeName,
|
||
|
}
|
||
|
|
||
|
caConfig, err := ac.config.ConnectCAConfiguration()
|
||
|
if err != nil {
|
||
|
return "", "", fmt.Errorf("Cannot generate CSR: %w", err)
|
||
|
}
|
||
|
|
||
|
conf, err := caConfig.GetCommonConfig()
|
||
|
if err != nil {
|
||
|
return "", "", fmt.Errorf("Failed to load common CA configuration: %w", err)
|
||
|
}
|
||
|
|
||
|
if conf.PrivateKeyType == "" {
|
||
|
conf.PrivateKeyType = connect.DefaultPrivateKeyType
|
||
|
}
|
||
|
if conf.PrivateKeyBits == 0 {
|
||
|
conf.PrivateKeyBits = connect.DefaultPrivateKeyBits
|
||
|
}
|
||
|
|
||
|
// Create a new private key
|
||
|
pk, pkPEM, err := connect.GeneratePrivateKeyWithConfig(conf.PrivateKeyType, conf.PrivateKeyBits)
|
||
|
if err != nil {
|
||
|
return "", "", fmt.Errorf("Failed to generate private key: %w", err)
|
||
|
}
|
||
|
|
||
|
dnsNames := ac.getDNSSANs()
|
||
|
ipAddresses := ac.getIPSANs()
|
||
|
|
||
|
// Create a CSR.
|
||
|
//
|
||
|
// The Common Name includes the dummy trust domain for now but Server will
|
||
|
// override this when it is signed anyway so it's OK.
|
||
|
cn := connect.AgentCN(ac.config.NodeName, unknownTrustDomain)
|
||
|
csr, err = connect.CreateCSR(id, cn, pk, dnsNames, ipAddresses)
|
||
|
if err != nil {
|
||
|
return "", "", err
|
||
|
}
|
||
|
|
||
|
return csr, pkPEM, nil
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) getDNSSANs() []string {
|
||
|
sans := defaultDNSSANs
|
||
|
switch {
|
||
|
case ac.config.AutoConfig.Enabled:
|
||
|
sans = append(sans, ac.config.AutoConfig.DNSSANs...)
|
||
|
case ac.config.AutoEncryptTLS:
|
||
|
sans = append(sans, ac.config.AutoEncryptDNSSAN...)
|
||
|
}
|
||
|
return sans
|
||
|
}
|
||
|
|
||
|
func (ac *AutoConfig) getIPSANs() []net.IP {
|
||
|
sans := defaultIPSANs
|
||
|
switch {
|
||
|
case ac.config.AutoConfig.Enabled:
|
||
|
sans = append(sans, ac.config.AutoConfig.IPSANs...)
|
||
|
case ac.config.AutoEncryptTLS:
|
||
|
sans = append(sans, ac.config.AutoEncryptIPSAN...)
|
||
|
}
|
||
|
return sans
|
||
|
}
|