consul/agent/connect/ca/provider.go

212 lines
10 KiB
Go

package ca
import (
"crypto/x509"
"errors"
)
//go:generate mockery --name Provider --inpackage
// ErrRateLimited is a sentinel error value Providers may return from any method
// to indicate that the operation can't complete due to a temporary rate limit.
// In the case of signing new certificates, Consul clients will respect this and
// intelligently backoff to optimize rotation rollout time while reducing load
// on servers and CA provider.
var ErrRateLimited = errors.New("operation rate limited by CA provider")
// PrimaryUsesIntermediate is an optional interface that CA providers may implement
// to indicate that they use an intermediate cert in the primary datacenter as
// well as the secondary. This is used when determining whether to run the
// intermediate renewal routine in the primary.
type PrimaryUsesIntermediate interface {
PrimaryUsesIntermediate()
}
// ProviderConfig encapsulates all the data Consul passes to `Configure` on a
// new provider instance. The provider must treat this as read-only and make
// copies of any map or slice if it might modify them internally.
type ProviderConfig struct {
// ClusterID is the current Consul cluster ID.
ClusterID string
// Datacenter is the current Consul datacenter.
Datacenter string
// IsPrimary is true when the CA instance is in the primary DC typically it
// may choose to act as a root in this case while secondaries are typically
// intermediate CAs. In some case the primary DC in Consul is an intermediate
// signed by some external CA along with that CA's public cert so the old name
// of `IsRoot` was misleading.
IsPrimary bool
// RawConfig is the user configuration for the provider and is
// provider-specific to be interpreted as the provider wishes.
RawConfig map[string]interface{}
// State contains the State the same provider last persisted. It is provided
// after a restart or reconfiguration, or on a leader election on a new server
// to maintain operation. It MUST NOT be used for secret storage since it is
// visible in the API to operators. It's intended use is to store small bits
// of state like UUIDs of external resources that the provider has created and
// needs to continue to manage.
State map[string]string
}
// Provider is the interface for Consul to interact with
// an external CA that provides leaf certificate signing for
// given SpiffeIDServices.
type Provider interface {
// Configure initializes the provider based on the given cluster ID, root
// status and configuration values. rawConfig contains the user-provided
// Config. State contains a the State the same provider last persisted on a
// restart or reconfiguration. The provider must not modify `rawConfig` or
// `state` maps directly as it may be being read from other goroutines.
Configure(cfg ProviderConfig) error
// State returns the current provider state. If the provider doesn't need to
// store anything other than what the user configured this can return nil. It
// is called after any config change before the new active config is stored in
// the state store and the most recent value returned by the provider is given
// in subsequent `Configure` calls provided that the current provider is the
// same type as the new provider instance being configured. This provides a
// simple way for providers to persist information like UUIDs of resources
// they manage. This state is visible to anyone with operator:read via the API
// so it's not intended for storing secrets like root private keys. Only
// strings are permitted since this has to pass through msgpack and so
// interface values will end up mangled in many cases which is ugly for all
// provider code to have to remember to reason about.
//
// Note that the map returned will be accessed (read-only) in other goroutines
// - for example passed to Configure in the Connect CA Config RPC endpoint -
// so it must not just be a pointer to a map that may internally be modified.
// If the Provider only writes to it during Configure it's safe to return
// as-is, but otherwise it's assumed the map returned is a copy of the state
// in the Provider struct so it won't change after being returned.
State() (map[string]string, error)
// ActiveIntermediate returns the current signing cert used by this provider
// for generating SPIFFE leaf certs. Note that this must not change except
// when Consul requests the change via GenerateIntermediate. Changing the
// signing cert will break Consul's assumptions about which validation paths
// are active.
ActiveIntermediate() (string, error)
// Sign signs a leaf certificate used by Connect proxies from a CSR. The PEM
// returned should include only the leaf certificate as all Intermediates
// needed to validate it will be added by Consul based on the active
// intermediate and any cross-signed intermediates managed by Consul. Note that
// providers should return ErrRateLimited if they are unable to complete the
// operation due to upstream rate limiting so that clients can intelligently
// backoff.
Sign(*x509.CertificateRequest) (string, error)
// Cleanup performs any necessary cleanup that should happen when the provider
// is shut down permanently, such as removing a temporary PKI backend in Vault
// created for an intermediate CA. Whether the CA provider type is changing
// and the other providers raw configuration is passed along so that the provider
// instance can determine which cleanup steps to perform. For example, when the
// Vault provider is in use and there is no type change occuring, the Vault
// provider should check if the intermediate PKI path is changing. If it is not
// changing then the provider should not remove that path from Vault.
Cleanup(providerTypeChange bool, otherConfig map[string]interface{}) error
// TODO: when CAManager has separate types for primary/secondary invert this
// relationship so that PrimaryProvider/SecondaryProvider embed Provider
PrimaryProvider
SecondaryProvider
}
type PrimaryProvider interface {
// GenerateRoot is called:
// * to initialize the CA system when a server is elected as a raft leader
// * when the CA configuration is updated in a way that might require
// generating a new root certificate.
//
// In both cases GenerateRoot is always called on a newly created provider
// after calling Provider.Configure, and before any other calls to the
// provider.
//
// The provider should return an existing root certificate if one exists,
// otherwise it should generate a new root certificate and return it.
GenerateRoot() (RootResult, error)
// GenerateIntermediate returns a new intermediate signing cert and sets it to
// the active intermediate. If multiple intermediates are needed to complete
// the chain from the signing certificate back to the active root, they should
// all by bundled here.
// TODO: replace with GenerateLeafSigningCert (https://github.com/hashicorp/consul/issues/12386)
GenerateIntermediate() (string, error)
// SignIntermediate will validate the CSR to ensure the trust domain in the
// URI SAN matches the local one and that basic constraints for a CA
// certificate are met. It should return a signed CA certificate with a path
// length constraint of 0 to ensure that the certificate cannot be used to
// generate further CA certs. Note that providers should return ErrRateLimited
// if they are unable to complete the operation due to upstream rate limiting
// so that clients can intelligently backoff.
SignIntermediate(*x509.CertificateRequest) (string, error)
// CrossSignCA must accept a CA certificate from another CA provider and cross
// sign it exactly as it is such that it forms a chain back the the
// CAProvider's current root. Specifically, the Distinguished Name, Subject
// Alternative Name, SubjectKeyID and other relevant extensions must be kept.
// The resulting certificate must have a distinct Serial Number and the
// AuthorityKeyID set to the CAProvider's current signing key as well as the
// Issuer related fields changed as necessary. The resulting certificate is
// returned as a PEM formatted string.
//
// If the CA provider does not support this operation, it may return an error
// provided `SupportsCrossSigning` also returns false. Note that
// providers should return ErrRateLimited if they are unable to complete the
// operation due to upstream rate limiting so that clients can intelligently
// backoff.
CrossSignCA(*x509.Certificate) (string, error)
// SupportsCrossSigning should indicate whether the CA provider supports
// cross-signing an external root to provide a seamless rotation. If the CA
// does not support this, the user will have to force an upgrade when that CA
// provider is the current CA as the upgrade may cause interruptions to
// connectivity during the rollout.
SupportsCrossSigning() (bool, error)
}
type SecondaryProvider interface {
// GenerateIntermediateCSR should return a CSR for an intermediate CA
// certificate. The intermediate CA will be signed by the primary CA and
// should be used by the provider to sign leaf certificates in the local
// datacenter.
//
// After the certificate is signed, SecondaryProvider.SetIntermediate will
// be called to store the intermediate CA.
GenerateIntermediateCSR() (string, error)
// SetIntermediate is called to store a newly signed leaf signing certificate and
// the chain of certificates back to the root CA certificate.
//
// The provider should save the certificates and use them to
// Provider.Sign leaf certificates.
SetIntermediate(intermediatePEM, rootPEM string) error
}
// RootResult is the result returned by PrimaryProvider.GenerateRoot.
//
// TODO: rename this struct
type RootResult struct {
// PEM encoded bundle of CA certificates. The first certificate must be the
// primary CA used to sign intermediates for secondary datacenters, and the
// last certificate must be the trusted CA.
//
// If there is only a single certificate in the bundle then it will be used
// as both the primary CA and the trusted CA.
PEM string
}
// NeedsStop is an optional interface that allows a CA to define a function
// to be called when the CA instance is no longer in use. This is different
// from Cleanup(), as only the local provider instance is being shut down
// such as in the case of a leader change.
type NeedsStop interface {
Stop()
}