github.com/outbrain/consul@v1.4.5/agent/connect/ca/provider_vault.go (about) 1 package ca 2 3 import ( 4 "bytes" 5 "crypto/x509" 6 "encoding/pem" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "strings" 11 12 "github.com/hashicorp/consul/agent/connect" 13 "github.com/hashicorp/consul/agent/structs" 14 "github.com/hashicorp/go-uuid" 15 vaultapi "github.com/hashicorp/vault/api" 16 "github.com/mitchellh/mapstructure" 17 ) 18 19 const VaultCALeafCertRole = "leaf-cert" 20 21 var ErrBackendNotMounted = fmt.Errorf("backend not mounted") 22 var ErrBackendNotInitialized = fmt.Errorf("backend not initialized") 23 24 type VaultProvider struct { 25 config *structs.VaultCAProviderConfig 26 client *vaultapi.Client 27 isRoot bool 28 clusterId string 29 } 30 31 func vaultTLSConfig(config *structs.VaultCAProviderConfig) *vaultapi.TLSConfig { 32 return &vaultapi.TLSConfig{ 33 CACert: config.CAFile, 34 CAPath: config.CAPath, 35 ClientCert: config.CertFile, 36 ClientKey: config.KeyFile, 37 Insecure: config.TLSSkipVerify, 38 TLSServerName: config.TLSServerName, 39 } 40 } 41 42 // Configure sets up the provider using the given configuration. 43 func (v *VaultProvider) Configure(clusterId string, isRoot bool, rawConfig map[string]interface{}) error { 44 config, err := ParseVaultCAConfig(rawConfig) 45 if err != nil { 46 return err 47 } 48 49 clientConf := &vaultapi.Config{ 50 Address: config.Address, 51 } 52 err = clientConf.ConfigureTLS(vaultTLSConfig(config)) 53 if err != nil { 54 return err 55 } 56 client, err := vaultapi.NewClient(clientConf) 57 if err != nil { 58 return err 59 } 60 61 client.SetToken(config.Token) 62 v.config = config 63 v.client = client 64 v.isRoot = isRoot 65 v.clusterId = clusterId 66 67 return nil 68 } 69 70 // ActiveRoot returns the active root CA certificate. 71 func (v *VaultProvider) ActiveRoot() (string, error) { 72 return v.getCA(v.config.RootPKIPath) 73 } 74 75 // GenerateRoot mounts and initializes a new root PKI backend if needed. 76 func (v *VaultProvider) GenerateRoot() error { 77 if !v.isRoot { 78 return fmt.Errorf("provider is not the root certificate authority") 79 } 80 81 // Set up the root PKI backend if necessary. 82 _, err := v.ActiveRoot() 83 switch err { 84 case ErrBackendNotMounted: 85 err := v.client.Sys().Mount(v.config.RootPKIPath, &vaultapi.MountInput{ 86 Type: "pki", 87 Description: "root CA backend for Consul Connect", 88 Config: vaultapi.MountConfigInput{ 89 MaxLeaseTTL: "8760h", 90 }, 91 }) 92 93 if err != nil { 94 return err 95 } 96 97 fallthrough 98 case ErrBackendNotInitialized: 99 spiffeID := connect.SpiffeIDSigning{ClusterID: v.clusterId, Domain: "consul"} 100 uuid, err := uuid.GenerateUUID() 101 if err != nil { 102 return err 103 } 104 _, err = v.client.Logical().Write(v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{ 105 "common_name": fmt.Sprintf("Vault CA Root Authority %s", uuid), 106 "uri_sans": spiffeID.URI().String(), 107 }) 108 if err != nil { 109 return err 110 } 111 default: 112 if err != nil { 113 return err 114 } 115 } 116 117 return nil 118 } 119 120 // GenerateIntermediateCSR creates a private key and generates a CSR 121 // for another datacenter's root to sign, overwriting the intermediate backend 122 // in the process. 123 func (v *VaultProvider) GenerateIntermediateCSR() (string, error) { 124 if v.isRoot { 125 return "", fmt.Errorf("provider is the root certificate authority, " + 126 "cannot generate an intermediate CSR") 127 } 128 129 return v.generateIntermediateCSR() 130 } 131 132 func (v *VaultProvider) generateIntermediateCSR() (string, error) { 133 mounts, err := v.client.Sys().ListMounts() 134 if err != nil { 135 return "", err 136 } 137 138 // Mount the backend if it isn't mounted already. 139 if _, ok := mounts[v.config.IntermediatePKIPath]; !ok { 140 err := v.client.Sys().Mount(v.config.IntermediatePKIPath, &vaultapi.MountInput{ 141 Type: "pki", 142 Description: "intermediate CA backend for Consul Connect", 143 Config: vaultapi.MountConfigInput{ 144 MaxLeaseTTL: "2160h", 145 }, 146 }) 147 148 if err != nil { 149 return "", err 150 } 151 } 152 153 // Create the role for issuing leaf certs if it doesn't exist yet 154 rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole 155 role, err := v.client.Logical().Read(rolePath) 156 if err != nil { 157 return "", err 158 } 159 spiffeID := connect.SpiffeIDSigning{ClusterID: v.clusterId, Domain: "consul"} 160 if role == nil { 161 _, err := v.client.Logical().Write(rolePath, map[string]interface{}{ 162 "allow_any_name": true, 163 "allowed_uri_sans": "spiffe://*", 164 "key_type": "any", 165 "max_ttl": v.config.LeafCertTTL.String(), 166 "no_store": true, 167 "require_cn": false, 168 }) 169 if err != nil { 170 return "", err 171 } 172 } 173 174 // Generate a new intermediate CSR for the root to sign. 175 data, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/generate/internal", map[string]interface{}{ 176 "common_name": "Vault CA Intermediate Authority", 177 "key_bits": 224, 178 "key_type": "ec", 179 "uri_sans": spiffeID.URI().String(), 180 }) 181 if err != nil { 182 return "", err 183 } 184 if data == nil || data.Data["csr"] == "" { 185 return "", fmt.Errorf("got empty value when generating intermediate CSR") 186 } 187 csr, ok := data.Data["csr"].(string) 188 if !ok { 189 return "", fmt.Errorf("csr result is not a string") 190 } 191 192 return csr, nil 193 } 194 195 // SetIntermediate writes the incoming intermediate and root certificates to the 196 // intermediate backend (as a chain). 197 func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error { 198 if v.isRoot { 199 return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter") 200 } 201 202 _, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{ 203 "certificate": fmt.Sprintf("%s\n%s", intermediatePEM, rootPEM), 204 }) 205 if err != nil { 206 return err 207 } 208 209 return nil 210 } 211 212 // ActiveIntermediate returns the current intermediate certificate. 213 func (v *VaultProvider) ActiveIntermediate() (string, error) { 214 return v.getCA(v.config.IntermediatePKIPath) 215 } 216 217 // getCA returns the raw CA cert for the given endpoint if there is one. 218 // We have to use the raw NewRequest call here instead of Logical().Read 219 // because the endpoint only returns the raw PEM contents of the CA cert 220 // and not the typical format of the secrets endpoints. 221 func (v *VaultProvider) getCA(path string) (string, error) { 222 req := v.client.NewRequest("GET", "/v1/"+path+"/ca/pem") 223 resp, err := v.client.RawRequest(req) 224 if resp != nil { 225 defer resp.Body.Close() 226 } 227 if resp != nil && resp.StatusCode == http.StatusNotFound { 228 return "", ErrBackendNotMounted 229 } 230 if err != nil { 231 return "", err 232 } 233 234 bytes, err := ioutil.ReadAll(resp.Body) 235 if err != nil { 236 return "", err 237 } 238 239 root := string(bytes) 240 if root == "" { 241 return "", ErrBackendNotInitialized 242 } 243 244 return root, nil 245 } 246 247 // GenerateIntermediate mounts the configured intermediate PKI backend if 248 // necessary, then generates and signs a new CA CSR using the root PKI backend 249 // and updates the intermediate backend to use that new certificate. 250 func (v *VaultProvider) GenerateIntermediate() (string, error) { 251 csr, err := v.generateIntermediateCSR() 252 if err != nil { 253 return "", err 254 } 255 256 // Sign the CSR with the root backend. 257 intermediate, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{ 258 "csr": csr, 259 "format": "pem_bundle", 260 }) 261 if err != nil { 262 return "", err 263 } 264 if intermediate == nil || intermediate.Data["certificate"] == "" { 265 return "", fmt.Errorf("got empty value when generating intermediate certificate") 266 } 267 268 // Set the intermediate backend to use the new certificate. 269 _, err = v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{ 270 "certificate": intermediate.Data["certificate"], 271 }) 272 if err != nil { 273 return "", err 274 } 275 276 return v.ActiveIntermediate() 277 } 278 279 // Sign calls the configured role in the intermediate PKI backend to issue 280 // a new leaf certificate based on the provided CSR, with the issuing 281 // intermediate CA cert attached. 282 func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) { 283 var pemBuf bytes.Buffer 284 if err := pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr.Raw}); err != nil { 285 return "", err 286 } 287 288 // Use the leaf cert role to sign a new cert for this CSR. 289 response, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"sign/"+VaultCALeafCertRole, map[string]interface{}{ 290 "csr": pemBuf.String(), 291 "ttl": v.config.LeafCertTTL.String(), 292 }) 293 if err != nil { 294 return "", fmt.Errorf("error issuing cert: %v", err) 295 } 296 if response == nil || response.Data["certificate"] == "" || response.Data["issuing_ca"] == "" { 297 return "", fmt.Errorf("certificate info returned from Vault was blank") 298 } 299 300 cert, ok := response.Data["certificate"].(string) 301 if !ok { 302 return "", fmt.Errorf("certificate was not a string") 303 } 304 ca, ok := response.Data["issuing_ca"].(string) 305 if !ok { 306 return "", fmt.Errorf("issuing_ca was not a string") 307 } 308 309 return fmt.Sprintf("%s\n%s", cert, ca), nil 310 } 311 312 // SignIntermediate returns a signed CA certificate with a path length constraint 313 // of 0 to ensure that the certificate cannot be used to generate further CA certs. 314 func (v *VaultProvider) SignIntermediate(csr *x509.CertificateRequest) (string, error) { 315 var pemBuf bytes.Buffer 316 err := pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr.Raw}) 317 if err != nil { 318 return "", err 319 } 320 321 // Sign the CSR with the root backend. 322 data, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-intermediate", map[string]interface{}{ 323 "csr": pemBuf.String(), 324 "format": "pem_bundle", 325 "max_path_length": 0, 326 }) 327 if err != nil { 328 return "", err 329 } 330 if data == nil || data.Data["certificate"] == "" { 331 return "", fmt.Errorf("got empty value when generating intermediate certificate") 332 } 333 334 intermediate, ok := data.Data["certificate"].(string) 335 if !ok { 336 return "", fmt.Errorf("signed intermediate result is not a string") 337 } 338 339 return intermediate, nil 340 } 341 342 // CrossSignCA takes a CA certificate and cross-signs it to form a trust chain 343 // back to our active root. 344 func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) { 345 var pemBuf bytes.Buffer 346 err := pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) 347 if err != nil { 348 return "", err 349 } 350 351 // Have the root PKI backend sign this cert. 352 response, err := v.client.Logical().Write(v.config.RootPKIPath+"root/sign-self-issued", map[string]interface{}{ 353 "certificate": pemBuf.String(), 354 }) 355 if err != nil { 356 return "", fmt.Errorf("error having Vault cross-sign cert: %v", err) 357 } 358 if response == nil || response.Data["certificate"] == "" { 359 return "", fmt.Errorf("certificate info returned from Vault was blank") 360 } 361 362 xcCert, ok := response.Data["certificate"].(string) 363 if !ok { 364 return "", fmt.Errorf("certificate was not a string") 365 } 366 367 return xcCert, nil 368 } 369 370 // Cleanup unmounts the configured intermediate PKI backend. It's fine to tear 371 // this down and recreate it on small config changes because the intermediate 372 // certs get bundled with the leaf certs, so there's no cost to the CA changing. 373 func (v *VaultProvider) Cleanup() error { 374 return v.client.Sys().Unmount(v.config.IntermediatePKIPath) 375 } 376 377 func ParseVaultCAConfig(raw map[string]interface{}) (*structs.VaultCAProviderConfig, error) { 378 config := structs.VaultCAProviderConfig{ 379 CommonCAProviderConfig: defaultCommonConfig(), 380 } 381 382 decodeConf := &mapstructure.DecoderConfig{ 383 DecodeHook: structs.ParseDurationFunc(), 384 Result: &config, 385 WeaklyTypedInput: true, 386 } 387 388 decoder, err := mapstructure.NewDecoder(decodeConf) 389 if err != nil { 390 return nil, err 391 } 392 393 if err := decoder.Decode(raw); err != nil { 394 return nil, fmt.Errorf("error decoding config: %s", err) 395 } 396 397 if config.Token == "" { 398 return nil, fmt.Errorf("must provide a Vault token") 399 } 400 401 if config.RootPKIPath == "" { 402 return nil, fmt.Errorf("must provide a valid path to a root PKI backend") 403 } 404 if !strings.HasSuffix(config.RootPKIPath, "/") { 405 config.RootPKIPath += "/" 406 } 407 408 if config.IntermediatePKIPath == "" { 409 return nil, fmt.Errorf("must provide a valid path for the intermediate PKI backend") 410 } 411 if !strings.HasSuffix(config.IntermediatePKIPath, "/") { 412 config.IntermediatePKIPath += "/" 413 } 414 415 if err := config.CommonCAProviderConfig.Validate(); err != nil { 416 return nil, err 417 } 418 419 return &config, nil 420 }