Skip to content

Commit

Permalink
db/postgres: Add support for inline TLS config
Browse files Browse the repository at this point in the history
  • Loading branch information
fairclothjm committed Oct 9, 2024
1 parent 32c490c commit 8e41f55
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 8 deletions.
1 change: 1 addition & 0 deletions internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ const (
VaultVersion115 = "1.15.0"
VaultVersion116 = "1.16.0"
VaultVersion117 = "1.17.0"
VaultVersion118 = "1.18.0"

/*
Vault auth methods
Expand Down
1 change: 1 addition & 0 deletions internal/provider/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
74 changes: 66 additions & 8 deletions vault/resource_database_secret_backend_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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{}) {
Expand Down
134 changes: 134 additions & 0 deletions vault/resource_database_secret_backend_connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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-----`
9 changes: 9 additions & 0 deletions website/docs/r/database_secret_backend_connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down

0 comments on commit 8e41f55

Please sign in to comment.