diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c876e533f76..a0e0f8226a39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ BUG FIXES: * secrets/gcp: Ensure that the IAM policy version is appropriately set after a roleset's bindings have changed. [[GH-9603](https://github.com/hashicorp/vault/pull/9603)] * replication (enterprise): Fix status API output incorrectly stating replication is in `idle` state. * core: Fix panic when printing over-long info fields at startup [[GH-9681](https://github.com/hashicorp/vault/pull/9681)] +* core: Seal migration using the new minimal-downtime strategy didn't work properly with performance standbys. [[GH-9690](https://github.com/hashicorp/vault/pull/9690)] ## 1.5.0 ### July 21st, 2020 @@ -122,10 +123,12 @@ BUG FIXES: BUG FIXES: +* auth/okta: fix bug introduced in 1.4.0 that broke handling of external groups with > 200 members [[GH-9580](https://github.com/hashicorp/vault/pull/9580)] * seal/awskms: fix AWS KMS auto-unseal when AWS_ROLE_SESSION_NAME not set [[GH-9416](https://github.com/hashicorp/vault/pull/9416)] * secrets/aws: Fix possible issue creating access keys when using Performance Standbys [[GH-9606](https://github.com/hashicorp/vault/pull/9606)] IMPROVEMENTS: +* auth/aws: Retry on transient failures during AWS IAM auth login attempts [[GH-8727](https://github.com/hashicorp/vault/pull/8727)] * ui: Add transit key algorithms aes128-gcm96, ecdsa-p384, ecdsa-p521 to the UI. [[GH-9070](https://github.com/hashicorp/vault/pull/9070)] & [[GH-9520](https://github.com/hashicorp/vault/pull/9520)] ## 1.4.3 @@ -137,6 +140,7 @@ IMPROVEMENTS: * auth/kerberos: Support identities without userPrincipalName [[GH-44](https://github.com/hashicorp/vault-plugin-auth-kerberos/issues/44)] * core: Add the Go version used to build a Vault binary to the server message output. [[GH-9078](https://github.com/hashicorp/vault/pull/9078)] * secrets/database: Add static role rotation for MongoDB Atlas database plugin [[GH-9311](https://github.com/hashicorp/vault/pull/9311)] +* physical/mysql: Require TLS or plaintext flagging in MySQL configuration [[GH-9012](https://github.com/hashicorp/vault/pull/9012)] * ui: Link to the Vault Changelog in the UI footer [[GH-9216](https://github.com/hashicorp/vault/pull/9216)] BUG FIXES: diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 8a96b81c2c4d..09642906733f 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -2797,7 +2797,8 @@ func TestBackend_AllowedDomainsTemplate(t *testing.T) { // Write role PKI. _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{ - "allowed_domains": []string{"foobar.com", "zipzap.com", "{{identity.entity.aliases." + userpassAccessor + ".name}}"}, + "allowed_domains": []string{"foobar.com", "zipzap.com", "{{identity.entity.aliases." + userpassAccessor + ".name}}", + "foo.{{identity.entity.aliases." + userpassAccessor + ".name}}.example.com"}, "allowed_domains_template": true, "allow_bare_domains": true, }) @@ -2824,6 +2825,12 @@ func TestBackend_AllowedDomainsTemplate(t *testing.T) { t.Fatal("expected error") } + // Issue certificate for foo.userpassname.domain. + _, err = client.Logical().Write("pki/issue/test", map[string]interface{}{"common_name": "foo.userpassname.example.com"}) + if err != nil { + t.Fatal("expected error") + } + // Set allowed_domains_template to false. _, err = client.Logical().Write("pki/roles/test", map[string]interface{}{ "allowed_domains_template": false, diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index 608dbf014c22..2a2c9b9b5639 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -315,8 +315,8 @@ func validateNames(b *backend, data *inputBundle, names []string) string { } if data.role.AllowedDomainsTemplate { - matched, _ := regexp.MatchString(`^{{.+?}}$`, currDomain) - if matched && data.req.EntityID != "" { + isTemplate, _ := framework.ValidateIdentityTemplate(currDomain) + if isTemplate && data.req.EntityID != "" { tmpCurrDomain, err := framework.PopulateIdentityTemplate(currDomain, data.req.EntityID, b.System()) if err != nil { continue diff --git a/command/agent.go b/command/agent.go index 793f26a31694..018d01e41243 100644 --- a/command/agent.go +++ b/command/agent.go @@ -349,6 +349,7 @@ func (c *AgentCommand) Run(args []string) int { Client: client, WrapTTL: sc.WrapTTL, DHType: sc.DHType, + DeriveKey: sc.DeriveKey, DHPath: sc.DHPath, AAD: sc.AAD, } diff --git a/command/agent/cert_with_name_end_to_end_test.go b/command/agent/cert_with_name_end_to_end_test.go index 0500dc8997c9..fea2bce4a6e2 100644 --- a/command/agent/cert_with_name_end_to_end_test.go +++ b/command/agent/cert_with_name_end_to_end_test.go @@ -11,6 +11,9 @@ import ( hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/sdk/helper/jsonutil" + "github.com/hashicorp/vault/sdk/helper/logging" + "github.com/hashicorp/vault/sdk/logical" vaultcert "github.com/hashicorp/vault/builtin/credential/cert" "github.com/hashicorp/vault/command/agent/auth" agentcert "github.com/hashicorp/vault/command/agent/auth/cert" @@ -18,9 +21,6 @@ import ( "github.com/hashicorp/vault/command/agent/sink/file" "github.com/hashicorp/vault/helper/dhutil" vaulthttp "github.com/hashicorp/vault/http" - "github.com/hashicorp/vault/sdk/helper/jsonutil" - "github.com/hashicorp/vault/sdk/helper/logging" - "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault" ) @@ -137,6 +137,7 @@ func testCertWithNameEndToEnd(t *testing.T, ahWrapping bool) { AAD: "foobar", DHType: "curve25519", DHPath: dhpath, + DeriveKey: true, Config: map[string]interface{}{ "path": out, }, @@ -186,14 +187,17 @@ func testCertWithNameEndToEnd(t *testing.T, ahWrapping bool) { continue } - aesKey, err := dhutil.GenerateSharedKey(pri, resp.Curve25519PublicKey) + shared, err := dhutil.GenerateSharedSecret(pri, resp.Curve25519PublicKey) + if err != nil { + t.Fatal(err) + } + aesKey, err := dhutil.DeriveSharedKey(shared, pub, resp.Curve25519PublicKey) if err != nil { t.Fatal(err) } if len(aesKey) == 0 { t.Fatal("got empty aes key") } - val, err = dhutil.DecryptAES(aesKey, resp.EncryptedPayload, resp.Nonce, []byte("foobar")) if err != nil { t.Fatalf("error: %v\nresp: %v", err, string(val)) diff --git a/command/agent/cert_with_no_name_end_to_end_test.go b/command/agent/cert_with_no_name_end_to_end_test.go index d6394de37e85..da62ba5d3faa 100644 --- a/command/agent/cert_with_no_name_end_to_end_test.go +++ b/command/agent/cert_with_no_name_end_to_end_test.go @@ -183,7 +183,7 @@ func testCertWithNoNAmeEndToEnd(t *testing.T, ahWrapping bool) { continue } - aesKey, err := dhutil.GenerateSharedKey(pri, resp.Curve25519PublicKey) + aesKey, err := dhutil.GenerateSharedSecret(pri, resp.Curve25519PublicKey) if err != nil { t.Fatal(err) } diff --git a/command/agent/config/config.go b/command/agent/config/config.go index 2036eb8067e5..489cbb746fb4 100644 --- a/command/agent/config/config.go +++ b/command/agent/config/config.go @@ -75,6 +75,7 @@ type Sink struct { WrapTTLRaw interface{} `hcl:"wrap_ttl"` WrapTTL time.Duration `hcl:"-"` DHType string `hcl:"dh_type"` + DeriveKey bool `hcl:"derive_key"` DHPath string `hcl:"dh_path"` AAD string `hcl:"aad"` AADEnvVar string `hcl:"aad_env_var"` @@ -395,6 +396,9 @@ func parseSinks(result *Config, list *ast.ObjectList) error { if s.AAD != "" { return multierror.Prefix(errors.New("specifying AAD data without 'dh_type' does not make sense"), fmt.Sprintf("sink.%s", s.Type)) } + if s.DeriveKey { + return multierror.Prefix(errors.New("specifying 'derive_key' data without 'dh_type' does not make sense"), fmt.Sprintf("sink.%s", s.Type)) + } case s.DHPath != "" && s.DHType != "": default: return multierror.Prefix(errors.New("'dh_type' and 'dh_path' must be specified together"), fmt.Sprintf("sink.%s", s.Type)) diff --git a/command/agent/config/config_test.go b/command/agent/config/config_test.go index b5c1d2e0c728..a316a73eb916 100644 --- a/command/agent/config/config_test.go +++ b/command/agent/config/config_test.go @@ -143,6 +143,7 @@ func TestLoadConfigFile(t *testing.T) { DHType: "curve25519", DHPath: "/tmp/file-foo-dhpath2", AAD: "aad", + DeriveKey: true, Config: map[string]interface{}{ "path": "/tmp/file-bar", }, diff --git a/command/agent/config/test-fixtures/config-embedded-type.hcl b/command/agent/config/test-fixtures/config-embedded-type.hcl index f17336078a65..919bfd907757 100644 --- a/command/agent/config/test-fixtures/config-embedded-type.hcl +++ b/command/agent/config/test-fixtures/config-embedded-type.hcl @@ -23,6 +23,7 @@ auto_auth { aad_env_var = "TEST_AAD_ENV" dh_type = "curve25519" dh_path = "/tmp/file-foo-dhpath2" + derive_key = true config = { path = "/tmp/file-bar" } diff --git a/command/agent/config/test-fixtures/config.hcl b/command/agent/config/test-fixtures/config.hcl index 096190d04d84..b02170736a8e 100644 --- a/command/agent/config/test-fixtures/config.hcl +++ b/command/agent/config/test-fixtures/config.hcl @@ -25,6 +25,7 @@ auto_auth { aad_env_var = "TEST_AAD_ENV" dh_type = "curve25519" dh_path = "/tmp/file-foo-dhpath2" + derive_key = true config = { path = "/tmp/file-bar" } diff --git a/command/agent/jwt_end_to_end_test.go b/command/agent/jwt_end_to_end_test.go index f07a3f98442d..0cda243725cb 100644 --- a/command/agent/jwt_end_to_end_test.go +++ b/command/agent/jwt_end_to_end_test.go @@ -158,6 +158,7 @@ func testJWTEndToEnd(t *testing.T, ahWrapping bool) { AAD: "foobar", DHType: "curve25519", DHPath: dhpath, + DeriveKey: true, Config: map[string]interface{}{ "path": out, }, @@ -231,7 +232,11 @@ func testJWTEndToEnd(t *testing.T, ahWrapping bool) { continue } - aesKey, err := dhutil.GenerateSharedKey(pri, resp.Curve25519PublicKey) + shared, err := dhutil.GenerateSharedSecret(pri, resp.Curve25519PublicKey) + if err != nil { + t.Fatal(err) + } + aesKey, err := dhutil.DeriveSharedKey(shared, pub, resp.Curve25519PublicKey) if err != nil { t.Fatal(err) } diff --git a/command/agent/sink/sink.go b/command/agent/sink/sink.go index acc93b773b94..f8eb921b387e 100644 --- a/command/agent/sink/sink.go +++ b/command/agent/sink/sink.go @@ -32,6 +32,7 @@ type SinkConfig struct { WrapTTL time.Duration DHType string DHPath string + DeriveKey bool AAD string cachedRemotePubKey []byte cachedPubKey []byte @@ -205,7 +206,16 @@ func (s *SinkConfig) encryptToken(token string) (string, error) { resp.Curve25519PublicKey = s.cachedPubKey } - aesKey, err = dhutil.GenerateSharedKey(s.cachedPriKey, s.cachedRemotePubKey) + secret, err := dhutil.GenerateSharedSecret(s.cachedPriKey, s.cachedRemotePubKey) + if err != nil { + return "", errwrap.Wrapf("error calculating shared key: {{err}}", err) + } + if s.DeriveKey { + aesKey, err = dhutil.DeriveSharedKey(secret, s.cachedPubKey, s.cachedRemotePubKey) + } else { + aesKey = secret + } + if err != nil { return "", errwrap.Wrapf("error deriving shared key: {{err}}", err) } diff --git a/go.mod b/go.mod index 0560ba3b35e9..af3a8fcddd4c 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/golang/protobuf v1.4.2 github.com/google/go-github v17.0.0+incompatible github.com/google/go-metrics-stackdriver v0.2.0 - github.com/hashicorp/consul-template v0.25.0 + github.com/hashicorp/consul-template v0.25.1 github.com/hashicorp/consul/api v1.4.0 github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/go-bindata v3.0.8-0.20180209072458-bf7910af8997+incompatible diff --git a/go.sum b/go.sum index e03783e62a6f..e31e08f58208 100644 --- a/go.sum +++ b/go.sum @@ -431,8 +431,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul-template v0.25.0 h1:wsnv4jSqBIVzlg6U0wNg+ePzfrsF3Vi9MqIqDEUrg9U= -github.com/hashicorp/consul-template v0.25.0/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0= +github.com/hashicorp/consul-template v0.25.1 h1:+D2s8eyRqWyX7GPNxeUi8tsyh8pRn3J6k8giEchPfKQ= +github.com/hashicorp/consul-template v0.25.1/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0= github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY= github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= diff --git a/helper/dhutil/dhutil.go b/helper/dhutil/dhutil.go index 86e23298e037..23257cf91b9a 100644 --- a/helper/dhutil/dhutil.go +++ b/helper/dhutil/dhutil.go @@ -1,11 +1,15 @@ package dhutil import ( + "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" + "encoding/hex" "errors" "fmt" + "golang.org/x/crypto/hkdf" "io" "golang.org/x/crypto/curve25519" @@ -34,9 +38,9 @@ func GeneratePublicPrivateKey() ([]byte, []byte, error) { return public[:], scalar[:], nil } -// generateSharedKey uses the private key and the other party's public key to +// GenerateSharedSecret uses the private key and the other party's public key to // generate the shared secret. -func GenerateSharedKey(ourPrivate, theirPublic []byte) ([]byte, error) { +func GenerateSharedSecret(ourPrivate, theirPublic []byte) ([]byte, error) { if len(ourPrivate) != 32 { return nil, fmt.Errorf("invalid private key length: %d", len(ourPrivate)) } @@ -44,13 +48,50 @@ func GenerateSharedKey(ourPrivate, theirPublic []byte) ([]byte, error) { return nil, fmt.Errorf("invalid public key length: %d", len(theirPublic)) } - var scalar, pub, secret [32]byte - copy(scalar[:], ourPrivate) - copy(pub[:], theirPublic) + return curve25519.X25519(ourPrivate, theirPublic) +} + +// DeriveSharedKey uses HKDF to derive a key from a shared secret and public keys +func DeriveSharedKey(secret, ourPublic, theirPublic []byte) ([]byte, error) { + // Derive the final key from the HKDF of the secret and public keys. + +/* + Internally, HKDF hashes the secret and two public keys. If Alice and Bob are doing DH key exchange, Alice calculates: + + HKDF(secret, A, B) since ourPublic is A. + + Bob calculates HKDF(secret, B, A), since Bob's ours is B. That produces a different value. Now we only care + that both public keys participate in the derivation, so simply sorting them so they are in a consistent + numerical order (either one would do) arrives at an agreed value. +*/ + + var pub1 []byte + var pub2 []byte + switch bytes.Compare(ourPublic, theirPublic) { + case 0: + return nil, errors.New("same public key supplied for both participants") + case -1: + pub1 = ourPublic + pub2 = theirPublic + case 1: + pub1 = theirPublic + pub2 = ourPublic + } + + kio := hkdf.New(sha256.New, secret, pub1, pub2) - curve25519.ScalarMult(&secret, &scalar, &pub) + var key [32]byte + n, err := io.ReadFull(kio, key[:]) + if err != nil { + // Don't return the key along with the error to prevent misuse + return nil, err + } + if n != 32 { + return nil, errors.New("short read from hkdf") + } + fmt.Printf("Key: %s\n", hex.EncodeToString(key[:])) - return secret[:], nil + return key[:], nil } // Use AES256-GCM to encrypt some plaintext with a provided key. The returned values are diff --git a/helper/dhutil/dhutil_test.go b/helper/dhutil/dhutil_test.go new file mode 100644 index 000000000000..46e90196d15e --- /dev/null +++ b/helper/dhutil/dhutil_test.go @@ -0,0 +1 @@ +package dhutil diff --git a/plugins/database/mysql/connection_producer.go b/plugins/database/mysql/connection_producer.go index 4e34372005cd..bf4b6581aa29 100644 --- a/plugins/database/mysql/connection_producer.go +++ b/plugins/database/mysql/connection_producer.go @@ -218,7 +218,9 @@ func (c *mySQLConnectionProducer) addTLStoDSN() (connURL string, err error) { return "", fmt.Errorf("unable to parse connectionURL: %s", err) } - config.TLSConfig = c.tlsConfigName + if len(c.tlsConfigName) > 0 { + config.TLSConfig = c.tlsConfigName + } connURL = config.FormatDSN() diff --git a/plugins/database/mysql/connection_producer_test.go b/plugins/database/mysql/connection_producer_test.go index 7da5a05200d3..4213d57c4ff9 100644 --- a/plugins/database/mysql/connection_producer_test.go +++ b/plugins/database/mysql/connection_producer_test.go @@ -45,6 +45,11 @@ func Test_addTLStoDSN(t *testing.T) { tlsConfigName: "tlsTest101", expectedResult: "user:pa?ssword?@tcp(localhost:3306)/test?tls=tlsTest101&foo=bar", }, + "tls, valid tls parameter in query string": { + rootUrl: "user:password@tcp(localhost:3306)/test?tls=true", + tlsConfigName: "", + expectedResult: "user:password@tcp(localhost:3306)/test?tls=true", + }, } for name, test := range tests { diff --git a/vendor/github.com/hashicorp/consul-template/config/env.go b/vendor/github.com/hashicorp/consul-template/config/env.go index a9a4b1ebe848..0d93aef6aca8 100644 --- a/vendor/github.com/hashicorp/consul-template/config/env.go +++ b/vendor/github.com/hashicorp/consul-template/config/env.go @@ -11,15 +11,21 @@ import ( // variable filtering. You should not use this directly and it is only public // for mapstructure's decoding. type EnvConfig struct { - // BlacklistEnv specifies a list of environment variables to explicitly + // Denylist specifies a list of environment variables to explicitly // exclude from the list of environment variables populated to the child. - // If both WhitelistEnv and BlacklistEnv are provided, BlacklistEnv takes - // precedence over the values in WhitelistEnv. - Blacklist []string `mapstructure:"blacklist"` + // If both Allowlist and Denylist are provided, Denylist takes + // precedence over the values in Allowlist. + Denylist []string `mapstructure:"denylist"` + + // DenylistDeprecated is the backward compatible option for Denylist for + // configuration supported by v0.25.0 and older. This should not be used + // directly, use Denylist instead. Values from this are combined to + // Denylist in Finalize(). + DenylistDeprecated []string `mapstructure:"blacklist" json:"-"` // CustomEnv specifies custom environment variables to pass to the child // process. These are provided programmatically, override any environment - // variables of the same name, are ignored from whitelist/blacklist, and + // variables of the same name, are ignored from allowlist/denylist, and // are still included even if PristineEnv is set to true. Custom []string `mapstructure:"custom"` @@ -27,9 +33,15 @@ type EnvConfig struct { // environment. Pristine *bool `mapstructure:"pristine"` - // WhitelistEnv specifies a list of environment variables to exclusively + // Allowlist specifies a list of environment variables to exclusively // include in the list of environment variables populated to the child. - Whitelist []string `mapstructure:"whitelist"` + Allowlist []string `mapstructure:"allowlist"` + + // AllowlistDeprecated is the backward compatible option for Allowlist for + // configuration supported by v0.25.0 and older. This should not be used + // directly, use Allowlist instead. Values from this are combined to + // Allowlist in Finalize(). + AllowlistDeprecated []string `mapstructure:"whitelist" json:"-"` } // DefaultEnvConfig returns a configuration that is populated with the @@ -46,8 +58,12 @@ func (c *EnvConfig) Copy() *EnvConfig { var o EnvConfig - if c.Blacklist != nil { - o.Blacklist = append([]string{}, c.Blacklist...) + if c.Denylist != nil { + o.Denylist = append([]string{}, c.Denylist...) + } + + if c.DenylistDeprecated != nil { + o.DenylistDeprecated = append([]string{}, c.DenylistDeprecated...) } if c.Custom != nil { @@ -56,8 +72,12 @@ func (c *EnvConfig) Copy() *EnvConfig { o.Pristine = c.Pristine - if c.Whitelist != nil { - o.Whitelist = append([]string{}, c.Whitelist...) + if c.Allowlist != nil { + o.Allowlist = append([]string{}, c.Allowlist...) + } + + if c.AllowlistDeprecated != nil { + o.AllowlistDeprecated = append([]string{}, c.AllowlistDeprecated...) } return &o @@ -81,8 +101,12 @@ func (c *EnvConfig) Merge(o *EnvConfig) *EnvConfig { r := c.Copy() - if o.Blacklist != nil { - r.Blacklist = append(r.Blacklist, o.Blacklist...) + if o.Denylist != nil { + r.Denylist = append(r.Denylist, o.Denylist...) + } + + if o.DenylistDeprecated != nil { + r.DenylistDeprecated = append(r.DenylistDeprecated, o.DenylistDeprecated...) } if o.Custom != nil { @@ -93,16 +117,20 @@ func (c *EnvConfig) Merge(o *EnvConfig) *EnvConfig { r.Pristine = o.Pristine } - if o.Whitelist != nil { - r.Whitelist = append(r.Whitelist, o.Whitelist...) + if o.Allowlist != nil { + r.Allowlist = append(r.Allowlist, o.Allowlist...) + } + + if o.AllowlistDeprecated != nil { + r.AllowlistDeprecated = append(r.AllowlistDeprecated, o.AllowlistDeprecated...) } return r } // Env calculates and returns the finalized environment for this exec -// configuration. It takes into account pristine, custom environment, whitelist, -// and blacklist values. +// configuration. It takes into account pristine, custom environment, allowlist, +// and denylist values. func (c *EnvConfig) Env() []string { // In pristine mode, just return the custom environment. If the user did not // specify a custom environment, just return the empty slice to force an @@ -136,22 +164,30 @@ func (c *EnvConfig) Env() []string { return false } - // Pull out any envvars that match the whitelist. - if len(c.Whitelist) > 0 { + // Pull out any envvars that match the allowlist. + // Combining lists on each reference may be slightly inefficient but this + // allows for out of order method calls, not requiring the config to be + // finalized first. + allowlist := combineLists(c.Allowlist, c.AllowlistDeprecated) + if len(allowlist) > 0 { newKeys := make([]string, 0, len(keys)) for _, k := range keys { - if anyGlobMatch(k, c.Whitelist) { + if anyGlobMatch(k, allowlist) { newKeys = append(newKeys, k) } } keys = newKeys } - // Remove any envvars that match the blacklist. - if len(c.Blacklist) > 0 { + // Remove any envvars that match the denylist. + // Combining lists on each reference may be slightly inefficient but this + // allows for out of order method calls, not requiring the config to be + // finalized first. + denylist := combineLists(c.Denylist, c.DenylistDeprecated) + if len(denylist) > 0 { newKeys := make([]string, 0, len(keys)) for _, k := range keys { - if !anyGlobMatch(k, c.Blacklist) { + if !anyGlobMatch(k, denylist) { newKeys = append(newKeys, k) } } @@ -172,8 +208,11 @@ func (c *EnvConfig) Env() []string { // Finalize ensures there no nil pointers. func (c *EnvConfig) Finalize() { - if c.Blacklist == nil { - c.Blacklist = []string{} + if c.Denylist == nil && c.DenylistDeprecated == nil { + c.Denylist = []string{} + c.DenylistDeprecated = []string{} + } else { + c.Denylist = combineLists(c.Denylist, c.DenylistDeprecated) } if c.Custom == nil { @@ -184,8 +223,11 @@ func (c *EnvConfig) Finalize() { c.Pristine = Bool(false) } - if c.Whitelist == nil { - c.Whitelist = []string{} + if c.Allowlist == nil && c.AllowlistDeprecated == nil { + c.Allowlist = []string{} + c.AllowlistDeprecated = []string{} + } else { + c.Allowlist = combineLists(c.Allowlist, c.AllowlistDeprecated) } } @@ -196,14 +238,33 @@ func (c *EnvConfig) GoString() string { } return fmt.Sprintf("&EnvConfig{"+ - "Blacklist:%v, "+ + "Denylist:%v, "+ "Custom:%v, "+ "Pristine:%s, "+ - "Whitelist:%v"+ + "Allowlist:%v"+ "}", - c.Blacklist, + combineLists(c.Denylist, c.DenylistDeprecated), c.Custom, BoolGoString(c.Pristine), - c.Whitelist, + combineLists(c.Allowlist, c.AllowlistDeprecated), ) } + +// combineLists makes a new list that combines 2 lists by adding values from +// the second list without removing any duplicates from the first. +func combineLists(a, b []string) []string { + combined := make([]string, len(a), len(a)+len(b)) + m := make(map[string]bool) + for i, v := range a { + m[v] = true + combined[i] = v + } + + for _, v := range b { + if !m[v] { + combined = append(combined, v) + } + } + + return combined +} diff --git a/vendor/github.com/hashicorp/consul-template/config/template.go b/vendor/github.com/hashicorp/consul-template/config/template.go index 4f69bfb6033f..a42e4d87fcac 100644 --- a/vendor/github.com/hashicorp/consul-template/config/template.go +++ b/vendor/github.com/hashicorp/consul-template/config/template.go @@ -76,9 +76,15 @@ type TemplateConfig struct { LeftDelim *string `mapstructure:"left_delimiter"` RightDelim *string `mapstructure:"right_delimiter"` - // FunctionBlacklist is a list of functions that this template is not + // FunctionDenylist is a list of functions that this template is not // permitted to run. - FunctionBlacklist []string `mapstructure:"function_blacklist"` + FunctionDenylist []string `mapstructure:"function_denylist"` + + // FunctionDenylistDeprecated is the backward compatible option for + // FunctionDenylist for configuration supported by v0.25.0 and older. This + // should not be used directly, use FunctionDenylist instead. Values from + // this are combined to FunctionDenylist in Finalize(). + FunctionDenylistDeprecated []string `mapstructure:"function_blacklist" json:"-"` // SandboxPath adds a prefix to any path provided to the `file` function // and causes an error if a relative path tries to traverse outside that @@ -132,9 +138,14 @@ func (c *TemplateConfig) Copy() *TemplateConfig { o.LeftDelim = c.LeftDelim o.RightDelim = c.RightDelim - for _, fun := range c.FunctionBlacklist { - o.FunctionBlacklist = append(o.FunctionBlacklist, fun) + for _, fun := range c.FunctionDenylist { + o.FunctionDenylist = append(o.FunctionDenylist, fun) + } + + for _, fun := range c.FunctionDenylistDeprecated { + o.FunctionDenylistDeprecated = append(o.FunctionDenylistDeprecated, fun) } + o.SandboxPath = c.SandboxPath return &o @@ -210,9 +221,14 @@ func (c *TemplateConfig) Merge(o *TemplateConfig) *TemplateConfig { r.RightDelim = o.RightDelim } - for _, fun := range o.FunctionBlacklist { - r.FunctionBlacklist = append(r.FunctionBlacklist, fun) + for _, fun := range o.FunctionDenylist { + r.FunctionDenylist = append(r.FunctionDenylist, fun) } + + for _, fun := range o.FunctionDenylistDeprecated { + r.FunctionDenylistDeprecated = append(r.FunctionDenylistDeprecated, fun) + } + if o.SandboxPath != nil { r.SandboxPath = o.SandboxPath } @@ -288,6 +304,13 @@ func (c *TemplateConfig) Finalize() { if c.SandboxPath == nil { c.SandboxPath = String("") } + + if c.FunctionDenylist == nil && c.FunctionDenylistDeprecated == nil { + c.FunctionDenylist = []string{} + c.FunctionDenylistDeprecated = []string{} + } else { + c.FunctionDenylist = combineLists(c.FunctionDenylist, c.FunctionDenylistDeprecated) + } } // GoString defines the printable version of this struct. @@ -309,8 +332,8 @@ func (c *TemplateConfig) GoString() string { "Source:%s, "+ "Wait:%#v, "+ "LeftDelim:%s, "+ - "RightDelim:%s"+ - "FunctionBlacklist:%s"+ + "RightDelim:%s, "+ + "FunctionDenylist:%s, "+ "SandboxPath:%s"+ "}", BoolGoString(c.Backup), @@ -326,7 +349,7 @@ func (c *TemplateConfig) GoString() string { c.Wait, StringGoString(c.LeftDelim), StringGoString(c.RightDelim), - c.FunctionBlacklist, + combineLists(c.FunctionDenylist, c.FunctionDenylistDeprecated), StringGoString(c.SandboxPath), ) } diff --git a/vendor/github.com/hashicorp/consul-template/dependency/vault_common.go b/vendor/github.com/hashicorp/consul-template/dependency/vault_common.go index f21305fcbecd..e579f8958b79 100644 --- a/vendor/github.com/hashicorp/consul-template/dependency/vault_common.go +++ b/vendor/github.com/hashicorp/consul-template/dependency/vault_common.go @@ -3,14 +3,9 @@ package dependency import ( "log" "math/rand" - "path" - "strings" "time" - "crypto/x509" "encoding/json" - "encoding/pem" - "github.com/hashicorp/vault/api" ) @@ -106,22 +101,6 @@ func renewSecret(clients *ClientSet, d renewer) error { } } -// durationFrom cert gets the duration of validity from cert data and -// returns that value as an integer number of seconds -func durationFromCert(certData string) int { - block, _ := pem.Decode([]byte(certData)) - if block == nil { - return -1 - } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - log.Printf("[WARN] Unable to parse certificate data: %s", err) - return -1 - } - - return int(cert.NotAfter.Sub(cert.NotBefore).Seconds()) -} - // leaseCheckWait accepts a secret and returns the recommended amount of // time to sleep. func leaseCheckWait(s *Secret) time.Duration { @@ -132,12 +111,11 @@ func leaseCheckWait(s *Secret) time.Duration { } // Handle if this is a certificate with no lease - if certInterface, ok := s.Data["certificate"]; ok && s.LeaseID == "" { - if certData, ok := certInterface.(string); ok { - newDuration := durationFromCert(certData) - if newDuration > 0 { - log.Printf("[DEBUG] Found certificate and set lease duration to %d seconds", newDuration) - base = newDuration + if _, ok := s.Data["certificate"]; ok && s.LeaseID == "" { + if expInterface, ok := s.Data["expiration"]; ok { + if expData, err := expInterface.(json.Number).Int64(); err == nil { + base = int(expData - time.Now().Unix()) + log.Printf("[DEBUG] Found certificate and set lease duration to %d seconds", base) } } } @@ -349,21 +327,3 @@ func isKVv2(client *api.Client, path string) (string, bool, error) { return mountPath, false, nil } - -func addPrefixToVKVPath(p, mountPath, apiPrefix string) string { - switch { - case p == mountPath, p == strings.TrimSuffix(mountPath, "/"): - return path.Join(mountPath, apiPrefix) - default: - p = strings.TrimPrefix(p, mountPath) - // Don't add /data/ to the path if it's been added manually. - apiPathPrefix := apiPrefix - if !strings.HasSuffix(apiPrefix, "/") { - apiPathPrefix += "/" - } - if strings.HasPrefix(p, apiPathPrefix) { - return path.Join(mountPath, p) - } - return path.Join(mountPath, apiPrefix, p) - } -} diff --git a/vendor/github.com/hashicorp/consul-template/dependency/vault_read.go b/vendor/github.com/hashicorp/consul-template/dependency/vault_read.go index 8f24f42db21a..ab71e9eb03eb 100644 --- a/vendor/github.com/hashicorp/consul-template/dependency/vault_read.go +++ b/vendor/github.com/hashicorp/consul-template/dependency/vault_read.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net/url" + "path" "strings" "time" @@ -145,7 +146,7 @@ func (d *VaultReadQuery) readSecret(clients *ClientSet, opts *QueryOptions) (*ap isKVv2 = false d.secretPath = d.rawPath } else if isKVv2 { - d.secretPath = addPrefixToVKVPath(d.rawPath, mountPath, "data") + d.secretPath = shimKVv2Path(d.rawPath, mountPath) } else { d.secretPath = d.rawPath } @@ -176,3 +177,21 @@ func deletedKVv2(s *api.Secret) bool { } return false } + +// shimKVv2Path aligns the supported legacy path to KV v2 specs by inserting +// /data/ into the path for reading secrets. Paths for metadata are not modified. +func shimKVv2Path(rawPath, mountPath string) string { + switch { + case rawPath == mountPath, rawPath == strings.TrimSuffix(mountPath, "/"): + return path.Join(mountPath, "data") + default: + p := strings.TrimPrefix(rawPath, mountPath) + + // Only add /data/ prefix to the path if neither /data/ or /metadata/ are + // present. + if strings.HasPrefix(p, "data/") || strings.HasPrefix(p, "metadata/") { + return rawPath + } + return path.Join(mountPath, "data", p) + } +} diff --git a/vendor/github.com/hashicorp/consul-template/dependency/vault_token.go b/vendor/github.com/hashicorp/consul-template/dependency/vault_token.go index 93ad5984ac6d..6e962906ce98 100644 --- a/vendor/github.com/hashicorp/consul-template/dependency/vault_token.go +++ b/vendor/github.com/hashicorp/consul-template/dependency/vault_token.go @@ -47,7 +47,6 @@ func (d *VaultTokenQuery) Fetch(clients *ClientSet, opts *QueryOptions, if err != nil { return nil, nil, errors.Wrap(err, d.String()) } - renewSecret(clients, d) } return nil, nil, ErrLeaseExpired diff --git a/vendor/github.com/hashicorp/consul-template/manager/runner.go b/vendor/github.com/hashicorp/consul-template/manager/runner.go index 9da7a9d342a4..f14a4c95dcba 100644 --- a/vendor/github.com/hashicorp/consul-template/manager/runner.go +++ b/vendor/github.com/hashicorp/consul-template/manager/runner.go @@ -102,6 +102,7 @@ type Runner struct { // template and command runtime with. These environment variables will be // available in both the command's environment as well as the template's // environment. + // NOTE this is only used when CT is being used as a library. Env map[string]string // stopLock is the lock around checking if the runner can be stopped @@ -883,13 +884,13 @@ func (r *Runner) init() error { } tmpl, err := template.NewTemplate(&template.NewTemplateInput{ - Source: config.StringVal(ctmpl.Source), - Contents: config.StringVal(ctmpl.Contents), - ErrMissingKey: config.BoolVal(ctmpl.ErrMissingKey), - LeftDelim: leftDelim, - RightDelim: rightDelim, - FunctionBlacklist: ctmpl.FunctionBlacklist, - SandboxPath: config.StringVal(ctmpl.SandboxPath), + Source: config.StringVal(ctmpl.Source), + Contents: config.StringVal(ctmpl.Contents), + ErrMissingKey: config.BoolVal(ctmpl.ErrMissingKey), + LeftDelim: leftDelim, + RightDelim: rightDelim, + FunctionDenylist: ctmpl.FunctionDenylist, + SandboxPath: config.StringVal(ctmpl.SandboxPath), }) if err != nil { return err diff --git a/vendor/github.com/hashicorp/consul-template/renderer/renderer.go b/vendor/github.com/hashicorp/consul-template/renderer/renderer.go index 59931c19e628..366eab81ded0 100644 --- a/vendor/github.com/hashicorp/consul-template/renderer/renderer.go +++ b/vendor/github.com/hashicorp/consul-template/renderer/renderer.go @@ -60,11 +60,12 @@ type RenderResult struct { // whether it would have rendered and actually did render. func Render(i *RenderInput) (*RenderResult, error) { existing, err := ioutil.ReadFile(i.Path) - if err != nil && !os.IsNotExist(err) { + fileExists := !os.IsNotExist(err) + if err != nil && fileExists { return nil, errors.Wrap(err, "failed reading file") } - if bytes.Equal(existing, i.Contents) { + if bytes.Equal(existing, i.Contents) && fileExists { return &RenderResult{ DidRender: false, WouldRender: true, diff --git a/vendor/github.com/hashicorp/consul-template/template/funcs.go b/vendor/github.com/hashicorp/consul-template/template/funcs.go index acf8e70a440a..ecbdb72a3bcd 100644 --- a/vendor/github.com/hashicorp/consul-template/template/funcs.go +++ b/vendor/github.com/hashicorp/consul-template/template/funcs.go @@ -1501,8 +1501,8 @@ func maximum(b, a interface{}) (interface{}, error) { } } -// blacklisted always returns an error, to be used in place of blacklisted template functions -func blacklisted(...string) (string, error) { +// denied always returns an error, to be used in place of denied template functions +func denied(...string) (string, error) { return "", errors.New("function is disabled") } diff --git a/vendor/github.com/hashicorp/consul-template/template/template.go b/vendor/github.com/hashicorp/consul-template/template/template.go index f95ad3e882c4..7b70bca559bb 100644 --- a/vendor/github.com/hashicorp/consul-template/template/template.go +++ b/vendor/github.com/hashicorp/consul-template/template/template.go @@ -46,9 +46,9 @@ type Template struct { // is indexed with a key that does not exist. errMissingKey bool - // functionBlacklist are functions not permitted to be executed + // functionDenylist are functions not permitted to be executed // when we render this template - functionBlacklist []string + functionDenylist []string // sandboxPath adds a prefix to any path provided to the `file` function // and causes an error if a relative path tries to traverse outside that @@ -72,9 +72,9 @@ type NewTemplateInput struct { LeftDelim string RightDelim string - // FunctionBlacklist are functions not permitted to be executed + // FunctionDenylist are functions not permitted to be executed // when we render this template - FunctionBlacklist []string + FunctionDenylist []string // SandboxPath adds a prefix to any path provided to the `file` function // and causes an error if a relative path tries to traverse outside that @@ -104,7 +104,7 @@ func NewTemplate(i *NewTemplateInput) (*Template, error) { t.leftDelim = i.LeftDelim t.rightDelim = i.RightDelim t.errMissingKey = i.ErrMissingKey - t.functionBlacklist = i.FunctionBlacklist + t.functionDenylist = i.FunctionDenylist t.sandboxPath = i.SandboxPath if i.Source != "" { @@ -175,13 +175,13 @@ func (t *Template) Execute(i *ExecuteInput) (*ExecuteResult, error) { tmpl.Delims(t.leftDelim, t.rightDelim) tmpl.Funcs(funcMap(&funcMapInput{ - t: tmpl, - brain: i.Brain, - env: i.Env, - used: &used, - missing: &missing, - functionBlacklist: t.functionBlacklist, - sandboxPath: t.sandboxPath, + t: tmpl, + brain: i.Brain, + env: i.Env, + used: &used, + missing: &missing, + functionDenylist: t.functionDenylist, + sandboxPath: t.sandboxPath, })) if t.errMissingKey { @@ -210,13 +210,13 @@ func (t *Template) Execute(i *ExecuteInput) (*ExecuteResult, error) { // funcMapInput is input to the funcMap, which builds the template functions. type funcMapInput struct { - t *template.Template - brain *Brain - env []string - functionBlacklist []string - sandboxPath string - used *dep.Set - missing *dep.Set + t *template.Template + brain *Brain + env []string + functionDenylist []string + sandboxPath string + used *dep.Set + missing *dep.Set } // funcMap is the map of template functions to their respective functions. @@ -300,9 +300,9 @@ func funcMap(i *funcMapInput) template.FuncMap { "maximum": maximum, } - for _, bf := range i.functionBlacklist { + for _, bf := range i.functionDenylist { if _, ok := r[bf]; ok { - r[bf] = blacklisted + r[bf] = denied } } diff --git a/vendor/github.com/hashicorp/consul-template/version/version.go b/vendor/github.com/hashicorp/consul-template/version/version.go index d0140809225a..482e3e8453eb 100644 --- a/vendor/github.com/hashicorp/consul-template/version/version.go +++ b/vendor/github.com/hashicorp/consul-template/version/version.go @@ -2,7 +2,7 @@ package version import "fmt" -const Version = "0.25.0" +const Version = "0.25.1" var ( Name string diff --git a/vendor/modules.txt b/vendor/modules.txt index 18f9cfd77027..a44eb28dff18 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -364,7 +364,7 @@ github.com/gorhill/cronexpr github.com/gorilla/websocket # github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed github.com/hailocab/go-hostpool -# github.com/hashicorp/consul-template v0.25.0 +# github.com/hashicorp/consul-template v0.25.1 github.com/hashicorp/consul-template/child github.com/hashicorp/consul-template/config github.com/hashicorp/consul-template/dependency diff --git a/website/pages/api-docs/secret/databases/mysql-maria.mdx b/website/pages/api-docs/secret/databases/mysql-maria.mdx index 051e5f83c150..302f53e3b432 100644 --- a/website/pages/api-docs/secret/databases/mysql-maria.mdx +++ b/website/pages/api-docs/secret/databases/mysql-maria.mdx @@ -45,6 +45,12 @@ has a number of parameters to further configure a connection. - `password` `(string: "")` - The root credential password used in the connection URL. +- `tls_certificate_key` `(string: "")` - x509 certificate for connecting to the database. + This must be a PEM encoded version of the private key and the certificate combined. + +- `tls_ca` `(string: "")` - x509 CA file for validating the certificate presented by the + MySQL server. Must be PEM encoded. + ### Sample Payload ```json diff --git a/website/pages/api-docs/secret/ssh/index.mdx b/website/pages/api-docs/secret/ssh/index.mdx index b2e2fa1b251c..3dba9d0d10a6 100644 --- a/website/pages/api-docs/secret/ssh/index.mdx +++ b/website/pages/api-docs/secret/ssh/index.mdx @@ -211,6 +211,11 @@ This endpoint creates or updates a named role. - `allowed_user_key_lengths` `(map: "")` – Specifies a map of ssh key types and their expected sizes which are allowed to be signed by the CA type. +- `algorithm_signer` `(string: "ssh-rsa")` - Algorithm to sign keys with. Valid + values are `ssh-rsa`, `rsa-sha2-256`, and `rsa-sha2-512`. Note that `ssh-rsa` + is now considered insecure and is not supported by current OpenSSH versions. + Defaults to `ssh-rsa` for backwards compatibility. + ### Sample Payload ```json diff --git a/website/pages/api-docs/secret/transit/index.mdx b/website/pages/api-docs/secret/transit/index.mdx index 26e4b6924b89..5071f9417058 100644 --- a/website/pages/api-docs/secret/transit/index.mdx +++ b/website/pages/api-docs/secret/transit/index.mdx @@ -891,9 +891,9 @@ supports signing. only for legacy applications. Signing using SHA-1 can be blocked by operators by assigning the following policy corresponding to a named key: - ``` - { - ] + ```hcl + path "/transit/sign/:name/sha1" { + capabilities = ["deny"] } ``` @@ -1046,9 +1046,9 @@ data. be blocked by operators by assigning the following policy corresponding to a named key: - ``` - { - ] + ```hcl + path "/transit/verify/:name/sha1" { + capabilities = ["deny"] } ``` diff --git a/website/pages/docs/agent/autoauth/index.mdx b/website/pages/docs/agent/autoauth/index.mdx index eab9fc24e903..2f84c3976687 100644 --- a/website/pages/docs/agent/autoauth/index.mdx +++ b/website/pages/docs/agent/autoauth/index.mdx @@ -155,6 +155,11 @@ These configuration values are common to all Sinks: agent should read the client's initial parameters (e.g. curve25519 public key). +- `derive_key` `(bool: false)` - If specified, the final encryption key is + calculated by using HKDF-SHA256 to derive a key from the calculated shared + secret and the two public keys for enhanced security. This is recommended + if backward compatibility isn't a concern. + - `aad` `(string: optional)` - If specified, additional authenticated data to use with the AES-GCM encryption of the token. Can be any string, including serialized data. diff --git a/website/pages/docs/secrets/databases/cassandra.mdx b/website/pages/docs/secrets/databases/cassandra.mdx index 9de96652200f..b55cb0e696cd 100644 --- a/website/pages/docs/secrets/databases/cassandra.mdx +++ b/website/pages/docs/secrets/databases/cassandra.mdx @@ -20,7 +20,7 @@ more information about setting up the database secrets engine. ## Capabilities | Plugin Name | Root Credential Rotation | Dynamic Roles | Static Roles | | --- | --- | --- | --- | -| `cassandra-database-plugin` | Yes | Yes | Yes | +| `cassandra-database-plugin` | Yes | Yes | No | ## Setup diff --git a/website/pages/docs/secrets/databases/index.mdx b/website/pages/docs/secrets/databases/index.mdx index ccd3ea1c43a1..d35e80b81675 100644 --- a/website/pages/docs/secrets/databases/index.mdx +++ b/website/pages/docs/secrets/databases/index.mdx @@ -131,7 +131,7 @@ the proper permission, it can generate credentials. ## Database Capabilities | Database | Root Credential Rotation | Dynamic Roles | Static Roles | | --- | --- | --- | --- | -| [Cassandra](/docs/secrets/databases/cassandra) | Yes | Yes | Yes | +| [Cassandra](/docs/secrets/databases/cassandra) | Yes | Yes | No | | [Elasticsearch](/docs/secrets/databases/elasticdb) | Yes | Yes | No | | [HanaDB](/docs/secrets/databases/hanadb) | No | Yes | No | | [InfluxDB](/docs/secrets/databases/influxdb) | Yes | Yes | No | diff --git a/website/pages/docs/secrets/databases/mysql-maria.mdx b/website/pages/docs/secrets/databases/mysql-maria.mdx index 63cb26b4e399..62a72caa72c4 100644 --- a/website/pages/docs/secrets/databases/mysql-maria.mdx +++ b/website/pages/docs/secrets/databases/mysql-maria.mdx @@ -88,6 +88,27 @@ the proper permission, it can generate credentials. username v-vaultuser-e2978cd0- ``` +## Client x509 Certificate Authentication + +This plugin supports using MySQL's [x509 Client-side Certificate Authentication](https://dev.mysql.com/doc/refman/8.0/en/using-encrypted-connections.html#using-encrypted-connections-client-side-configuration) + +To use this authentication mechanism, configure the plugin: + +```shell-session +$ vault write database/config/my-mysql-database \ + plugin_name=mysql-database-plugin \ + allowed_roles="my-role" \ + connection_url="user:password@tcp(localhost:3306)/test" \ + tls_certificate_key=@/path/to/client.pem \ + tls_ca=@/path/to/client.ca +``` + +Note: `tls_certificate_key` and `tls_ca` map to [`ssl-cert (combined with ssl-key)`](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-cert) +and [`ssl-ca`](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-ca) configuration options +from MySQL with the exception that the Vault parameters are the contents of those files, not filenames. As such, +the two options are independent of each other. See the [MySQL Connection Options](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html) +for more information. + ## Examples ### Using wildcards in grant statements