2020-06-05 19:56:19 +00:00
package consul
import (
"context"
2020-07-28 19:31:48 +00:00
"crypto/x509"
2020-06-05 19:56:19 +00:00
"encoding/base64"
"fmt"
"github.com/hashicorp/consul/acl"
2020-07-28 19:31:48 +00:00
"github.com/hashicorp/consul/proto"
"github.com/hashicorp/consul/agent/connect"
2020-06-05 19:56:19 +00:00
"github.com/hashicorp/consul/agent/consul/authmethod/ssoauth"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/lib/template"
2020-07-23 15:24:20 +00:00
"github.com/hashicorp/consul/proto/pbautoconf"
2020-07-28 19:31:48 +00:00
"github.com/hashicorp/consul/proto/pbconfig"
"github.com/hashicorp/consul/proto/pbconnect"
2020-06-05 19:56:19 +00:00
"github.com/hashicorp/consul/tlsutil"
bexpr "github.com/hashicorp/go-bexpr"
2020-07-28 19:31:48 +00:00
"github.com/mitchellh/mapstructure"
2020-06-05 19:56:19 +00:00
)
type AutoConfigOptions struct {
NodeName string
SegmentName string
2020-07-28 19:31:48 +00:00
CSR * x509 . CertificateRequest
SpiffeID * connect . SpiffeIDAgent
2020-06-05 19:56:19 +00:00
}
type AutoConfigAuthorizer interface {
// Authorizes the request and returns a struct containing the various
// options for how to generate the configuration.
2020-07-23 15:24:20 +00:00
Authorize ( * pbautoconf . AutoConfigRequest ) ( AutoConfigOptions , error )
2020-06-05 19:56:19 +00:00
}
type disabledAuthorizer struct { }
2020-07-23 15:24:20 +00:00
func ( _ * disabledAuthorizer ) Authorize ( _ * pbautoconf . AutoConfigRequest ) ( AutoConfigOptions , error ) {
2020-06-05 19:56:19 +00:00
return AutoConfigOptions { } , fmt . Errorf ( "Auto Config is disabled" )
}
type jwtAuthorizer struct {
validator * ssoauth . Validator
allowReuse bool
claimAssertions [ ] string
}
2020-07-23 15:24:20 +00:00
func ( a * jwtAuthorizer ) Authorize ( req * pbautoconf . AutoConfigRequest ) ( AutoConfigOptions , error ) {
2020-06-05 19:56:19 +00:00
// perform basic JWT Authorization
identity , err := a . validator . ValidateLogin ( context . Background ( ) , req . JWT )
if err != nil {
// TODO (autoconf) maybe we should add a more generic permission denied error not tied to the ACL package?
return AutoConfigOptions { } , acl . PermissionDenied ( "Failed JWT authorization: %v" , err )
}
varMap := map [ string ] string {
"node" : req . Node ,
"segment" : req . Segment ,
}
for _ , raw := range a . claimAssertions {
// validate and fill any HIL
filled , err := template . InterpolateHIL ( raw , varMap , true )
if err != nil {
return AutoConfigOptions { } , fmt . Errorf ( "Failed to render claim assertion template %q: %w" , raw , err )
}
evaluator , err := bexpr . CreateEvaluatorForType ( filled , nil , identity . SelectableFields )
if err != nil {
return AutoConfigOptions { } , fmt . Errorf ( "Failed to create evaluator for claim assertion %q: %w" , filled , err )
}
ok , err := evaluator . Evaluate ( identity . SelectableFields )
if err != nil {
return AutoConfigOptions { } , fmt . Errorf ( "Failed to execute claim assertion %q: %w" , filled , err )
}
if ! ok {
return AutoConfigOptions { } , acl . PermissionDenied ( "Failed JWT claim assertion" )
}
}
2020-07-28 19:31:48 +00:00
opts := AutoConfigOptions {
2020-06-05 19:56:19 +00:00
NodeName : req . Node ,
SegmentName : req . Segment ,
2020-07-28 19:31:48 +00:00
}
if req . CSR != "" {
csr , id , err := parseAutoConfigCSR ( req . CSR )
if err != nil {
return AutoConfigOptions { } , err
}
if id . Agent != req . Node {
return AutoConfigOptions { } , fmt . Errorf ( "Spiffe ID agent name (%s) of the certificate signing request is not for the correct node (%s)" , id . Agent , req . Node )
}
opts . CSR = csr
opts . SpiffeID = id
}
return opts , nil
2020-06-05 19:56:19 +00:00
}
2020-07-07 19:39:04 +00:00
type AutoConfigBackend interface {
CreateACLToken ( template * structs . ACLToken ) ( * structs . ACLToken , error )
DatacenterJoinAddresses ( segment string ) ( [ ] string , error )
ForwardRPC ( method string , info structs . RPCInfo , args , reply interface { } ) ( bool , error )
2020-07-28 19:31:48 +00:00
GetCARoots ( ) ( * structs . IndexedCARoots , error )
SignCertificate ( csr * x509 . CertificateRequest , id connect . CertURI ) ( * structs . IssuedCert , error )
2020-07-07 19:39:04 +00:00
}
2020-06-05 19:56:19 +00:00
2020-07-07 19:39:04 +00:00
// AutoConfig endpoint is used for cluster auto configuration operations
type AutoConfig struct {
2020-07-08 16:36:11 +00:00
// currently AutoConfig does not support pushing down any configuration that would be reloadable on the servers
// (outside of some TLS settings such as the configured CA certs which are retrieved via the TLS configurator)
// If that changes then we will need to change this to use an atomic.Value and provide means of reloading it.
config * Config
tlsConfigurator * tlsutil . Configurator
2020-07-07 19:39:04 +00:00
backend AutoConfigBackend
2020-06-05 19:56:19 +00:00
authorizer AutoConfigAuthorizer
}
2020-07-08 16:36:11 +00:00
func NewAutoConfig ( conf * Config , tlsConfigurator * tlsutil . Configurator , backend AutoConfigBackend , authz AutoConfigAuthorizer ) * AutoConfig {
if conf == nil {
conf = DefaultConfig ( )
2020-07-07 19:39:04 +00:00
}
2020-07-08 16:36:11 +00:00
return & AutoConfig {
config : conf ,
tlsConfigurator : tlsConfigurator ,
backend : backend ,
authorizer : authz ,
}
2020-07-07 19:39:04 +00:00
}
2020-06-05 19:56:19 +00:00
// updateTLSCertificatesInConfig will ensure that the TLS settings regarding how an agent is
// made aware of its certificates are populated. This will only work if connect is enabled and
// in some cases only if auto_encrypt is enabled on the servers. This endpoint has the option
// to configure auto_encrypt or potentially in the future to generate the certificates inline.
2020-07-28 19:31:48 +00:00
func ( ac * AutoConfig ) updateTLSCertificatesInConfig ( opts AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error {
// nothing to be done as we cannot generate certificates
if ! ac . config . ConnectEnabled {
return nil
}
if opts . CSR != nil {
cert , err := ac . backend . SignCertificate ( opts . CSR , opts . SpiffeID )
if err != nil {
return fmt . Errorf ( "Failed to sign CSR: %w" , err )
}
// convert to the protobuf form of the issued certificate
pbcert , err := translateIssuedCertToProtobuf ( cert )
if err != nil {
return err
}
resp . Certificate = pbcert
}
connectRoots , err := ac . backend . GetCARoots ( )
if err != nil {
return fmt . Errorf ( "Failed to lookup the CA roots: %w" , err )
}
// convert to the protobuf form of the issued certificate
pbroots , err := translateCARootsToProtobuf ( connectRoots )
if err != nil {
return err
}
resp . CARoots = pbroots
// get the non-connect CA certs from the TLS Configurator
if ac . tlsConfigurator != nil {
resp . ExtraCACertificates = ac . tlsConfigurator . ManualCAPems ( )
}
2020-06-05 19:56:19 +00:00
return nil
}
// updateACLtokensInConfig will configure all of the agents ACL settings and will populate
// the configuration with an agent token usable for all default agent operations.
2020-07-28 19:31:48 +00:00
func ( ac * AutoConfig ) updateACLsInConfig ( opts AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error {
acl := & pbconfig . ACL {
2020-07-08 16:36:11 +00:00
Enabled : ac . config . ACLsEnabled ,
PolicyTTL : ac . config . ACLPolicyTTL . String ( ) ,
RoleTTL : ac . config . ACLRoleTTL . String ( ) ,
TokenTTL : ac . config . ACLTokenTTL . String ( ) ,
DisabledTTL : ac . config . ACLDisabledTTL . String ( ) ,
DownPolicy : ac . config . ACLDownPolicy ,
DefaultPolicy : ac . config . ACLDefaultPolicy ,
EnableKeyListPolicy : ac . config . ACLEnableKeyListPolicy ,
2020-06-05 19:56:19 +00:00
}
// when ACLs are enabled we want to create a local token with a node identity
2020-07-08 16:36:11 +00:00
if ac . config . ACLsEnabled {
2020-07-07 19:39:04 +00:00
// set up the token template - the ids and create
template := structs . ACLToken {
2020-06-05 19:56:19 +00:00
Description : fmt . Sprintf ( "Auto Config Token for Node %q" , opts . NodeName ) ,
Local : true ,
NodeIdentities : [ ] * structs . ACLNodeIdentity {
{
NodeName : opts . NodeName ,
2020-07-08 16:36:11 +00:00
Datacenter : ac . config . Datacenter ,
2020-06-05 19:56:19 +00:00
} ,
} ,
EnterpriseMeta : * structs . DefaultEnterpriseMeta ( ) ,
}
2020-07-07 19:39:04 +00:00
token , err := ac . backend . CreateACLToken ( & template )
if err != nil {
return fmt . Errorf ( "Failed to generate an ACL token for node %q - %w" , opts . NodeName , err )
2020-06-05 19:56:19 +00:00
}
2020-07-28 19:31:48 +00:00
acl . Tokens = & pbconfig . ACLTokens { Agent : token . SecretID }
2020-06-05 19:56:19 +00:00
}
2020-07-28 19:31:48 +00:00
resp . Config . ACL = acl
2020-06-05 19:56:19 +00:00
return nil
}
// updateJoinAddressesInConfig determines the correct gossip endpoints that clients should
// be connecting to for joining the cluster based on the segment given in the opts parameter.
2020-07-28 19:31:48 +00:00
func ( ac * AutoConfig ) updateJoinAddressesInConfig ( opts AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error {
2020-07-07 19:39:04 +00:00
joinAddrs , err := ac . backend . DatacenterJoinAddresses ( opts . SegmentName )
2020-06-05 19:56:19 +00:00
if err != nil {
return err
}
2020-07-28 19:31:48 +00:00
if resp . Config . Gossip == nil {
resp . Config . Gossip = & pbconfig . Gossip { }
2020-06-05 19:56:19 +00:00
}
2020-07-28 19:31:48 +00:00
resp . Config . Gossip . RetryJoinLAN = joinAddrs
2020-06-05 19:56:19 +00:00
return nil
}
// updateGossipEncryptionInConfig will populate the gossip encryption configuration settings
2020-07-28 19:31:48 +00:00
func ( ac * AutoConfig ) updateGossipEncryptionInConfig ( _ AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error {
2020-06-05 19:56:19 +00:00
// Add gossip encryption settings if there is any key loaded
2020-07-08 16:36:11 +00:00
memberlistConfig := ac . config . SerfLANConfig . MemberlistConfig
2020-06-05 19:56:19 +00:00
if lanKeyring := memberlistConfig . Keyring ; lanKeyring != nil {
2020-07-28 19:31:48 +00:00
if resp . Config . Gossip == nil {
resp . Config . Gossip = & pbconfig . Gossip { }
2020-06-05 19:56:19 +00:00
}
2020-07-28 19:31:48 +00:00
if resp . Config . Gossip . Encryption == nil {
resp . Config . Gossip . Encryption = & pbconfig . GossipEncryption { }
2020-06-05 19:56:19 +00:00
}
pk := lanKeyring . GetPrimaryKey ( )
if len ( pk ) > 0 {
2020-07-28 19:31:48 +00:00
resp . Config . Gossip . Encryption . Key = base64 . StdEncoding . EncodeToString ( pk )
2020-06-05 19:56:19 +00:00
}
2020-07-28 19:31:48 +00:00
resp . Config . Gossip . Encryption . VerifyIncoming = memberlistConfig . GossipVerifyIncoming
resp . Config . Gossip . Encryption . VerifyOutgoing = memberlistConfig . GossipVerifyOutgoing
2020-06-05 19:56:19 +00:00
}
return nil
}
// updateTLSSettingsInConfig will populate the TLS configuration settings but will not
// populate leaf or ca certficiates.
2020-07-28 19:31:48 +00:00
func ( ac * AutoConfig ) updateTLSSettingsInConfig ( _ AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error {
2020-07-08 16:36:11 +00:00
if ac . tlsConfigurator == nil {
2020-07-07 19:39:04 +00:00
// TLS is not enabled?
return nil
}
2020-06-05 19:56:19 +00:00
// add in TLS configuration
2020-07-28 19:31:48 +00:00
if resp . Config . TLS == nil {
resp . Config . TLS = & pbconfig . TLS { }
2020-06-05 19:56:19 +00:00
}
2020-07-07 19:39:04 +00:00
2020-07-28 19:31:48 +00:00
resp . Config . TLS . VerifyServerHostname = ac . tlsConfigurator . VerifyServerHostname ( )
2020-07-08 16:36:11 +00:00
base := ac . tlsConfigurator . Base ( )
2020-07-28 19:31:48 +00:00
resp . Config . TLS . VerifyOutgoing = base . VerifyOutgoing
resp . Config . TLS . MinVersion = base . TLSMinVersion
resp . Config . TLS . PreferServerCipherSuites = base . PreferServerCipherSuites
2020-06-05 19:56:19 +00:00
var err error
2020-07-28 19:31:48 +00:00
resp . Config . TLS . CipherSuites , err = tlsutil . CipherString ( base . CipherSuites )
2020-06-05 19:56:19 +00:00
return err
}
// baseConfig will populate the configuration with some base settings such as the
// datacenter names, node name etc.
2020-07-28 19:31:48 +00:00
func ( ac * AutoConfig ) baseConfig ( opts AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error {
2020-06-05 19:56:19 +00:00
if opts . NodeName == "" {
return fmt . Errorf ( "Cannot generate auto config response without a node name" )
}
2020-07-28 19:31:48 +00:00
resp . Config . Datacenter = ac . config . Datacenter
resp . Config . PrimaryDatacenter = ac . config . PrimaryDatacenter
resp . Config . NodeName = opts . NodeName
resp . Config . SegmentName = opts . SegmentName
2020-06-05 19:56:19 +00:00
return nil
}
2020-07-28 19:31:48 +00:00
type autoConfigUpdater func ( c * AutoConfig , opts AutoConfigOptions , resp * pbautoconf . AutoConfigResponse ) error
2020-06-05 19:56:19 +00:00
var (
// variable holding the list of config updating functions to execute when generating
// the auto config response. This will allow for more easily adding extra self-contained
// configurators here in the future.
autoConfigUpdaters [ ] autoConfigUpdater = [ ] autoConfigUpdater {
2020-07-07 19:39:04 +00:00
( * AutoConfig ) . baseConfig ,
( * AutoConfig ) . updateJoinAddressesInConfig ,
( * AutoConfig ) . updateGossipEncryptionInConfig ,
( * AutoConfig ) . updateTLSSettingsInConfig ,
( * AutoConfig ) . updateACLsInConfig ,
( * AutoConfig ) . updateTLSCertificatesInConfig ,
2020-06-05 19:56:19 +00:00
}
)
// AgentAutoConfig will authorize the incoming request and then generate the configuration
// to push down to the client
2020-07-23 15:24:20 +00:00
func ( ac * AutoConfig ) InitialConfiguration ( req * pbautoconf . AutoConfigRequest , resp * pbautoconf . AutoConfigResponse ) error {
2020-06-05 19:56:19 +00:00
// default the datacenter to our datacenter - agents do not have to specify this as they may not
// yet know the datacenter name they are going to be in.
if req . Datacenter == "" {
2020-07-08 16:36:11 +00:00
req . Datacenter = ac . config . Datacenter
2020-06-05 19:56:19 +00:00
}
// TODO (autoconf) Is performing auto configuration over the WAN really a bad idea?
2020-07-08 16:36:11 +00:00
if req . Datacenter != ac . config . Datacenter {
2020-06-05 19:56:19 +00:00
return fmt . Errorf ( "invalid datacenter %q - agent auto configuration cannot target a remote datacenter" , req . Datacenter )
}
2020-07-08 16:36:11 +00:00
// TODO (autoconf) maybe panic instead?
if ac . backend == nil {
return fmt . Errorf ( "No Auto Config backend is configured" )
}
2020-06-05 19:56:19 +00:00
// forward to the leader
2020-07-07 19:39:04 +00:00
if done , err := ac . backend . ForwardRPC ( "AutoConfig.InitialConfiguration" , req , req , resp ) ; done {
2020-06-05 19:56:19 +00:00
return err
}
// TODO (autoconf) maybe panic instead?
2020-07-07 19:39:04 +00:00
if ac . authorizer == nil {
2020-06-05 19:56:19 +00:00
return fmt . Errorf ( "No Auto Config authorizer is configured" )
}
// authorize the request with the configured authorizer
2020-07-07 19:39:04 +00:00
opts , err := ac . authorizer . Authorize ( req )
2020-06-05 19:56:19 +00:00
if err != nil {
return err
}
2020-07-28 19:31:48 +00:00
resp . Config = & pbconfig . Config { }
2020-06-05 19:56:19 +00:00
// update all the configurations
for _ , configFn := range autoConfigUpdaters {
2020-07-28 19:31:48 +00:00
if err := configFn ( ac , opts , resp ) ; err != nil {
2020-06-05 19:56:19 +00:00
return err
}
}
return nil
}
2020-07-28 19:31:48 +00:00
func parseAutoConfigCSR ( csr string ) ( * x509 . CertificateRequest , * connect . SpiffeIDAgent , error ) {
// Parse the CSR string into the x509 CertificateRequest struct
x509CSR , err := connect . ParseCSR ( csr )
if err != nil {
return nil , nil , fmt . Errorf ( "Failed to parse CSR: %w" , err )
}
// ensure that a URI SAN is present
if len ( x509CSR . URIs ) < 1 {
return nil , nil , fmt . Errorf ( "CSR didn't include any URI SANs" )
}
// Parse the SPIFFE ID
spiffeID , err := connect . ParseCertURI ( x509CSR . URIs [ 0 ] )
if err != nil {
return nil , nil , fmt . Errorf ( "Failed to parse the SPIFFE URI: %w" , err )
}
agentID , isAgent := spiffeID . ( * connect . SpiffeIDAgent )
if ! isAgent {
return nil , nil , fmt . Errorf ( "SPIFFE ID is not an Agent ID" )
}
return x509CSR , agentID , nil
}
func translateCARootsToProtobuf ( in * structs . IndexedCARoots ) ( * pbconnect . CARoots , error ) {
var out pbconnect . CARoots
if err := mapstructureTranslateToProtobuf ( in , & out ) ; err != nil {
return nil , fmt . Errorf ( "Failed to re-encode CA Roots: %w" , err )
}
return & out , nil
}
func translateIssuedCertToProtobuf ( in * structs . IssuedCert ) ( * pbconnect . IssuedCert , error ) {
var out pbconnect . IssuedCert
if err := mapstructureTranslateToProtobuf ( in , & out ) ; err != nil {
return nil , fmt . Errorf ( "Failed to re-encode CA Roots: %w" , err )
}
return & out , nil
}
func mapstructureTranslateToProtobuf ( in interface { } , out interface { } ) error {
decoder , err := mapstructure . NewDecoder ( & mapstructure . DecoderConfig {
DecodeHook : proto . HookTimeToPBTimestamp ,
Result : out ,
} )
if err != nil {
return err
}
return decoder . Decode ( in )
}