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 }