github.com/outbrain/consul@v1.4.5/agent/structs/connect_ca.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"time"
     7  
     8  	"github.com/mitchellh/mapstructure"
     9  )
    10  
    11  // IndexedCARoots is the list of currently trusted CA Roots.
    12  type IndexedCARoots struct {
    13  	// ActiveRootID is the ID of a root in Roots that is the active CA root.
    14  	// Other roots are still valid if they're in the Roots list but are in
    15  	// the process of being rotated out.
    16  	ActiveRootID string
    17  
    18  	// TrustDomain is the identification root for this Consul cluster. All
    19  	// certificates signed by the cluster's CA must have their identifying URI in
    20  	// this domain.
    21  	//
    22  	// This does not include the protocol (currently spiffe://) since we may
    23  	// implement other protocols in future with equivalent semantics. It should be
    24  	// compared against the "authority" section of a URI (i.e. host:port).
    25  	//
    26  	// We need to support migrating a cluster between trust domains to support
    27  	// Multi-DC migration in Enterprise. In this case the current trust domain is
    28  	// here but entries in Roots may also have ExternalTrustDomain set to a
    29  	// non-empty value implying they were previous roots that are still trusted
    30  	// but under a different trust domain.
    31  	//
    32  	// Note that we DON'T validate trust domain during AuthZ since it causes
    33  	// issues of loss of connectivity during migration between trust domains. The
    34  	// only time the additional validation adds value is where the cluster shares
    35  	// an external root (e.g. organization-wide root) with another distinct Consul
    36  	// cluster or PKI system. In this case, x509 Name Constraints can be added to
    37  	// enforce that Consul's CA can only validly sign or trust certs within the
    38  	// same trust-domain. Name constraints as enforced by TLS handshake also allow
    39  	// seamless rotation between trust domains thanks to cross-signing.
    40  	TrustDomain string
    41  
    42  	// Roots is a list of root CA certs to trust.
    43  	Roots []*CARoot
    44  
    45  	// QueryMeta contains the meta sent via a header. We ignore for JSON
    46  	// so this whole structure can be returned.
    47  	QueryMeta `json:"-"`
    48  }
    49  
    50  // CARoot represents a root CA certificate that is trusted.
    51  type CARoot struct {
    52  	// ID is a globally unique ID (UUID) representing this CA root.
    53  	ID string
    54  
    55  	// Name is a human-friendly name for this CA root. This value is
    56  	// opaque to Consul and is not used for anything internally.
    57  	Name string
    58  
    59  	// SerialNumber is the x509 serial number of the certificate.
    60  	SerialNumber uint64
    61  
    62  	// SigningKeyID is the ID of the public key that corresponds to the private
    63  	// key used to sign the certificate. Is is the HexString format of the raw
    64  	// AuthorityKeyID bytes.
    65  	SigningKeyID string
    66  
    67  	// ExternalTrustDomain is the trust domain this root was generated under. It
    68  	// is usually empty implying "the current cluster trust-domain". It is set
    69  	// only in the case that a cluster changes trust domain and then all old roots
    70  	// that are still trusted have the old trust domain set here.
    71  	//
    72  	// We currently DON'T validate these trust domains explicitly anywhere, see
    73  	// IndexedRoots.TrustDomain doc. We retain this information for debugging and
    74  	// future flexibility.
    75  	ExternalTrustDomain string
    76  
    77  	// Time validity bounds.
    78  	NotBefore time.Time
    79  	NotAfter  time.Time
    80  
    81  	// RootCert is the PEM-encoded public certificate.
    82  	RootCert string
    83  
    84  	// IntermediateCerts is a list of PEM-encoded intermediate certs to
    85  	// attach to any leaf certs signed by this CA.
    86  	IntermediateCerts []string
    87  
    88  	// SigningCert is the PEM-encoded signing certificate and SigningKey
    89  	// is the PEM-encoded private key for the signing certificate. These
    90  	// may actually be empty if the CA plugin in use manages these for us.
    91  	SigningCert string `json:",omitempty"`
    92  	SigningKey  string `json:",omitempty"`
    93  
    94  	// Active is true if this is the current active CA. This must only
    95  	// be true for exactly one CA. For any method that modifies roots in the
    96  	// state store, tests should be written to verify that multiple roots
    97  	// cannot be active.
    98  	Active bool
    99  
   100  	// RotatedOutAt is the time at which this CA was removed from the state.
   101  	// This will only be set on roots that have been rotated out from being the
   102  	// active root.
   103  	RotatedOutAt time.Time `json:"-"`
   104  
   105  	RaftIndex
   106  }
   107  
   108  // CARoots is a list of CARoot structures.
   109  type CARoots []*CARoot
   110  
   111  // CASignRequest is the request for signing a service certificate.
   112  type CASignRequest struct {
   113  	// Datacenter is the target for this request.
   114  	Datacenter string
   115  
   116  	// CSR is the PEM-encoded CSR.
   117  	CSR string
   118  
   119  	// WriteRequest is a common struct containing ACL tokens and other
   120  	// write-related common elements for requests.
   121  	WriteRequest
   122  }
   123  
   124  // RequestDatacenter returns the datacenter for a given request.
   125  func (q *CASignRequest) RequestDatacenter() string {
   126  	return q.Datacenter
   127  }
   128  
   129  // IssuedCert is a certificate that has been issued by a Connect CA.
   130  type IssuedCert struct {
   131  	// SerialNumber is the unique serial number for this certificate.
   132  	// This is encoded in standard hex separated by :.
   133  	SerialNumber string
   134  
   135  	// CertPEM and PrivateKeyPEM are the PEM-encoded certificate and private
   136  	// key for that cert, respectively. This should not be stored in the
   137  	// state store, but is present in the sign API response.
   138  	CertPEM       string `json:",omitempty"`
   139  	PrivateKeyPEM string `json:",omitempty"`
   140  
   141  	// Service is the name of the service for which the cert was issued.
   142  	// ServiceURI is the cert URI value.
   143  	Service    string
   144  	ServiceURI string
   145  
   146  	// ValidAfter and ValidBefore are the validity periods for the
   147  	// certificate.
   148  	ValidAfter  time.Time
   149  	ValidBefore time.Time
   150  
   151  	RaftIndex
   152  }
   153  
   154  // CAOp is the operation for a request related to intentions.
   155  type CAOp string
   156  
   157  const (
   158  	CAOpSetRoots            CAOp = "set-roots"
   159  	CAOpSetConfig           CAOp = "set-config"
   160  	CAOpSetProviderState    CAOp = "set-provider-state"
   161  	CAOpDeleteProviderState CAOp = "delete-provider-state"
   162  	CAOpSetRootsAndConfig   CAOp = "set-roots-config"
   163  )
   164  
   165  // CARequest is used to modify connect CA data. This is used by the
   166  // FSM (agent/consul/fsm) to apply changes.
   167  type CARequest struct {
   168  	// Op is the type of operation being requested. This determines what
   169  	// other fields are required.
   170  	Op CAOp
   171  
   172  	// Datacenter is the target for this request.
   173  	Datacenter string
   174  
   175  	// Index is used by CAOpSetRoots and CAOpSetConfig for a CAS operation.
   176  	Index uint64
   177  
   178  	// Roots is a list of roots. This is used for CAOpSet. One root must
   179  	// always be active.
   180  	Roots []*CARoot
   181  
   182  	// Config is the configuration for the current CA plugin.
   183  	Config *CAConfiguration
   184  
   185  	// ProviderState is the state for the builtin CA provider.
   186  	ProviderState *CAConsulProviderState
   187  
   188  	// WriteRequest is a common struct containing ACL tokens and other
   189  	// write-related common elements for requests.
   190  	WriteRequest
   191  }
   192  
   193  // RequestDatacenter returns the datacenter for a given request.
   194  func (q *CARequest) RequestDatacenter() string {
   195  	return q.Datacenter
   196  }
   197  
   198  const (
   199  	ConsulCAProvider = "consul"
   200  	VaultCAProvider  = "vault"
   201  )
   202  
   203  // CAConfiguration is the configuration for the current CA plugin.
   204  type CAConfiguration struct {
   205  	// ClusterID is a unique identifier for the cluster
   206  	ClusterID string `json:"-"`
   207  
   208  	// Provider is the CA provider implementation to use.
   209  	Provider string
   210  
   211  	// Configuration is arbitrary configuration for the provider. This
   212  	// should only contain primitive values and containers (such as lists
   213  	// and maps).
   214  	Config map[string]interface{}
   215  
   216  	RaftIndex
   217  }
   218  
   219  func (c *CAConfiguration) GetCommonConfig() (*CommonCAProviderConfig, error) {
   220  	if c == nil {
   221  		return nil, fmt.Errorf("config map was nil")
   222  	}
   223  
   224  	var config CommonCAProviderConfig
   225  
   226  	// Set Defaults
   227  	config.CSRMaxPerSecond = 50 // See doc comment for rationale here.
   228  
   229  	decodeConf := &mapstructure.DecoderConfig{
   230  		DecodeHook:       ParseDurationFunc(),
   231  		Result:           &config,
   232  		WeaklyTypedInput: true,
   233  	}
   234  
   235  	decoder, err := mapstructure.NewDecoder(decodeConf)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	if err := decoder.Decode(c.Config); err != nil {
   241  		return nil, fmt.Errorf("error decoding config: %s", err)
   242  	}
   243  
   244  	return &config, nil
   245  }
   246  
   247  type CommonCAProviderConfig struct {
   248  	LeafCertTTL time.Duration
   249  
   250  	SkipValidate bool
   251  
   252  	// CSRMaxPerSecond is a rate limit on processing Connect Certificate Signing
   253  	// Requests on the servers. It applies to all CA providers so can be used to
   254  	// limit rate to an external CA too. 0 disables the rate limit. Defaults to 50
   255  	// which is low enough to prevent overload of a reasonably sized production
   256  	// server while allowing a cluster with 1000 service instances to complete a
   257  	// rotation in 20 seconds. For reference a quad-core 2017 MacBook pro can
   258  	// process 100 signing RPCs a second while using less than half of one core.
   259  	// For large clusters with powerful servers it's advisable to increase this
   260  	// rate or to disable this limit and instead rely on CSRMaxConcurrent to only
   261  	// consume a subset of the server's cores.
   262  	CSRMaxPerSecond float32
   263  
   264  	// CSRMaxConcurrent is a limit on how many concurrent CSR signing requests
   265  	// will be processed in parallel. New incoming signing requests will try for
   266  	// `consul.csrSemaphoreWait` (currently 500ms) for a slot before being
   267  	// rejected with a "rate limited" backpressure response. This effectively sets
   268  	// how many CPU cores can be occupied by Connect CA signing activity and
   269  	// should be a (small) subset of your server's available cores to allow other
   270  	// tasks to complete when a barrage of CSRs come in (e.g. after a CA root
   271  	// rotation). Setting to 0 disables the limit, attempting to sign certs
   272  	// immediately in the RPC goroutine. This is 0 by default and CSRMaxPerSecond
   273  	// is used. This is ignored if CSRMaxPerSecond is non-zero.
   274  	CSRMaxConcurrent int
   275  }
   276  
   277  func (c CommonCAProviderConfig) Validate() error {
   278  	if c.SkipValidate {
   279  		return nil
   280  	}
   281  
   282  	if c.LeafCertTTL < time.Hour {
   283  		return fmt.Errorf("leaf cert TTL must be greater than 1h")
   284  	}
   285  
   286  	if c.LeafCertTTL > 365*24*time.Hour {
   287  		return fmt.Errorf("leaf cert TTL must be less than 1 year")
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  type ConsulCAProviderConfig struct {
   294  	CommonCAProviderConfig `mapstructure:",squash"`
   295  
   296  	PrivateKey     string
   297  	RootCert       string
   298  	RotationPeriod time.Duration
   299  }
   300  
   301  // CAConsulProviderState is used to track the built-in Consul CA provider's state.
   302  type CAConsulProviderState struct {
   303  	ID               string
   304  	PrivateKey       string
   305  	RootCert         string
   306  	IntermediateCert string
   307  
   308  	RaftIndex
   309  }
   310  
   311  type VaultCAProviderConfig struct {
   312  	CommonCAProviderConfig `mapstructure:",squash"`
   313  
   314  	Address             string
   315  	Token               string
   316  	RootPKIPath         string
   317  	IntermediatePKIPath string
   318  
   319  	CAFile        string
   320  	CAPath        string
   321  	CertFile      string
   322  	KeyFile       string
   323  	TLSServerName string
   324  	TLSSkipVerify bool
   325  }
   326  
   327  // CALeafOp is the operation for a request related to leaf certificates.
   328  type CALeafOp string
   329  
   330  const (
   331  	CALeafOpIncrementIndex CALeafOp = "increment-index"
   332  )
   333  
   334  // CALeafRequest is used to modify connect CA leaf data. This is used by the
   335  // FSM (agent/consul/fsm) to apply changes.
   336  type CALeafRequest struct {
   337  	// Op is the type of operation being requested. This determines what
   338  	// other fields are required.
   339  	Op CALeafOp
   340  
   341  	// Datacenter is the target for this request.
   342  	Datacenter string
   343  
   344  	// WriteRequest is a common struct containing ACL tokens and other
   345  	// write-related common elements for requests.
   346  	WriteRequest
   347  }
   348  
   349  // RequestDatacenter returns the datacenter for a given request.
   350  func (q *CALeafRequest) RequestDatacenter() string {
   351  	return q.Datacenter
   352  }
   353  
   354  // ParseDurationFunc is a mapstructure hook for decoding a string or
   355  // []uint8 into a time.Duration value.
   356  func ParseDurationFunc() mapstructure.DecodeHookFunc {
   357  	return func(
   358  		f reflect.Type,
   359  		t reflect.Type,
   360  		data interface{}) (interface{}, error) {
   361  		var v time.Duration
   362  		if t != reflect.TypeOf(v) {
   363  			return data, nil
   364  		}
   365  
   366  		switch {
   367  		case f.Kind() == reflect.String:
   368  			if dur, err := time.ParseDuration(data.(string)); err != nil {
   369  				return nil, err
   370  			} else {
   371  				v = dur
   372  			}
   373  			return v, nil
   374  		case f == reflect.SliceOf(reflect.TypeOf(uint8(0))):
   375  			s := Uint8ToString(data.([]uint8))
   376  			if dur, err := time.ParseDuration(s); err != nil {
   377  				return nil, err
   378  			} else {
   379  				v = dur
   380  			}
   381  			return v, nil
   382  		default:
   383  			return data, nil
   384  		}
   385  	}
   386  }
   387  
   388  func Uint8ToString(bs []uint8) string {
   389  	b := make([]byte, len(bs))
   390  	for i, v := range bs {
   391  		b[i] = byte(v)
   392  	}
   393  	return string(b)
   394  }