From 8e41f550c1d3376b4b06cdd20d1843e43ec23db7 Mon Sep 17 00:00:00 2001 From: JM Faircloth Date: Tue, 24 Sep 2024 16:09:52 -0500 Subject: [PATCH] db/postgres: Add support for inline TLS config --- internal/consts/consts.go | 1 + internal/provider/meta.go | 1 + ...urce_database_secret_backend_connection.go | 74 ++++++++-- ...database_secret_backend_connection_test.go | 134 ++++++++++++++++++ .../r/database_secret_backend_connection.md | 9 ++ 5 files changed, 211 insertions(+), 8 deletions(-) diff --git a/internal/consts/consts.go b/internal/consts/consts.go index cb271ad565..f9f28e3b44 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -516,6 +516,7 @@ const ( VaultVersion115 = "1.15.0" VaultVersion116 = "1.16.0" VaultVersion117 = "1.17.0" + VaultVersion118 = "1.18.0" /* Vault auth methods diff --git a/internal/provider/meta.go b/internal/provider/meta.go index 4840e9a8cb..a50e599392 100644 --- a/internal/provider/meta.go +++ b/internal/provider/meta.go @@ -43,6 +43,7 @@ var ( VaultVersion115 = version.Must(version.NewSemver(consts.VaultVersion115)) VaultVersion116 = version.Must(version.NewSemver(consts.VaultVersion116)) VaultVersion117 = version.Must(version.NewSemver(consts.VaultVersion117)) + VaultVersion118 = version.Must(version.NewSemver(consts.VaultVersion118)) TokenTTLMinRecommended = time.Minute * 15 ) diff --git a/vault/resource_database_secret_backend_connection.go b/vault/resource_database_secret_backend_connection.go index 4a2eb79117..48680e0b6e 100644 --- a/vault/resource_database_secret_backend_connection.go +++ b/vault/resource_database_secret_backend_connection.go @@ -558,14 +558,10 @@ func getDatabaseSchema(typ schema.ValueType) schemaMap { ConflictsWith: util.CalculateConflictsWith(dbEngineMySQLLegacy.Name(), dbEngineTypes), }, dbEnginePostgres.name: { - Type: typ, - Optional: true, - Description: "Connection parameters for the postgresql-database-plugin plugin.", - Elem: connectionStringResource(&connectionStringConfig{ - includeUserPass: true, - includeDisableEscaping: true, - isCloud: true, - }), + Type: typ, + Optional: true, + Description: "Connection parameters for the postgresql-database-plugin plugin.", + Elem: postgresConnectionStringResource(), MaxItems: 1, ConflictsWith: util.CalculateConflictsWith(dbEnginePostgres.Name(), dbEngineTypes), }, @@ -789,9 +785,48 @@ func connectionStringResource(config *connectionStringConfig) *schema.Resource { } } + if config.isCloud { + res.Schema["auth_type"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Specify alternative authorization type. (Only 'gcp_iam' is valid currently)", + } + res.Schema["service_account_json"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "A JSON encoded credential for use with IAM authorization", + Sensitive: true, + } + } + return res } +func postgresConnectionStringResource() *schema.Resource { + r := connectionStringResource(&connectionStringConfig{ + includeUserPass: true, + includeDisableEscaping: true, + isCloud: true, + }) + r.Schema["tls_ca"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The x509 CA file for validating the certificate presented by the PostgreSQL server. Must be PEM encoded.", + } + r.Schema["tls_certificate"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The x509 client certificate for connecting to the database. Must be PEM encoded.", + } + r.Schema["private_key"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The secret key used for the x509 client certificate. Must be PEM encoded.", + Sensitive: true, + } + return r +} + func mysqlConnectionStringResource() *schema.Resource { r := connectionStringResource(&connectionStringConfig{ includeUserPass: true, @@ -1099,6 +1134,17 @@ func getPostgresConnectionDetailsFromResponse(d *schema.ResourceData, prefix str } } + if provider.IsAPISupported(meta, provider.VaultVersion118) { + if v, ok := data["tls_ca"]; ok { + result["tls_ca"] = v.(string) + } + if v, ok := data["tls_certificate"]; ok { + result["tls_certificate"] = v.(string) + } + // the private key is a secret that is never revealed by Vault + result["private_key"] = d.Get(prefix + "private_key") + } + return result } @@ -1491,6 +1537,18 @@ func setMySQLDatabaseConnectionData(d *schema.ResourceData, prefix string, data func setPostgresDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}, meta interface{}) { setDatabaseConnectionDataWithDisableEscaping(d, prefix, data) setCloudDatabaseConnectionData(d, prefix, data, meta) + + if provider.IsAPISupported(meta, provider.VaultVersion118) { + if v, ok := d.GetOk(prefix + "tls_ca"); ok { + data["tls_ca"] = v.(string) + } + if v, ok := d.GetOk(prefix + "tls_certificate"); ok { + data["tls_certificate"] = v.(string) + } + if v, ok := d.GetOk(prefix + "private_key"); ok { + data["private_key"] = v.(string) + } + } } func setRedisDatabaseConnectionData(d *schema.ResourceData, prefix string, data map[string]interface{}) { diff --git a/vault/resource_database_secret_backend_connection_test.go b/vault/resource_database_secret_backend_connection_test.go index 4b8150390e..4bd08c576b 100644 --- a/vault/resource_database_secret_backend_connection_test.go +++ b/vault/resource_database_secret_backend_connection_test.go @@ -835,6 +835,30 @@ func TestAccDatabaseSecretBackendConnection_postgresql(t *testing.T) { }) } +func TestAccDatabaseSecretBackendConnection_postgresql_tls(t *testing.T) { + testResource := "vault_database_secret_backend_connection.test" + backend := acctest.RandomWithPrefix("tf-test-db") + pluginName := dbEnginePostgres.DefaultPluginName() + name := acctest.RandomWithPrefix("db") + + fmt.Print(testAccDatabaseSecretBackendConnectionConfig_postgresql_tls(name, backend, testPostgresCACert, testPostgresClientCert, testPostgresClientKey)) + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + CheckDestroy: testAccDatabaseSecretBackendConnectionCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDatabaseSecretBackendConnectionConfig_postgresql_tls(name, backend, testPostgresCACert, testPostgresClientCert, testPostgresClientKey), + Check: testComposeCheckFuncCommonDatabaseSecretBackend(name, backend, pluginName, + resource.TestCheckResourceAttr(testResource, "postgresql.0.tls_ca", testPostgresCACert), + resource.TestCheckResourceAttr(testResource, "postgresql.0.tls_certificate", testPostgresClientCert), + resource.TestCheckResourceAttr(testResource, "postgresql.0.private_key", testPostgresClientKey), + ), + }, + }, + }) +} + func TestAccDatabaseSecretBackendConnection_postgresql_cloud(t *testing.T) { // wanted this to be the included with the following test, but the env-var check is different values := testutil.SkipTestEnvUnset(t, "POSTGRES_CLOUD_URL", "POSTGRES_CLOUD_SERVICE_ACCOUNT_JSON") @@ -1716,6 +1740,33 @@ resource "vault_database_secret_backend_connection" "test" { `, path, name, parsedURL.String()) } +func testAccDatabaseSecretBackendConnectionConfig_postgresql_tls(name, path, tlsCA, tlsCert, privateKey string) string { + return fmt.Sprintf(` +resource "vault_mount" "db" { + path = "%s" + type = "database" +} + +resource "vault_database_secret_backend_connection" "test" { + backend = vault_mount.db.path + name = "%s" + allowed_roles = ["dev", "prod"] + root_rotation_statements = ["FOOBAR"] + verify_connection = false + + postgresql { + connection_url = "postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=verify-full" + username = "user1" + password = "pass1" + + tls_ca = %q + tls_certificate = %q + private_key = %q + } +} +`, path, name, tlsCA, tlsCert, privateKey) +} + func testAccDatabaseSecretBackendConnectionConfig_postgres_cloud(name, path, connURL, authType, serviceAccountJSON string) string { return fmt.Sprintf(` resource "vault_mount" "db" { @@ -2315,3 +2366,86 @@ func Test_getDBEngineFromResp(t *testing.T) { }) } } + +const testPostgresCACert = `-----BEGIN CERTIFICATE----- +MIIE2jCCAsKgAwIBAgIBATANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJjYTAe +Fw0yNDA3MTIyMDEzMzhaFw0yNjAxMTIyMDIzMzdaMA0xCzAJBgNVBAMTAmNhMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzd6h6kI5Z3ofQzV5HiKL+uge +hr8AeCywNBUAQ8yX97HKLPYMw2HQFAx9mMIiW1EbwfMb5NYY6blyyyUfZAwzIDzq +IrKwGSa//bS2mO19N5oQdy0w+S+Xo55tsDq0C1hNZf7TOJ/lVOi+Ot68OXqLhmTa +TYrkdYb33kanYVV5IyMgAgGA6w78gJPLKKe57CKe4oq2bU7jPANxu00TthRNL51c +xucGYJCRkeqK7F6MSxXXS1Xv73mh4uTiFYxwsHbgTKW5LAADWWMCwtgjP1ZSfU1U +0BGSyh/fVl1gQIjPCbGp+lSO+eYNhC+/42hvi//y0wH4cv0LwssHZKltDq4pEFwZ +WH8iX6gXoo6FD8FNNCBaZbBAHwPGUkbtjXVl2xhwA5YAin78YM5a1Sy3ZbfxxN++ +msSdMxyVM0I/ctnAF/M8RFLOqf/xAbJf/AaXBvPhJAOqqhqMsi2SF5jc5faUpqRE +eOHJGnfFsl1O/0t4YPly/SDowIatMRe+fYp7E79cBve4R9uMPTH2/wV1xla4s42r +GZRoIkeRNg0dI0eFiBOJL8jDTrm8702a2Blu6E0YfxV+XppooGUVzwd2zl7Gvqnl +cSHx4S5drMEz5WGu6/u/ae712BLBvmBrdU1ta8Fr644AZrrsAIL5plY4wez55+/u +LcDlUkKZ01579eDdQBkCAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB +/wQIMAYBAf8CAQAwHQYDVR0OBBYEFF6gLEwzFMWpqOFkhuPY/mMepy8QMA0GCSqG +SIb3DQEBCwUAA4ICAQCaiOUPCFpJx6fw+Vw/sUxzX/JYyrxiOOrMErM5upw3gErq +pYP6RPZS16lOS0LwXQ9G6c72hgcSf4oBL4C/bCXDX7MtXFaa5INI+Bga+OUM9TtD +zSZ/CWpfmUPY1NJOIqFIfhMVmuzGlxdQXrjT0pKQ/SlEJnNN6A5qFkX7+UBgT7Ii +/D11w+I8VTzVmzkkSKalg0I8TZlXD2ADPA+SEljDB6e6wTJie++uLFjhfc2ssJ4I +ZEkMUg8JOtyVAKlrE+i3YhQFjJuTKezIV9ss6akZb8719/iLPudiPo+4Hd4NrEi6 ++VDFsc2bBCO8vLYSDyf8pqzZCG74oIZbmneAM2c4pnbTcm5LsBThVUbvzH1rPZGh +3oB4F/CHeov9BfLCdPRjL1HFlD5zUUmA5gPI2avZL8RKCsX1lO7xXVfQihofMAd1 +Fb3Wn5+ECeWCPf6EnuEG6aZWyFiBzDrC4jP628t5Oc38Wi3ZQkyVV4BJP+/9AxoF +Z8o9nMIie9BSNfnbAvxwEThY8Jwc5/azZdHXYUrLDA6bcsBw77WTI/TiddzqbU4g +yCAn30ML5eJBdp30xw9+pGpFVbUh4vxBVyLC4Yhbcvp9PuZmIKvkruWNSj3zQSnm +avNz7L+OZEENF2qm/XN6WG5UQHbl3VN614k1dsyD54T+LpNmDZ20wFKPH3zmDw== +-----END CERTIFICATE-----` + +const testPostgresClientCert = `-----BEGIN CERTIFICATE----- +MIIEMTCCAhmgAwIBAgIQBMMx4O5wAf+ulbCz1EwvAjANBgkqhkiG9w0BAQsFADAN +MQswCQYDVQQDEwJjYTAeFw0yNDA3MTIyMDEzMzhaFw0yNjAxMTIyMDIzMzdaMBEx +DzANBgNVBAMTBmNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALNhfyofmTapjkf7M5bd6UALxVIngdMx0m/LCE5You2OtuuEM4rFDTs4yi2FFIY+ +3rT+ibEiw0QYCTJZ+xOv4TQE8lSGxnrIFtVXlwFLq5eeuuY2eMFtevXj5g6bk50/ +FQTs2Laq7LRgN8ZoW+Hn6wglbuM+QLIHGBZtVFfgYXVi54FO24MMWqThgIX21Ns6 +iA7nbG/00QYlaqGaZX5vd07cdhxo3qwMSqJc2EP7OKLtmwSuGU4CyWOKfuFr7ITl +DObGIqODvIaRBVFjIsJiEER5V5FWyCAbj1f5jREO6rXoBlwsFvUw7PlFHtX5t7RO +JkUajvbsPYFjNDNwk1u4Mo8CAwEAAaOBiDCBhTAOBgNVHQ8BAf8EBAMCA7gwHQYD +VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTnlzQEFsMsV3EM +v52+eD/GsjMvqTAfBgNVHSMEGDAWgBReoCxMMxTFqajhZIbj2P5jHqcvEDAUBgNV +HREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAMm6bto3Tti9iMS/ +kRpDJ74oIAKybm313va7w85qqxa5wDHY5MAr9qWL/tNUQlzfgsfOrLbXxgVZBjAn +1raxZaBQ5aclmTYKdJRmXAPzcYTu0YV/L3zg2ZX3Rds3M10u1BSxhXK4vTS6VH+K +d3BZF3uQ0pRd49PERTdb+M5l4y/TV+pmgEsDYarLjAoS4WVBXe3FM/RMYjNQJIae +baLCf87G7G/WMtmunW+PmL2pKDlmbkENoSULmX1IQ2CxotdYfI8IJWDE3nKzufzR +X/1mfAksgsSHH4qTUXQFARoGwVaz04pe+E6R0QbgZKWIhhPF+PX99Jm+Uc7s7e7+ +u4E76SOfKXfzuB2sfJlR4BxJnVxxrmJVzBRC7ENwXJ2kTfL6PwLT1xUIu/VtJf7N +YnXYx7Is8VVJ7oTCrA1k5tCuPv0AV3SnPq/YzhpUgiWI6sAAtv5GshVETSWta0Bh +XKkRkRK3ubxD7yPhEWypubHY2Nutdj05erBz7FslGvSoPwJrlroLQbwb0fOlHvFA +klHpzMyNSttmDa6S93wGD7U44C+8kMJUZGT3fy3CFvJacvpHdsNKKHhNEgBn+zmG +IRrNQZaXS3XsmjyczI7SETRlYABq644LZlFOXkX4Gj4YG6mkznlB9sYp1OLu34Xn +4Y1FHwnTAcoTwkEPiki3oChg0ndz +-----END CERTIFICATE-----` + +const testPostgresClientKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAs2F/Kh+ZNqmOR/szlt3pQAvFUieB0zHSb8sITlii7Y6264Qz +isUNOzjKLYUUhj7etP6JsSLDRBgJMln7E6/hNATyVIbGesgW1VeXAUurl5665jZ4 +wW169ePmDpuTnT8VBOzYtqrstGA3xmhb4efrCCVu4z5AsgcYFm1UV+BhdWLngU7b +gwxapOGAhfbU2zqIDudsb/TRBiVqoZplfm93Ttx2HGjerAxKolzYQ/s4ou2bBK4Z +TgLJY4p+4WvshOUM5sYio4O8hpEFUWMiwmIQRHlXkVbIIBuPV/mNEQ7qtegGXCwW +9TDs+UUe1fm3tE4mRRqO9uw9gWM0M3CTW7gyjwIDAQABAoIBAQCf5IsGUC4w1EhY +Dyj4FIwiI5vaVA7b4vAR6CdaNpXcLLcODcQnsOfPXxqQIqyd0RKQwMaZV0Q4wTgJ +Yr1z2fViefpLr+rhbNM1jaKza/Di8IDmTa2rtNvCrEbXxIN6yc0Bm+C8SnU9fvqY +Z1NndWNB2qQR+N6QEdS9wOxKfF5C08y9Z3B2xoM2HpeYYW4WEJezMvcDGtDp152p +tN/z8sLKca5doFLwIUiGuJ3g4a5048R9MyEP7bg8g/LAtas4jmOx9xIISRt2i6LJ +ESszY5yy09K06o+IMKWSTDE1GD6o90wEuGDzF6fMNFxRgqVwAVsIaOO9ZRmIa8fV +yyw07MnhAoGBAOG/VXK7AFqfrMLPJY66WX1Az8mLP+uEVv/bfAsnheRNgMD5v3RE +0MgAnx7BAud4O9x4Ej3suNeEiDr4Ukg74CHVKWZACkcTgjY2rg023bwq9slml61E +8XQDgd1D4EELAJAjIldc0rzQ8YTSJ3xBVp5KL+hl1CFPxpEuFhYS7dURAoGBAMtr +edUwge1ti3NCW615RWyDbstvAOlcTyT/a0JViIH7zcZg20ZaQop9PYyGdu/19ha7 +8G32flWVqoWf2lBJ+ewG5ykHPEh+O3RLv+3cZs3+0c+fN70PCovsnN5C4BbmR6ls +5FV6/sTJqgN9BJN+wHV1Dj4wMHwWzdXqDUKnPw2fAoGBANsz4+/8/zIAPEwJwuld +r8m85kdI7K9vmN7mrANUxGFUlIJNwIdQzv52BAxj1MMYb9/7w5LXywCS04mXWKaF +ZXTUvFdqNdCgc97ap5VzQkoV2f7knMGF4YMKaM6GuznNSiWryAvWuVbY+LxFKEwy +Ub5wQSbDwgD6qtCMVKvog4JRAoGAcXmoAhxILnmQdCCNYc0nxCvhj4yBtqwu3lW5 +sMxkFRaxqLt5Ntq9CeJphk2wZZYQzIfUzJLX0Mhn0pjkwSszRs5m/0UxBMOeSPbE +v1zW4I0I38hS4J1WZc39iCNIPJ4DVekPyvuMyZwxwjZoahsoI53D7z8UnPRfqLgi +447GpsMCgYBnNiNlMvl4UqkZ83mJsqBwPhM3o3jPgS9OHk+nKjRws19lLUuRXCxy +a/0qa6m6iLDrh6oyVXsKlRgsePBl7jUjP3HZTalWpX8+HFbVYIPN3mU50qgjR/uF +lHWczW8tCg9aF3oBqvxt8WV/TU4oV4amunSkbD9HzqcnOuj1fGcZ9w== +-----END RSA PRIVATE KEY-----` diff --git a/website/docs/r/database_secret_backend_connection.md b/website/docs/r/database_secret_backend_connection.md index 291e415636..b3d364fecc 100644 --- a/website/docs/r/database_secret_backend_connection.md +++ b/website/docs/r/database_secret_backend_connection.md @@ -325,6 +325,15 @@ See the [Vault * `password` - (Optional) The root credential password used in the connection URL. +* `tls_ca` - (Optional) The x509 CA file for validating the certificate + presented by the PostgreSQL server. Must be PEM encoded. + +* `tls_certificate` - (Optional) The x509 client certificate for connecting to + the database. Must be PEM encoded. + +* `private_key` - (Optional) The secret key used for the x509 client + certificate. Must be PEM encoded. + * `auth_type` - (Optional) Enable IAM authentication to a Google Cloud instance when set to `gcp_iam` * `service_account_json` - (Optional) JSON encoding of an IAM access key. Requires `auth_type` to be `gcp_iam`.