From 3617acbaa338dc4bdae5ac842b74f22ffc18b2b5 Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Fri, 31 May 2024 13:22:37 -0400 Subject: [PATCH] dbaas: ensure password is always retained in connection URIs, replicas, and connection pools. (#1169) * dbaas: ensure password is always retained in connection URI. * dbaas: ensure password is always retained in connection pools. * dbaas: ensure password is always retained in replicas. * Remove unused const --- .../database/resource_database_cluster.go | 37 +++++-------- .../resource_database_connection_pool.go | 47 ++++++++++++++--- .../resource_database_connection_pool_test.go | 4 -- .../database/resource_database_replica.go | 52 ++++++++++++++++--- 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/digitalocean/database/resource_database_cluster.go b/digitalocean/database/resource_database_cluster.go index f2d9fdd2f..86333b2b4 100644 --- a/digitalocean/database/resource_database_cluster.go +++ b/digitalocean/database/resource_database_cluster.go @@ -19,7 +19,6 @@ import ( ) const ( - mongoDBEngineSlug = "mongodb" mysqlDBEngineSlug = "mysql" redisDBEngineSlug = "redis" ) @@ -640,32 +639,12 @@ func flattenMaintWindowOpts(opts godo.DatabaseMaintenanceWindow) []map[string]in return result } -func buildDBConnectionURI(database *godo.Database, d *schema.ResourceData) (string, error) { - if database.EngineSlug == mongoDBEngineSlug { - return buildMongoDBConnectionURI(database.Connection, d) - } - - return database.Connection.URI, nil -} - -func buildDBPrivateURI(database *godo.Database, d *schema.ResourceData) (string, error) { - if database.EngineSlug == mongoDBEngineSlug { - uri, err := buildMongoDBConnectionURI(database.PrivateConnection, d) - if err != nil { - return "", err - } - return uri, nil - } - - return database.PrivateConnection.URI, nil -} - func setDatabaseConnectionInfo(database *godo.Database, d *schema.ResourceData) error { if database.Connection != nil { d.Set("host", database.Connection.Host) d.Set("port", database.Connection.Port) - uri, err := buildDBConnectionURI(database, d) + uri, err := buildDBConnectionURI(database.Connection, d) if err != nil { return err } @@ -681,7 +660,7 @@ func setDatabaseConnectionInfo(database *godo.Database, d *schema.ResourceData) if database.PrivateConnection != nil { d.Set("private_host", database.PrivateConnection.Host) - privateUri, err := buildDBPrivateURI(database, d) + privateUri, err := buildDBPrivateURI(database.PrivateConnection, d) if err != nil { return err } @@ -705,11 +684,17 @@ func setUIConnectionInfo(database *godo.Database, d *schema.ResourceData) error return nil } +// buildDBConnectionURI constructs a connection URI using the password stored in state. +// // MongoDB clusters only return their password in response to the initial POST. // The host for the cluster is not known until it becomes available. In order to // build a usable connection URI, we must save the password and then add it to // the URL returned latter. -func buildMongoDBConnectionURI(conn *godo.DatabaseConnection, d *schema.ResourceData) (string, error) { +// +// This also protects against the password being removed from the URI if the user +// switches to using a read-only token. All database engines redact the password +// in that case +func buildDBConnectionURI(conn *godo.DatabaseConnection, d *schema.ResourceData) (string, error) { password := d.Get("password") uri, err := url.Parse(conn.URI) if err != nil { @@ -722,6 +707,10 @@ func buildMongoDBConnectionURI(conn *godo.DatabaseConnection, d *schema.Resource return uri.String(), nil } +func buildDBPrivateURI(conn *godo.DatabaseConnection, d *schema.ResourceData) (string, error) { + return buildDBConnectionURI(conn, d) +} + func expandBackupRestore(config []interface{}) *godo.DatabaseBackupRestore { backupRestoreConfig := config[0].(map[string]interface{}) diff --git a/digitalocean/database/resource_database_connection_pool.go b/digitalocean/database/resource_database_connection_pool.go index cf31475a4..2d524b8b0 100644 --- a/digitalocean/database/resource_database_connection_pool.go +++ b/digitalocean/database/resource_database_connection_pool.go @@ -126,6 +126,11 @@ func resourceDigitalOceanDatabaseConnectionPoolCreate(ctx context.Context, d *sc d.SetId(createConnectionPoolID(clusterID, pool.Name)) log.Printf("[INFO] DatabaseConnectionPool Name: %s", pool.Name) + err = setConnectionPoolInfo(pool, d) + if err != nil { + return diag.Errorf("Error building connection URI: %s", err) + } + return resourceDigitalOceanDatabaseConnectionPoolRead(ctx, d, meta) } @@ -153,13 +158,41 @@ func resourceDigitalOceanDatabaseConnectionPoolRead(ctx context.Context, d *sche d.Set("size", pool.Size) d.Set("db_name", pool.Database) - // Computed values - d.Set("host", pool.Connection.Host) - d.Set("private_host", pool.PrivateConnection.Host) - d.Set("port", pool.Connection.Port) - d.Set("uri", pool.Connection.URI) - d.Set("private_uri", pool.PrivateConnection.URI) - d.Set("password", pool.Connection.Password) + err = setConnectionPoolInfo(pool, d) + if err != nil { + return diag.Errorf("Error building connection URI: %s", err) + } + + return nil +} + +func setConnectionPoolInfo(pool *godo.DatabasePool, d *schema.ResourceData) error { + if pool.Connection != nil { + d.Set("host", pool.Connection.Host) + d.Set("port", pool.Connection.Port) + + if pool.Connection.Password != "" { + d.Set("password", pool.Connection.Password) + } + + uri, err := buildDBConnectionURI(pool.Connection, d) + if err != nil { + return err + } + + d.Set("uri", uri) + } + + if pool.PrivateConnection != nil { + d.Set("private_host", pool.PrivateConnection.Host) + + privateURI, err := buildDBConnectionURI(pool.PrivateConnection, d) + if err != nil { + return err + } + + d.Set("private_uri", privateURI) + } return nil } diff --git a/digitalocean/database/resource_database_connection_pool_test.go b/digitalocean/database/resource_database_connection_pool_test.go index 9cbbe6b3f..97b43a3e3 100644 --- a/digitalocean/database/resource_database_connection_pool_test.go +++ b/digitalocean/database/resource_database_connection_pool_test.go @@ -61,10 +61,6 @@ func TestAccDigitalOceanDatabaseConnectionPool_Basic(t *testing.T) { "digitalocean_database_connection_pool.pool-01", "name", databaseConnectionPoolName), resource.TestCheckResourceAttr( "digitalocean_database_connection_pool.pool-01", "mode", "session"), - resource.TestCheckResourceAttr( - "digitalocean_database_connection_pool.pool-01", "user", ""), - resource.TestCheckResourceAttr( - "digitalocean_database_connection_pool.pool-01", "password", ""), ), }, }, diff --git a/digitalocean/database/resource_database_replica.go b/digitalocean/database/resource_database_replica.go index 6a2882e62..e7e5c1d3b 100644 --- a/digitalocean/database/resource_database_replica.go +++ b/digitalocean/database/resource_database_replica.go @@ -171,6 +171,7 @@ func resourceDigitalOceanDatabaseReplicaCreate(ctx context.Context, d *schema.Re } } replicaCluster = rc + return nil }) @@ -178,6 +179,11 @@ func resourceDigitalOceanDatabaseReplicaCreate(ctx context.Context, d *schema.Re return diag.FromErr(err) } + err = setReplicaConnectionInfo(replicaCluster, d) + if err != nil { + return diag.Errorf("Error building connection URI: %s", err) + } + replica, err := waitForDatabaseReplica(client, clusterId, "online", replicaCluster.Name) if err != nil { return diag.Errorf("Error creating DatabaseReplica: %s", err) @@ -218,17 +224,47 @@ func resourceDigitalOceanDatabaseReplicaRead(ctx context.Context, d *schema.Reso // Computed values d.Set("uuid", replica.ID) - d.Set("host", replica.Connection.Host) - d.Set("private_host", replica.PrivateConnection.Host) - d.Set("port", replica.Connection.Port) - d.Set("uri", replica.Connection.URI) - d.Set("private_uri", replica.PrivateConnection.URI) - d.Set("database", replica.Connection.Database) - d.Set("user", replica.Connection.User) - d.Set("password", replica.Connection.Password) d.Set("private_network_uuid", replica.PrivateNetworkUUID) d.Set("storage_size_mib", strconv.FormatUint(replica.StorageSizeMib, 10)) + err = setReplicaConnectionInfo(replica, d) + if err != nil { + return diag.Errorf("Error building connection URI: %s", err) + } + + return nil +} + +func setReplicaConnectionInfo(replica *godo.DatabaseReplica, d *schema.ResourceData) error { + if replica.Connection != nil { + d.Set("host", replica.Connection.Host) + d.Set("port", replica.Connection.Port) + d.Set("database", replica.Connection.Database) + d.Set("user", replica.Connection.User) + + if replica.Connection.Password != "" { + d.Set("password", replica.Connection.Password) + } + + uri, err := buildDBConnectionURI(replica.Connection, d) + if err != nil { + return err + } + + d.Set("uri", uri) + } + + if replica.PrivateConnection != nil { + d.Set("private_host", replica.PrivateConnection.Host) + + privateURI, err := buildDBConnectionURI(replica.PrivateConnection, d) + if err != nil { + return err + } + + d.Set("private_uri", privateURI) + } + return nil }