2023-03-28 18:39:22 +00:00
// Copyright (c) HashiCorp, Inc.
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-28 18:39:22 +00:00
2018-06-13 08:40:03 +00:00
package ca
import (
"bytes"
2020-09-11 15:41:05 +00:00
"context"
2018-06-13 08:40:03 +00:00
"crypto/x509"
"encoding/pem"
"fmt"
2022-11-10 16:26:01 +00:00
"io"
2018-06-13 08:40:03 +00:00
"net/http"
"strings"
2020-10-09 11:35:42 +00:00
"time"
2018-06-13 08:40:03 +00:00
2020-08-25 17:34:49 +00:00
"github.com/hashicorp/go-hclog"
2018-06-13 08:40:03 +00:00
vaultapi "github.com/hashicorp/vault/api"
"github.com/mitchellh/mapstructure"
2021-06-23 19:47:30 +00:00
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
2022-06-01 21:53:52 +00:00
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/lib/decode"
2018-06-13 08:40:03 +00:00
)
2021-11-18 20:15:28 +00:00
const (
VaultCALeafCertRole = "leaf-cert"
VaultAuthMethodTypeAliCloud = "alicloud"
VaultAuthMethodTypeAppRole = "approle"
VaultAuthMethodTypeAWS = "aws"
VaultAuthMethodTypeAzure = "azure"
VaultAuthMethodTypeCloudFoundry = "cf"
VaultAuthMethodTypeGitHub = "github"
VaultAuthMethodTypeGCP = "gcp"
VaultAuthMethodTypeJWT = "jwt"
VaultAuthMethodTypeKerberos = "kerberos"
VaultAuthMethodTypeKubernetes = "kubernetes"
VaultAuthMethodTypeLDAP = "ldap"
VaultAuthMethodTypeOCI = "oci"
VaultAuthMethodTypeOkta = "okta"
VaultAuthMethodTypeRadius = "radius"
VaultAuthMethodTypeTLS = "cert"
VaultAuthMethodTypeToken = "token"
VaultAuthMethodTypeUserpass = "userpass"
defaultK8SServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
)
2018-06-13 08:40:03 +00:00
2023-02-07 20:52:22 +00:00
var (
ErrBackendNotMounted = fmt . Errorf ( "backend not mounted" )
ErrBackendNotInitialized = fmt . Errorf ( "backend not initialized" )
)
2018-06-13 08:40:03 +00:00
type VaultProvider struct {
2020-08-25 17:34:49 +00:00
config * structs . VaultCAProviderConfig
2022-05-05 02:41:55 +00:00
2020-08-25 17:34:49 +00:00
client * vaultapi . Client
2023-09-13 19:33:02 +00:00
2022-05-05 02:41:55 +00:00
baseNamespace string
2020-09-10 13:12:48 +00:00
2022-03-28 14:58:16 +00:00
stopWatcher func ( )
2020-08-25 17:34:49 +00:00
2022-09-08 08:11:19 +00:00
isPrimary bool
clusterID string
spiffeID * connect . SpiffeIDSigning
logger hclog . Logger
2022-11-28 21:17:58 +00:00
// isConsulMountedIntermediate is used to determine if we should tune the
// mount if the VaultProvider is ever reconfigured. This is at most a
// "best guess" to determine whether this instance of Consul created the
// intermediate mount but will not be able to tell if an existing mount
// was created by Consul (in a previous running instance) or was external.
isConsulMountedIntermediate bool
2018-06-13 08:40:03 +00:00
}
2022-12-05 21:39:21 +00:00
var _ Provider = ( * VaultProvider ) ( nil )
2021-06-21 21:51:37 +00:00
func NewVaultProvider ( logger hclog . Logger ) * VaultProvider {
return & VaultProvider {
2022-03-28 14:58:16 +00:00
stopWatcher : func ( ) { } ,
logger : logger ,
2021-06-21 21:51:37 +00:00
}
2020-09-11 15:41:05 +00:00
}
2019-01-08 16:09:22 +00:00
func vaultTLSConfig ( config * structs . VaultCAProviderConfig ) * vaultapi . TLSConfig {
return & vaultapi . TLSConfig {
CACert : config . CAFile ,
CAPath : config . CAPath ,
ClientCert : config . CertFile ,
ClientKey : config . KeyFile ,
Insecure : config . TLSSkipVerify ,
TLSServerName : config . TLSServerName ,
}
}
2018-09-11 23:43:04 +00:00
// Configure sets up the provider using the given configuration.
2023-02-07 20:52:22 +00:00
// Configure supports being called multiple times to re-configure the provider.
2019-11-18 14:22:19 +00:00
func ( v * VaultProvider ) Configure ( cfg ProviderConfig ) error {
2023-06-21 19:34:42 +00:00
config , err := ParseVaultCAConfig ( cfg . RawConfig , v . isPrimary )
2018-06-13 08:40:03 +00:00
if err != nil {
2018-09-07 02:18:54 +00:00
return err
2018-06-13 08:40:03 +00:00
}
2018-06-14 17:56:17 +00:00
clientConf := & vaultapi . Config {
2018-09-07 02:18:54 +00:00
Address : config . Address ,
2018-06-13 08:40:03 +00:00
}
2019-01-08 16:09:22 +00:00
err = clientConf . ConfigureTLS ( vaultTLSConfig ( config ) )
if err != nil {
return err
}
2018-06-14 17:56:17 +00:00
client , err := vaultapi . NewClient ( clientConf )
if err != nil {
2018-09-07 02:18:54 +00:00
return err
2018-06-14 17:56:17 +00:00
}
2022-04-14 18:18:06 +00:00
// We don't want to set the namespace if it's empty to prevent potential
// unknown behavior (what does Vault do with an empty namespace). The Vault
// client also makes sure the inputs are not empty strings so let's do the
// same.
if config . Namespace != "" {
client . SetNamespace ( config . Namespace )
2022-05-05 02:41:55 +00:00
v . baseNamespace = config . Namespace
2022-04-14 18:18:06 +00:00
}
2021-11-18 20:15:28 +00:00
if config . AuthMethod != nil {
loginResp , err := vaultLogin ( client , config . AuthMethod )
if err != nil {
return err
}
config . Token = loginResp . Auth . ClientToken
}
2018-09-07 02:18:54 +00:00
client . SetToken ( config . Token )
2021-11-05 16:42:28 +00:00
2018-09-07 02:18:54 +00:00
v . config = config
v . client = client
2019-11-18 14:22:19 +00:00
v . isPrimary = cfg . IsPrimary
v . clusterID = cfg . ClusterID
2021-11-05 22:20:24 +00:00
v . spiffeID = connect . SpiffeIDSigningForCluster ( v . clusterID )
2020-08-25 17:34:49 +00:00
// Look up the token to see if we can auto-renew its lease.
2020-10-27 20:03:17 +00:00
secret , err := client . Auth ( ) . Token ( ) . LookupSelf ( )
2020-08-25 17:34:49 +00:00
if err != nil {
return err
2021-02-22 20:08:49 +00:00
} else if secret == nil {
2021-11-18 20:15:28 +00:00
return fmt . Errorf ( "could not look up Vault provider token: not found" )
2020-08-25 17:34:49 +00:00
}
2020-09-10 13:31:04 +00:00
var token struct {
Renewable bool
TTL int
2020-08-25 17:34:49 +00:00
}
2020-09-10 13:31:04 +00:00
if err := mapstructure . Decode ( secret . Data , & token ) ; err != nil {
return err
2020-08-25 17:34:49 +00:00
}
// Set up a renewer to renew the token automatically, if supported.
2021-11-18 20:15:28 +00:00
if token . Renewable || config . AuthMethod != nil {
2020-10-09 12:02:18 +00:00
lifetimeWatcher , err := client . NewLifetimeWatcher ( & vaultapi . LifetimeWatcherInput {
2020-08-25 17:34:49 +00:00
Secret : & vaultapi . Secret {
Auth : & vaultapi . SecretAuth {
2020-09-09 23:36:37 +00:00
ClientToken : config . Token ,
2020-09-10 13:31:04 +00:00
Renewable : token . Renewable ,
2020-09-09 23:36:37 +00:00
LeaseDuration : secret . LeaseDuration ,
2020-08-25 17:34:49 +00:00
} ,
} ,
2020-10-09 12:02:18 +00:00
Increment : token . TTL ,
RenewBehavior : vaultapi . RenewBehaviorIgnoreErrors ,
2020-08-25 17:34:49 +00:00
} )
if err != nil {
2021-11-18 20:15:28 +00:00
return fmt . Errorf ( "error beginning Vault provider token renewal: %v" , err )
2020-08-25 17:34:49 +00:00
}
2020-09-11 15:41:05 +00:00
2021-11-18 20:15:28 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
2022-03-28 14:58:16 +00:00
if v . stopWatcher != nil {
2023-02-07 20:52:22 +00:00
// stop the running watcher loop if we are re-configuring
2022-03-28 14:58:16 +00:00
v . stopWatcher ( )
}
v . stopWatcher = cancel
2020-10-09 12:02:18 +00:00
go v . renewToken ( ctx , lifetimeWatcher )
2020-08-25 17:34:49 +00:00
}
2018-06-13 08:40:03 +00:00
2022-09-08 08:11:19 +00:00
// Update the intermediate (managed) PKI mount and role
if err := v . setupIntermediatePKIPath ( ) ; err != nil {
return err
}
2018-09-07 02:18:54 +00:00
return nil
}
2018-06-13 08:40:03 +00:00
2022-02-03 18:28:43 +00:00
func ( v * VaultProvider ) ValidateConfigUpdate ( prevRaw , nextRaw map [ string ] interface { } ) error {
2023-06-21 19:34:42 +00:00
prev , err := ParseVaultCAConfig ( prevRaw , v . isPrimary )
2022-02-03 18:28:43 +00:00
if err != nil {
2022-02-03 23:44:09 +00:00
return fmt . Errorf ( "failed to parse existing CA config: %w" , err )
2022-02-03 18:28:43 +00:00
}
2023-06-21 19:34:42 +00:00
next , err := ParseVaultCAConfig ( nextRaw , v . isPrimary )
2022-02-03 18:28:43 +00:00
if err != nil {
2022-02-03 23:44:09 +00:00
return fmt . Errorf ( "failed to parse new CA config: %w" , err )
2022-02-03 18:28:43 +00:00
}
if prev . RootPKIPath != next . RootPKIPath {
return nil
}
if prev . PrivateKeyType != "" && prev . PrivateKeyType != connect . DefaultPrivateKeyType {
if prev . PrivateKeyType != next . PrivateKeyType {
return fmt . Errorf ( "cannot update the PrivateKeyType field without changing RootPKIPath" )
}
}
if prev . PrivateKeyBits != 0 && prev . PrivateKeyBits != connect . DefaultPrivateKeyBits {
if prev . PrivateKeyBits != next . PrivateKeyBits {
return fmt . Errorf ( "cannot update the PrivateKeyBits field without changing RootPKIPath" )
}
}
return nil
}
2021-11-18 20:15:28 +00:00
// renewToken uses a vaultapi.LifetimeWatcher to repeatedly renew our token's lease.
// If the token can no longer be renewed and auth method is set,
// it will re-authenticate to Vault using the auth method and restart the renewer with the new token.
2020-10-09 12:02:18 +00:00
func ( v * VaultProvider ) renewToken ( ctx context . Context , watcher * vaultapi . LifetimeWatcher ) {
go watcher . Start ( )
defer watcher . Stop ( )
2020-08-25 17:34:49 +00:00
for {
select {
2020-09-11 15:41:05 +00:00
case <- ctx . Done ( ) :
2020-08-25 17:34:49 +00:00
return
2020-10-09 12:02:18 +00:00
case err := <- watcher . DoneCh ( ) :
2023-02-07 20:52:22 +00:00
// Watcher has stopped
2020-08-25 17:34:49 +00:00
if err != nil {
2020-09-15 19:28:58 +00:00
v . logger . Error ( "Error renewing token for Vault provider" , "error" , err )
2020-08-25 17:34:49 +00:00
}
2021-11-18 20:15:28 +00:00
// If the watcher has exited and auth method is enabled,
// re-authenticate using the auth method and set up a new watcher.
if v . config . AuthMethod != nil {
// Login to Vault using the auth method.
loginResp , err := vaultLogin ( v . client , v . config . AuthMethod )
if err != nil {
v . logger . Error ( "Error login in to Vault with %q auth method" , v . config . AuthMethod . Type )
2023-02-07 20:52:22 +00:00
2021-11-18 20:15:28 +00:00
go watcher . Start ( )
continue
}
// Set the new token for the vault client.
v . client . SetToken ( loginResp . Auth . ClientToken )
v . logger . Info ( "Successfully re-authenticated with Vault using auth method" )
// Start the new watcher for the new token.
watcher , err = v . client . NewLifetimeWatcher ( & vaultapi . LifetimeWatcherInput {
Secret : loginResp ,
RenewBehavior : vaultapi . RenewBehaviorIgnoreErrors ,
} )
if err != nil {
v . logger . Error ( "Error starting token renewal process" )
go watcher . Start ( )
continue
}
}
2022-03-28 14:58:16 +00:00
2020-10-09 12:02:18 +00:00
go watcher . Start ( )
2020-09-11 15:41:05 +00:00
2020-10-09 12:02:18 +00:00
case <- watcher . RenewCh ( ) :
2020-11-12 01:05:04 +00:00
v . logger . Info ( "Successfully renewed token for Vault provider" )
2020-08-25 17:34:49 +00:00
}
}
}
2019-11-11 20:57:16 +00:00
// State implements Provider. Vault provider needs no state other than the
// user-provided config currently.
func ( v * VaultProvider ) State ( ) ( map [ string ] string , error ) {
return nil , nil
}
2023-04-03 15:40:33 +00:00
// GenerateCAChain mounts and initializes a new root PKI backend if needed.
2023-07-14 19:58:33 +00:00
func ( v * VaultProvider ) GenerateCAChain ( ) ( string , error ) {
2019-11-18 14:22:19 +00:00
if ! v . isPrimary {
2023-07-14 19:58:33 +00:00
return "" , fmt . Errorf ( "provider is not the root certificate authority" )
2018-06-13 08:40:03 +00:00
}
// Set up the root PKI backend if necessary.
2022-05-05 02:41:55 +00:00
rootPEM , err := v . getCA ( v . config . RootPKINamespace , v . config . RootPKIPath )
2018-06-13 08:40:03 +00:00
switch err {
case ErrBackendNotMounted :
2022-05-05 02:41:55 +00:00
err := v . mountNamespaced ( v . config . RootPKINamespace , v . config . RootPKIPath , & vaultapi . MountInput {
2018-06-13 08:40:03 +00:00
Type : "pki" ,
Description : "root CA backend for Consul Connect" ,
Config : vaultapi . MountConfigInput {
2021-11-02 18:02:10 +00:00
// the max lease ttl denotes the maximum ttl that secrets are created from the engine
// the default lease ttl is the kind of ttl that will *reliably* set the ttl to v.config.RootCertTTL
// https://www.vaultproject.io/docs/secrets/pki#configure-a-ca-certificate
MaxLeaseTTL : v . config . RootCertTTL . String ( ) ,
DefaultLeaseTTL : v . config . RootCertTTL . String ( ) ,
2018-06-13 08:40:03 +00:00
} ,
} )
if err != nil {
2023-07-14 19:58:33 +00:00
return "" , fmt . Errorf ( "failed to mount root CA backend: %w" , err )
2018-06-13 08:40:03 +00:00
}
2022-11-28 21:17:58 +00:00
// We want to initialize afterwards
2018-06-13 08:40:03 +00:00
fallthrough
case ErrBackendNotInitialized :
2019-11-11 17:11:54 +00:00
uid , err := connect . CompactUID ( )
2019-08-27 21:45:58 +00:00
if err != nil {
2023-07-14 19:58:33 +00:00
return "" , err
2019-08-27 21:45:58 +00:00
}
2022-05-05 02:41:55 +00:00
resp , err := v . writeNamespaced ( v . config . RootPKINamespace , v . config . RootPKIPath + "root/generate/internal" , map [ string ] interface { } {
2019-11-18 14:22:19 +00:00
"common_name" : connect . CACN ( "vault" , uid , v . clusterID , v . isPrimary ) ,
2019-09-23 17:04:40 +00:00
"uri_sans" : v . spiffeID . URI ( ) . String ( ) ,
2019-07-30 21:47:39 +00:00
"key_type" : v . config . PrivateKeyType ,
"key_bits" : v . config . PrivateKeyBits ,
2018-06-13 08:40:03 +00:00
} )
if err != nil {
2023-07-14 19:58:33 +00:00
return "" , fmt . Errorf ( "failed to initialize root CA: %w" , err )
2021-12-01 20:39:20 +00:00
}
2022-01-05 23:21:04 +00:00
var ok bool
rootPEM , ok = resp . Data [ "certificate" ] . ( string )
if ! ok {
2023-07-14 19:58:33 +00:00
return "" , fmt . Errorf ( "unexpected response from Vault: %v" , resp . Data [ "certificate" ] )
2018-06-13 08:40:03 +00:00
}
2021-12-01 20:39:20 +00:00
2018-06-13 08:40:03 +00:00
default :
if err != nil {
2023-07-14 19:58:33 +00:00
return "" , fmt . Errorf ( "unexpected error while setting root PKI backend: %w" , err )
2018-06-13 08:40:03 +00:00
}
}
2022-05-05 02:41:55 +00:00
rootChain , err := v . getCAChain ( v . config . RootPKINamespace , v . config . RootPKIPath )
2022-01-05 23:21:04 +00:00
if err != nil {
2023-07-14 19:58:33 +00:00
return "" , err
2022-01-05 23:21:04 +00:00
}
// Workaround for a bug in the Vault PKI API.
// See https://github.com/hashicorp/vault/issues/13489
if rootChain == "" {
rootChain = rootPEM
}
2023-07-14 19:58:33 +00:00
return rootChain , nil
2018-06-13 08:40:03 +00:00
}
2018-09-13 20:09:07 +00:00
// GenerateIntermediateCSR creates a private key and generates a CSR
// for another datacenter's root to sign, overwriting the intermediate backend
// in the process.
2022-12-05 21:39:21 +00:00
func ( v * VaultProvider ) GenerateIntermediateCSR ( ) ( string , string , error ) {
2019-11-18 14:22:19 +00:00
if v . isPrimary {
2022-12-05 21:39:21 +00:00
return "" , "" , fmt . Errorf ( "provider is the root certificate authority, " +
2018-09-13 20:09:07 +00:00
"cannot generate an intermediate CSR" )
2018-06-13 08:40:03 +00:00
}
2022-12-05 21:39:21 +00:00
return v . generateIntermediateCSR ( )
2018-06-13 08:40:03 +00:00
}
2020-06-05 19:36:22 +00:00
func ( v * VaultProvider ) setupIntermediatePKIPath ( ) error {
2022-09-08 08:11:19 +00:00
mountConfig := vaultapi . MountConfigInput {
MaxLeaseTTL : v . config . IntermediateCertTTL . String ( ) ,
2020-06-05 19:36:22 +00:00
}
2018-06-13 08:40:03 +00:00
2022-05-05 02:41:55 +00:00
_ , err := v . getCA ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath )
2022-04-01 06:35:38 +00:00
if err != nil {
if err == ErrBackendNotMounted {
2022-05-05 02:41:55 +00:00
err := v . mountNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath , & vaultapi . MountInput {
2022-04-01 06:35:38 +00:00
Type : "pki" ,
Description : "intermediate CA backend for Consul Connect" ,
2022-09-08 08:11:19 +00:00
Config : mountConfig ,
2022-04-01 06:35:38 +00:00
} )
if err != nil {
2022-11-28 21:17:58 +00:00
return fmt . Errorf ( "failed to mount intermediate PKI backend: %w" , err )
2022-04-01 06:35:38 +00:00
}
2022-11-28 21:17:58 +00:00
// Required to determine if we should tune the mount
// if the VaultProvider is ever reconfigured.
v . isConsulMountedIntermediate = true
} else if err == ErrBackendNotInitialized {
// If this is the first time calling setupIntermediatePKIPath, the backend
// will not have been initialized. Since the mount is ready we can suppress
// this error.
2022-04-01 06:35:38 +00:00
} else {
2022-11-28 21:17:58 +00:00
return fmt . Errorf ( "unexpected error while fetching intermediate CA: %w" , err )
2018-06-13 08:40:03 +00:00
}
2022-09-08 08:11:19 +00:00
} else {
2022-11-28 21:17:58 +00:00
v . logger . Info ( "Found existing Intermediate PKI path mount" ,
"namespace" , v . config . IntermediatePKINamespace ,
"path" , v . config . IntermediatePKIPath ,
)
// This codepath requires the Vault policy:
//
// path "/sys/mounts/<intermediate_pki_path>/tune" {
// capabilities = [ "update" ]
// }
//
2022-09-08 08:11:19 +00:00
err := v . tuneMountNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath , & mountConfig )
if err != nil {
2022-11-28 21:17:58 +00:00
if v . isConsulMountedIntermediate {
v . logger . Warn ( "Intermediate PKI path was mounted by Consul but could not be tuned" ,
"namespace" , v . config . IntermediatePKINamespace ,
"path" , v . config . IntermediatePKIPath ,
"error" , err ,
)
} else {
v . logger . Debug ( "Failed to tune Intermediate PKI mount. 403 Forbidden is expected if Consul does not have tune capabilities for the Intermediate PKI mount (i.e. using Vault-managed policies)" ,
"namespace" , v . config . IntermediatePKINamespace ,
"path" , v . config . IntermediatePKIPath ,
"error" , err ,
)
}
2022-09-08 08:11:19 +00:00
}
2018-06-13 08:40:03 +00:00
}
2022-11-28 21:17:58 +00:00
// Create the role for issuing leaf certs
2018-06-13 08:40:03 +00:00
rolePath := v . config . IntermediatePKIPath + "roles/" + VaultCALeafCertRole
2022-09-08 08:11:19 +00:00
_ , err = v . writeNamespaced ( v . config . IntermediatePKINamespace , rolePath , map [ string ] interface { } {
"allow_any_name" : true ,
"allowed_uri_sans" : "spiffe://*" ,
"key_type" : "any" ,
"max_ttl" : v . config . LeafCertTTL . String ( ) ,
"no_store" : true ,
"require_cn" : false ,
} )
2022-05-05 02:41:55 +00:00
2023-05-03 20:30:37 +00:00
// enable auto-tidy with tidy_expired_issuers
v . autotidyIssuers ( v . config . IntermediatePKIPath )
2022-09-08 08:11:19 +00:00
return err
2020-06-05 19:36:22 +00:00
}
2022-11-17 21:29:49 +00:00
// generateIntermediateCSR returns the CSR and key_id (only present in
// Vault 1.11+) or any errors encountered.
func ( v * VaultProvider ) generateIntermediateCSR ( ) ( string , string , error ) {
2018-06-13 08:40:03 +00:00
// Generate a new intermediate CSR for the root to sign.
2019-11-11 17:11:54 +00:00
uid , err := connect . CompactUID ( )
if err != nil {
2022-11-17 21:29:49 +00:00
return "" , "" , err
2019-11-11 17:11:54 +00:00
}
2022-05-05 02:41:55 +00:00
data , err := v . writeNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "intermediate/generate/internal" , map [ string ] interface { } {
2019-11-18 14:22:19 +00:00
"common_name" : connect . CACN ( "vault" , uid , v . clusterID , v . isPrimary ) ,
2019-07-30 21:47:39 +00:00
"key_type" : v . config . PrivateKeyType ,
"key_bits" : v . config . PrivateKeyBits ,
2019-09-23 17:04:40 +00:00
"uri_sans" : v . spiffeID . URI ( ) . String ( ) ,
2018-06-13 08:40:03 +00:00
} )
if err != nil {
2022-11-17 21:29:49 +00:00
return "" , "" , err
2018-06-13 08:40:03 +00:00
}
2018-09-13 20:09:07 +00:00
if data == nil || data . Data [ "csr" ] == "" {
2022-11-17 21:29:49 +00:00
return "" , "" , fmt . Errorf ( "got empty value when generating intermediate CSR" )
2018-06-13 08:40:03 +00:00
}
2018-09-13 20:09:07 +00:00
csr , ok := data . Data [ "csr" ] . ( string )
if ! ok {
2022-11-17 21:29:49 +00:00
return "" , "" , fmt . Errorf ( "csr result is not a string" )
2018-09-13 20:09:07 +00:00
}
2022-11-17 21:29:49 +00:00
// Vault 1.11+ will return a "key_id" field which helps
// identify the correct issuer to set as default.
// https://github.com/hashicorp/vault/blob/e445c8b4f58dc20a0316a7fd1b5725b401c3b17a/builtin/logical/pki/path_intermediate.go#L154
if rawkeyId , ok := data . Data [ "key_id" ] ; ok {
keyId , ok := rawkeyId . ( string )
if ! ok {
return "" , "" , fmt . Errorf ( "key_id is not a string" )
}
return csr , keyId , nil
}
return csr , "" , nil
2018-09-13 20:09:07 +00:00
}
// SetIntermediate writes the incoming intermediate and root certificates to the
// intermediate backend (as a chain).
2022-12-05 21:39:21 +00:00
func ( v * VaultProvider ) SetIntermediate ( intermediatePEM , rootPEM , keyId string ) error {
2019-11-18 14:22:19 +00:00
if v . isPrimary {
2018-09-13 20:09:07 +00:00
return fmt . Errorf ( "cannot set an intermediate using another root in the primary datacenter" )
}
2022-01-06 00:08:26 +00:00
err := validateSetIntermediate ( intermediatePEM , rootPEM , v . spiffeID )
2019-09-23 17:04:40 +00:00
if err != nil {
return err
}
2022-12-05 21:39:21 +00:00
importResp , err := v . writeNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "intermediate/set-signed" , map [ string ] interface { } {
2021-11-12 00:16:53 +00:00
"certificate" : intermediatePEM ,
2018-09-13 20:09:07 +00:00
} )
if err != nil {
return err
}
2022-12-05 21:39:21 +00:00
// Vault 1.11+ will return a non-nil response from intermediate/set-signed
if importResp != nil {
err := v . setDefaultIntermediateIssuer ( importResp , keyId )
if err != nil {
return fmt . Errorf ( "failed to update default intermediate issuer: %w" , err )
}
}
2018-09-13 20:09:07 +00:00
return nil
}
// ActiveIntermediate returns the current intermediate certificate.
2023-04-03 15:40:33 +00:00
func ( v * VaultProvider ) ActiveLeafSigningCert ( ) ( string , error ) {
2022-05-05 02:41:55 +00:00
cert , err := v . getCA ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath )
2020-06-05 19:36:22 +00:00
// This error is expected when calling initializeSecondaryCA for the
// first time. It means that the backend is mounted and ready, but
// there is no intermediate.
// This error is swallowed because there is nothing the caller can do
// about it. The caller needs to handle the empty cert though and
// create an intermediate CA.
if err == ErrBackendNotInitialized {
return "" , nil
}
return cert , err
2018-09-13 20:09:07 +00:00
}
// getCA returns the raw CA cert for the given endpoint if there is one.
// We have to use the raw NewRequest call here instead of Logical().Read
// because the endpoint only returns the raw PEM contents of the CA cert
// and not the typical format of the secrets endpoints.
2022-05-05 02:41:55 +00:00
func ( v * VaultProvider ) getCA ( namespace , path string ) ( string , error ) {
2023-09-13 19:33:02 +00:00
resp , err := v . client . WithNamespace ( namespace ) . Logical ( ) . ReadRaw ( path + "/ca/pem" )
2018-09-13 20:09:07 +00:00
if resp != nil {
defer resp . Body . Close ( )
}
if resp != nil && resp . StatusCode == http . StatusNotFound {
return "" , ErrBackendNotMounted
}
if err != nil {
return "" , err
}
2022-11-10 16:26:01 +00:00
bytes , err := io . ReadAll ( resp . Body )
2018-09-13 20:09:07 +00:00
if err != nil {
return "" , err
}
2022-06-01 21:53:52 +00:00
root := lib . EnsureTrailingNewline ( string ( bytes ) )
2018-09-13 20:09:07 +00:00
if root == "" {
return "" , ErrBackendNotInitialized
}
return root , nil
}
2022-01-05 23:21:04 +00:00
// TODO: refactor to remove duplication with getCA
2022-05-05 02:41:55 +00:00
func ( v * VaultProvider ) getCAChain ( namespace , path string ) ( string , error ) {
2023-09-13 19:33:02 +00:00
resp , err := v . client . WithNamespace ( namespace ) . Logical ( ) . ReadRaw ( path + "/ca_chain" )
2022-01-05 23:21:04 +00:00
if resp != nil {
defer resp . Body . Close ( )
}
if resp != nil && resp . StatusCode == http . StatusNotFound {
return "" , ErrBackendNotMounted
}
if err != nil {
return "" , err
}
2022-11-10 16:26:01 +00:00
raw , err := io . ReadAll ( resp . Body )
2022-01-05 23:21:04 +00:00
if err != nil {
return "" , err
}
2022-06-01 21:53:52 +00:00
root := lib . EnsureTrailingNewline ( string ( raw ) )
2022-01-05 23:21:04 +00:00
return root , nil
}
2023-07-14 19:58:33 +00:00
// GenerateLeafSigningCert mounts the configured intermediate PKI backend if
2018-09-13 20:09:07 +00:00
// necessary, then generates and signs a new CA CSR using the root PKI backend
// and updates the intermediate backend to use that new certificate.
2023-04-03 15:40:33 +00:00
func ( v * VaultProvider ) GenerateLeafSigningCert ( ) ( string , error ) {
2022-11-17 21:29:49 +00:00
csr , keyId , err := v . generateIntermediateCSR ( )
2018-09-13 20:09:07 +00:00
if err != nil {
return "" , err
}
2018-06-13 08:40:03 +00:00
// Sign the CSR with the root backend.
2022-05-05 02:41:55 +00:00
intermediate , err := v . writeNamespaced ( v . config . RootPKINamespace , v . config . RootPKIPath + "root/sign-intermediate" , map [ string ] interface { } {
2019-09-23 17:04:40 +00:00
"csr" : csr ,
"use_csr_values" : true ,
"format" : "pem_bundle" ,
2020-09-30 19:31:21 +00:00
"ttl" : v . config . IntermediateCertTTL . String ( ) ,
2018-06-13 08:40:03 +00:00
} )
if err != nil {
return "" , err
}
2018-06-15 23:25:53 +00:00
if intermediate == nil || intermediate . Data [ "certificate" ] == "" {
2018-06-13 08:40:03 +00:00
return "" , fmt . Errorf ( "got empty value when generating intermediate certificate" )
}
// Set the intermediate backend to use the new certificate.
2022-11-17 21:29:49 +00:00
importResp , err := v . writeNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "intermediate/set-signed" , map [ string ] interface { } {
2018-06-13 08:40:03 +00:00
"certificate" : intermediate . Data [ "certificate" ] ,
} )
if err != nil {
return "" , err
}
2022-11-17 21:29:49 +00:00
// Vault 1.11+ will return a non-nil response from intermediate/set-signed
if importResp != nil {
err := v . setDefaultIntermediateIssuer ( importResp , keyId )
if err != nil {
return "" , fmt . Errorf ( "failed to update default intermediate issuer: %w" , err )
}
}
2023-04-03 15:40:33 +00:00
return v . ActiveLeafSigningCert ( )
2018-06-13 08:40:03 +00:00
}
2022-11-17 21:29:49 +00:00
// setDefaultIntermediateIssuer updates the default issuer for
// intermediate CA since Vault, as part of its 1.11+ support for
// multiple issuers, no longer overwrites the default issuer when
// generateIntermediateCSR (intermediate/generate/internal) is called.
//
// The response we get from calling [/intermediate/set-signed]
// should contain a "mapping" data field we can use to cross-reference
// with the keyId returned when calling [/intermediate/generate/internal].
//
2023-09-13 19:33:02 +00:00
// After a new default issuer is written, this function also cleans up
// the previous default issuer along with its associated key.
//
2022-11-17 21:29:49 +00:00
// [/intermediate/set-signed]: https://developer.hashicorp.com/vault/api-docs/secret/pki#import-ca-certificates-and-keys
// [/intermediate/generate/internal]: https://developer.hashicorp.com/vault/api-docs/secret/pki#generate-intermediate-csr
func ( v * VaultProvider ) setDefaultIntermediateIssuer ( vaultResp * vaultapi . Secret , keyId string ) error {
if vaultResp . Data [ "mapping" ] == nil {
return fmt . Errorf ( "expected Vault response data to have a 'mapping' key" )
}
if keyId == "" {
return fmt . Errorf ( "expected non-empty keyId" )
}
mapping , ok := vaultResp . Data [ "mapping" ] . ( map [ string ] any )
if ! ok {
return fmt . Errorf ( "unexpected type for 'mapping' value in Vault response" )
}
var intermediateId string
// The value in this KV pair is called "key"
for issuer , key := range mapping {
if key == keyId {
// Expect to find the key_id we got from Vault when we
// generated the intermediate CSR.
intermediateId = issuer
break
}
}
if intermediateId == "" {
return fmt . Errorf ( "could not find key_id %q in response from vault" , keyId )
}
// For Vault 1.11+ it is important to GET then POST to avoid clobbering fields
// like `default_follows_latest_issuer`.
// https://developer.hashicorp.com/vault/api-docs/secret/pki#default_follows_latest_issuer
resp , err := v . readNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "config/issuers" )
if err != nil {
return fmt . Errorf ( "could not read from /config/issuers: %w" , err )
}
issuersConf := resp . Data
2023-09-13 19:33:02 +00:00
prevIssuer , ok := issuersConf [ "default" ] . ( string )
if ! ok {
return fmt . Errorf ( "unexpected type for 'default' value in Vault response from /pki/config/issuers" )
}
if prevIssuer == intermediateId {
return nil
}
2022-11-17 21:29:49 +00:00
// Overwrite the default issuer
issuersConf [ "default" ] = intermediateId
_ , err = v . writeNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "config/issuers" , issuersConf )
if err != nil {
return fmt . Errorf ( "could not write default issuer to /config/issuers: %w" , err )
}
2023-09-13 19:33:02 +00:00
// Find the key_id of the previous issuer. In Consul, issuers have 1:1 relationship with
// keys so we can delete issuer first then the key.
resp , err = v . readNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "issuer/" + prevIssuer )
if err != nil {
return fmt . Errorf ( "could not read issuer %q: %w" , prevIssuer , err )
}
prevKeyId , ok := resp . Data [ "key_id" ] . ( string )
if ! ok {
return fmt . Errorf ( "unexpected type for 'key_id' value in Vault response" )
}
// Delete the previously known default issuer to prevent the number of unused
// issuers from increasing too much.
_ , err = v . deleteNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "issuer/" + prevIssuer )
if err != nil {
v . logger . Warn ( "Could not delete previous issuer. Manually delete from Vault to prevent the list of issuers from growing too large." ,
"prev_issuer_id" , prevIssuer ,
"error" , err )
}
// Keys can only be deleted if there are no more issuers referencing them.
_ , err = v . deleteNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "key/" + prevKeyId )
if err != nil {
v . logger . Warn ( "Could not delete previous key. Manually delete from Vault to prevent the list of keys from growing too large." ,
"prev_key_id" , prevKeyId ,
"error" , err )
}
2022-11-17 21:29:49 +00:00
return nil
}
2018-06-13 08:40:03 +00:00
// Sign calls the configured role in the intermediate PKI backend to issue
// a new leaf certificate based on the provided CSR, with the issuing
// intermediate CA cert attached.
func ( v * VaultProvider ) Sign ( csr * x509 . CertificateRequest ) ( string , error ) {
2021-06-25 18:00:00 +00:00
connect . HackSANExtensionForCSR ( csr )
2018-06-13 08:40:03 +00:00
var pemBuf bytes . Buffer
if err := pem . Encode ( & pemBuf , & pem . Block { Type : "CERTIFICATE REQUEST" , Bytes : csr . Raw } ) ; err != nil {
return "" , err
}
// Use the leaf cert role to sign a new cert for this CSR.
2022-05-05 02:41:55 +00:00
response , err := v . writeNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath + "sign/" + VaultCALeafCertRole , map [ string ] interface { } {
2018-06-14 21:20:10 +00:00
"csr" : pemBuf . String ( ) ,
2018-07-26 00:51:45 +00:00
"ttl" : v . config . LeafCertTTL . String ( ) ,
2018-06-13 08:40:03 +00:00
} )
if err != nil {
2018-06-14 21:20:10 +00:00
return "" , fmt . Errorf ( "error issuing cert: %v" , err )
2018-06-13 08:40:03 +00:00
}
if response == nil || response . Data [ "certificate" ] == "" || response . Data [ "issuing_ca" ] == "" {
return "" , fmt . Errorf ( "certificate info returned from Vault was blank" )
}
cert , ok := response . Data [ "certificate" ] . ( string )
if ! ok {
return "" , fmt . Errorf ( "certificate was not a string" )
}
2022-06-01 21:53:52 +00:00
return lib . EnsureTrailingNewline ( cert ) , nil
2018-06-13 08:40:03 +00:00
}
2018-09-13 20:09:07 +00:00
// SignIntermediate returns a signed CA certificate with a path length constraint
// of 0 to ensure that the certificate cannot be used to generate further CA certs.
func ( v * VaultProvider ) SignIntermediate ( csr * x509 . CertificateRequest ) ( string , error ) {
2019-11-21 17:40:29 +00:00
err := validateSignIntermediate ( csr , v . spiffeID )
if err != nil {
return "" , err
}
2018-09-13 20:09:07 +00:00
var pemBuf bytes . Buffer
2019-11-21 17:40:29 +00:00
err = pem . Encode ( & pemBuf , & pem . Block { Type : "CERTIFICATE REQUEST" , Bytes : csr . Raw } )
2018-09-13 20:09:07 +00:00
if err != nil {
return "" , err
}
// Sign the CSR with the root backend.
2022-05-05 02:41:55 +00:00
data , err := v . writeNamespaced ( v . config . RootPKINamespace , v . config . RootPKIPath + "root/sign-intermediate" , map [ string ] interface { } {
2018-09-13 20:09:07 +00:00
"csr" : pemBuf . String ( ) ,
2019-09-23 17:04:40 +00:00
"use_csr_values" : true ,
2018-09-13 20:09:07 +00:00
"format" : "pem_bundle" ,
"max_path_length" : 0 ,
2020-09-30 19:31:21 +00:00
"ttl" : v . config . IntermediateCertTTL . String ( ) ,
2018-09-13 20:09:07 +00:00
} )
if err != nil {
return "" , err
}
if data == nil || data . Data [ "certificate" ] == "" {
return "" , fmt . Errorf ( "got empty value when generating intermediate certificate" )
}
intermediate , ok := data . Data [ "certificate" ] . ( string )
if ! ok {
return "" , fmt . Errorf ( "signed intermediate result is not a string" )
}
2022-06-01 21:53:52 +00:00
return lib . EnsureTrailingNewline ( intermediate ) , nil
2018-09-13 02:52:24 +00:00
}
2018-06-19 23:46:18 +00:00
// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain
// back to our active root.
func ( v * VaultProvider ) CrossSignCA ( cert * x509 . Certificate ) ( string , error ) {
2022-05-05 02:41:55 +00:00
rootPEM , err := v . getCA ( v . config . RootPKINamespace , v . config . RootPKIPath )
2020-10-09 11:35:42 +00:00
if err != nil {
2022-11-28 21:17:58 +00:00
return "" , fmt . Errorf ( "failed to get root CA: %w" , err )
2020-10-09 11:35:42 +00:00
}
rootCert , err := connect . ParseCert ( rootPEM )
if err != nil {
return "" , fmt . Errorf ( "error parsing root cert: %v" , err )
}
if rootCert . NotAfter . Before ( time . Now ( ) ) {
return "" , fmt . Errorf ( "root certificate is expired" )
}
2018-06-19 23:46:18 +00:00
var pemBuf bytes . Buffer
2020-10-09 11:35:42 +00:00
err = pem . Encode ( & pemBuf , & pem . Block { Type : "CERTIFICATE" , Bytes : cert . Raw } )
2018-06-15 23:25:53 +00:00
if err != nil {
2018-06-13 08:40:03 +00:00
return "" , err
}
2018-06-19 23:46:18 +00:00
// Have the root PKI backend sign this cert.
2022-05-05 02:41:55 +00:00
response , err := v . writeNamespaced ( v . config . RootPKINamespace , v . config . RootPKIPath + "root/sign-self-issued" , map [ string ] interface { } {
2018-06-19 23:46:18 +00:00
"certificate" : pemBuf . String ( ) ,
2018-06-15 23:25:53 +00:00
} )
if err != nil {
return "" , fmt . Errorf ( "error having Vault cross-sign cert: %v" , err )
}
if response == nil || response . Data [ "certificate" ] == "" {
return "" , fmt . Errorf ( "certificate info returned from Vault was blank" )
}
xcCert , ok := response . Data [ "certificate" ] . ( string )
if ! ok {
return "" , fmt . Errorf ( "certificate was not a string" )
}
2022-06-01 21:53:52 +00:00
return lib . EnsureTrailingNewline ( xcCert ) , nil
2018-06-13 08:40:03 +00:00
}
2019-11-11 21:36:22 +00:00
// SupportsCrossSigning implements Provider
2021-06-23 19:47:30 +00:00
func ( v * VaultProvider ) SupportsCrossSigning ( ) ( bool , error ) {
2019-11-11 21:36:22 +00:00
return true , nil
}
2018-06-13 08:40:03 +00:00
// Cleanup unmounts the configured intermediate PKI backend. It's fine to tear
// this down and recreate it on small config changes because the intermediate
// certs get bundled with the leaf certs, so there's no cost to the CA changing.
2021-01-15 18:20:27 +00:00
func ( v * VaultProvider ) Cleanup ( providerTypeChange bool , otherConfig map [ string ] interface { } ) error {
2020-09-10 13:12:48 +00:00
v . Stop ( )
2020-08-25 17:34:49 +00:00
2021-01-15 18:20:27 +00:00
if ! providerTypeChange {
2023-06-21 19:34:42 +00:00
newConfig , err := ParseVaultCAConfig ( otherConfig , v . isPrimary )
2021-01-15 18:20:27 +00:00
if err != nil {
return err
}
// if the intermeidate PKI path isn't changing we don't want to delete it as
// Cleanup is called after initializing the new provider
if newConfig . IntermediatePKIPath == v . config . IntermediatePKIPath {
return nil
}
}
2022-05-05 02:41:55 +00:00
err := v . unmountNamespaced ( v . config . IntermediatePKINamespace , v . config . IntermediatePKIPath )
2021-01-15 18:20:27 +00:00
switch err {
case ErrBackendNotMounted , ErrBackendNotInitialized :
// suppress these errors if we didn't finish initialization before
return nil
default :
return err
}
2018-06-13 08:40:03 +00:00
}
2020-09-10 13:12:48 +00:00
// Stop shuts down the token renew goroutine.
func ( v * VaultProvider ) Stop ( ) {
2022-03-28 14:58:16 +00:00
v . stopWatcher ( )
2020-09-10 13:12:48 +00:00
}
2022-05-05 02:41:55 +00:00
// We use raw path here
func ( v * VaultProvider ) mountNamespaced ( namespace , path string , mountInfo * vaultapi . MountInput ) error {
2023-09-13 19:33:02 +00:00
return v . client . WithNamespace ( namespace ) . Sys ( ) . Mount ( path , mountInfo )
2022-05-05 02:41:55 +00:00
}
2022-09-08 08:11:19 +00:00
func ( v * VaultProvider ) tuneMountNamespaced ( namespace , path string , mountConfig * vaultapi . MountConfigInput ) error {
2023-09-13 19:33:02 +00:00
return v . client . WithNamespace ( namespace ) . Sys ( ) . TuneMount ( path , * mountConfig )
2022-09-08 08:11:19 +00:00
}
2022-05-05 02:41:55 +00:00
func ( v * VaultProvider ) unmountNamespaced ( namespace , path string ) error {
2023-09-13 19:33:02 +00:00
return v . client . WithNamespace ( namespace ) . Sys ( ) . Unmount ( path )
2022-05-05 02:41:55 +00:00
}
func ( v * VaultProvider ) readNamespaced ( namespace string , resource string ) ( * vaultapi . Secret , error ) {
2023-09-13 19:33:02 +00:00
return v . client . WithNamespace ( namespace ) . Logical ( ) . Read ( resource )
2022-05-05 02:41:55 +00:00
}
func ( v * VaultProvider ) writeNamespaced ( namespace string , resource string , data map [ string ] interface { } ) ( * vaultapi . Secret , error ) {
2023-09-13 19:33:02 +00:00
return v . client . WithNamespace ( namespace ) . Logical ( ) . Write ( resource , data )
2022-05-05 02:41:55 +00:00
}
2023-09-13 19:33:02 +00:00
func ( v * VaultProvider ) deleteNamespaced ( namespace string , resource string ) ( * vaultapi . Secret , error ) {
return v . client . WithNamespace ( namespace ) . Logical ( ) . Delete ( resource )
2022-05-05 02:41:55 +00:00
}
2023-05-03 20:30:37 +00:00
// autotidyIssuers sets Vault's auto-tidy to remove expired issuers
// Returns a boolean on success for testing (as there is no post-facto way of
// checking if it is set). Logs at info level on failure to set and why,
// returning the log message for test purposes as well.
func ( v * VaultProvider ) autotidyIssuers ( path string ) ( bool , string ) {
s , err := v . client . Logical ( ) . Write ( path + "/config/auto-tidy" ,
map [ string ] interface { } {
"enabled" : true ,
"tidy_expired_issuers" : true ,
} )
var errStr string
if err != nil {
errStr = err . Error ( )
switch {
case strings . Contains ( errStr , "404" ) :
errStr = "vault versions < 1.12 don't support auto-tidy"
case strings . Contains ( errStr , "400" ) :
errStr = "vault versions < 1.13 don't support the tidy_expired_issuers field"
case strings . Contains ( errStr , "403" ) :
errStr = "permission denied on auto-tidy path in vault"
}
v . logger . Info ( "Unable to enable Vault's auto-tidy feature for expired issuers" , "reason" , errStr , "path" , path )
}
// return values for tests
tidySet := false
if s != nil {
if tei , ok := s . Data [ "tidy_expired_issuers" ] ; ok {
tidySet , _ = tei . ( bool )
}
}
return tidySet , errStr
}
2023-06-21 19:34:42 +00:00
func ParseVaultCAConfig ( raw map [ string ] interface { } , isPrimary bool ) ( * structs . VaultCAProviderConfig , error ) {
2018-07-16 09:46:10 +00:00
config := structs . VaultCAProviderConfig {
CommonCAProviderConfig : defaultCommonConfig ( ) ,
}
2018-06-13 08:40:03 +00:00
2018-06-20 21:33:02 +00:00
decodeConf := & mapstructure . DecoderConfig {
2021-11-18 20:15:28 +00:00
DecodeHook : mapstructure . ComposeDecodeHookFunc (
structs . ParseDurationFunc ( ) ,
decode . HookTranslateKeys ,
) ,
2018-06-20 21:33:02 +00:00
Result : & config ,
WeaklyTypedInput : true ,
}
decoder , err := mapstructure . NewDecoder ( decodeConf )
if err != nil {
return nil , err
}
if err := decoder . Decode ( raw ) ; err != nil {
2018-06-13 08:40:03 +00:00
return nil , fmt . Errorf ( "error decoding config: %s" , err )
}
2021-11-18 20:15:28 +00:00
if config . Token == "" && config . AuthMethod == nil {
return nil , fmt . Errorf ( "must provide a Vault token or configure a Vault auth method" )
}
if config . Token != "" && config . AuthMethod != nil {
return nil , fmt . Errorf ( "only one of Vault token or Vault auth method can be provided, but not both" )
2018-06-13 08:40:03 +00:00
}
2023-06-21 19:34:42 +00:00
if isPrimary && config . RootPKIPath == "" {
2018-06-13 08:40:03 +00:00
return nil , fmt . Errorf ( "must provide a valid path to a root PKI backend" )
}
2023-06-21 19:34:42 +00:00
if config . RootPKIPath != "" && ! strings . HasSuffix ( config . RootPKIPath , "/" ) {
2018-06-13 08:40:03 +00:00
config . RootPKIPath += "/"
}
if config . IntermediatePKIPath == "" {
return nil , fmt . Errorf ( "must provide a valid path for the intermediate PKI backend" )
}
if ! strings . HasSuffix ( config . IntermediatePKIPath , "/" ) {
config . IntermediatePKIPath += "/"
}
2018-07-20 23:04:04 +00:00
if err := config . CommonCAProviderConfig . Validate ( ) ; err != nil {
return nil , err
}
2018-06-13 08:40:03 +00:00
return & config , nil
}
2021-11-18 20:15:28 +00:00
func vaultLogin ( client * vaultapi . Client , authMethod * structs . VaultAuthMethod ) ( * vaultapi . Secret , error ) {
2023-01-18 19:53:04 +00:00
vaultAuth , err := configureVaultAuthMethod ( authMethod )
2021-11-18 20:15:28 +00:00
if err != nil {
return nil , err
}
2023-01-18 19:53:04 +00:00
resp , err := vaultAuth . Login ( context . Background ( ) , client )
2021-11-18 20:15:28 +00:00
if err != nil {
return nil , err
}
if resp == nil || resp . Auth == nil || resp . Auth . ClientToken == "" {
return nil , fmt . Errorf ( "login response did not return client token" )
}
return resp , nil
}
2023-03-02 22:05:40 +00:00
// Note the authMethod's parameters (Params) is populated from a freeform map
// in the configuration where they could hardcode values to be passed directly
// to the `auth/*/login` endpoint. Each auth method's authentication code
// needs to handle two cases:
// - The legacy case (which should be deprecated) where the user has
// hardcoded login values directly (eg. a `jwt` string)
// - The case where they use the configuration option used in the
// vault agent's auth methods.
2023-01-18 19:53:04 +00:00
func configureVaultAuthMethod ( authMethod * structs . VaultAuthMethod ) ( VaultAuthenticator , error ) {
2021-11-18 20:15:28 +00:00
if authMethod . MountPath == "" {
authMethod . MountPath = authMethod . Type
}
2023-01-18 19:53:04 +00:00
loginPath := ""
2021-11-18 20:15:28 +00:00
switch authMethod . Type {
2023-01-18 19:53:04 +00:00
case VaultAuthMethodTypeAWS :
return NewAWSAuthClient ( authMethod ) , nil
2023-03-01 00:07:33 +00:00
case VaultAuthMethodTypeAzure :
return NewAzureAuthClient ( authMethod )
2023-01-18 19:53:04 +00:00
case VaultAuthMethodTypeGCP :
return NewGCPAuthClient ( authMethod )
2023-03-02 20:33:06 +00:00
case VaultAuthMethodTypeJWT :
return NewJwtAuthClient ( authMethod )
2023-03-03 19:29:53 +00:00
case VaultAuthMethodTypeAppRole :
return NewAppRoleAuthClient ( authMethod )
2023-03-07 03:02:05 +00:00
case VaultAuthMethodTypeAliCloud :
return NewAliCloudAuthClient ( authMethod )
2021-11-18 20:15:28 +00:00
case VaultAuthMethodTypeKubernetes :
2023-03-02 22:05:40 +00:00
return NewK8sAuthClient ( authMethod )
2021-11-18 20:15:28 +00:00
// These auth methods require a username for the login API path.
case VaultAuthMethodTypeLDAP , VaultAuthMethodTypeUserpass , VaultAuthMethodTypeOkta , VaultAuthMethodTypeRadius :
// Get username from the params.
if username , ok := authMethod . Params [ "username" ] ; ok {
loginPath = fmt . Sprintf ( "auth/%s/login/%s" , authMethod . MountPath , username )
} else {
2023-01-18 19:53:04 +00:00
return nil , fmt . Errorf ( "failed to get 'username' from auth method params" )
2021-11-18 20:15:28 +00:00
}
2023-01-18 19:53:04 +00:00
return NewVaultAPIAuthClient ( authMethod , loginPath ) , nil
2021-11-18 20:15:28 +00:00
// This auth method requires a role for the login API path.
case VaultAuthMethodTypeOCI :
if role , ok := authMethod . Params [ "role" ] ; ok {
loginPath = fmt . Sprintf ( "auth/%s/login/%s" , authMethod . MountPath , role )
} else {
2023-01-18 19:53:04 +00:00
return nil , fmt . Errorf ( "failed to get 'role' from auth method params" )
2021-11-18 20:15:28 +00:00
}
2023-01-18 19:53:04 +00:00
return NewVaultAPIAuthClient ( authMethod , loginPath ) , nil
2021-11-18 20:15:28 +00:00
case VaultAuthMethodTypeToken :
2023-01-18 19:53:04 +00:00
return nil , fmt . Errorf ( "'token' auth method is not supported via auth method configuration; " +
2021-11-18 20:15:28 +00:00
"please provide the token with the 'token' parameter in the CA configuration" )
// The rest of the auth methods use auth/<auth method path> login API path.
2023-03-07 03:02:05 +00:00
case VaultAuthMethodTypeCloudFoundry ,
2021-11-18 20:15:28 +00:00
VaultAuthMethodTypeGitHub ,
VaultAuthMethodTypeKerberos ,
VaultAuthMethodTypeTLS :
2023-01-18 19:53:04 +00:00
return NewVaultAPIAuthClient ( authMethod , loginPath ) , nil
2021-11-18 20:15:28 +00:00
default :
2023-01-18 19:53:04 +00:00
return nil , fmt . Errorf ( "auth method %q is not supported" , authMethod . Type )
2021-11-18 20:15:28 +00:00
}
}