Skip to content

Commit 82c61be

Browse files
committed
Add support for GCP IAM impersonation
1 parent 5213579 commit 82c61be

File tree

4 files changed

+85
-32
lines changed

4 files changed

+85
-32
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
gocloud.dev v0.34.0
1717
golang.org/x/net v0.26.0
1818
golang.org/x/oauth2 v0.10.0
19+
google.golang.org/api v0.134.0
1920
)
2021

2122
require (
@@ -91,7 +92,6 @@ require (
9192
golang.org/x/sys v0.21.0 // indirect
9293
golang.org/x/text v0.16.0 // indirect
9394
golang.org/x/time v0.3.0 // indirect
94-
google.golang.org/api v0.134.0 // indirect
9595
google.golang.org/appengine v1.6.7 // indirect
9696
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect
9797
google.golang.org/grpc v1.57.0 // indirect

postgresql/config.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ import (
1212

1313
"github.com/blang/semver"
1414
_ "github.com/lib/pq" // PostgreSQL db
15+
"gocloud.dev/gcp"
16+
"gocloud.dev/gcp/cloudsql"
1517
"gocloud.dev/postgres"
1618
_ "gocloud.dev/postgres/awspostgres"
17-
_ "gocloud.dev/postgres/gcppostgres"
19+
"gocloud.dev/postgres/gcppostgres"
20+
"google.golang.org/api/impersonate"
1821
)
1922

2023
type featureName uint
@@ -157,21 +160,22 @@ type ClientCertificateConfig struct {
157160

158161
// Config - provider config
159162
type Config struct {
160-
Scheme string
161-
Host string
162-
Port int
163-
Username string
164-
Password string
165-
DatabaseUsername string
166-
Superuser bool
167-
SSLMode string
168-
ApplicationName string
169-
Timeout int
170-
ConnectTimeoutSec int
171-
MaxConns int
172-
ExpectedVersion semver.Version
173-
SSLClientCert *ClientCertificateConfig
174-
SSLRootCertPath string
163+
Scheme string
164+
Host string
165+
Port int
166+
Username string
167+
Password string
168+
DatabaseUsername string
169+
Superuser bool
170+
SSLMode string
171+
ApplicationName string
172+
Timeout int
173+
ConnectTimeoutSec int
174+
MaxConns int
175+
ExpectedVersion semver.Version
176+
SSLClientCert *ClientCertificateConfig
177+
SSLRootCertPath string
178+
GCPIAMImpersonateServiceAccount string
175179
}
176180

177181
// Client struct holding connection string
@@ -280,6 +284,25 @@ func (c *Client) Connect() (*DBConnection, error) {
280284
var err error
281285
if c.config.Scheme == "postgres" {
282286
db, err = sql.Open(proxyDriverName, dsn)
287+
} else if c.config.Scheme == "gcppostgres" && c.config.GCPIAMImpersonateServiceAccount != "" {
288+
ts, err := impersonate.CredentialsTokenSource(context.Background(), impersonate.CredentialsConfig{
289+
TargetPrincipal: c.config.GCPIAMImpersonateServiceAccount,
290+
Scopes: []string{"https://www.googleapis.com/auth/sqlservice.admin"},
291+
})
292+
if err != nil {
293+
return nil, fmt.Errorf("Error creating token source with service account impersonation of %s: %w", c.config.GCPIAMImpersonateServiceAccount, err)
294+
}
295+
client, err := gcp.NewHTTPClient(gcp.DefaultTransport(), ts)
296+
if err != nil {
297+
return nil, fmt.Errorf("Error creating HTTP client with service account impersonation of %s: %w", c.config.GCPIAMImpersonateServiceAccount, err)
298+
}
299+
certSource := cloudsql.NewCertSourceWithIAM(client, ts)
300+
opener := gcppostgres.URLOpener{CertSource: certSource}
301+
dbURL, err := url.Parse(dsn)
302+
if err != nil {
303+
return nil, fmt.Errorf("Error parsing connection string: %w", err)
304+
}
305+
db, err = opener.OpenPostgresURL(context.Background(), dbURL)
283306
} else {
284307
db, err = postgres.Open(context.Background(), dsn)
285308
}

postgresql/provider.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package postgresql
33
import (
44
"context"
55
"fmt"
6+
"os"
7+
68
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
79
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
8-
"os"
910

1011
"github.com/blang/semver"
1112
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -103,6 +104,13 @@ func Provider() *schema.Provider {
103104
Description: "MS Azure tenant ID (see: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config.html)",
104105
},
105106

107+
"gcp_iam_impersonate_service_account": {
108+
Type: schema.TypeString,
109+
Optional: true,
110+
Default: "",
111+
Description: "Service account to impersonate when using GCP IAM authentication.",
112+
},
113+
106114
// Conection username can be different than database username with user name mapas (e.g.: in Azure)
107115
// See https://www.postgresql.org/docs/current/auth-username-maps.html
108116
"database_username": {
@@ -323,19 +331,20 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
323331
}
324332

325333
config := Config{
326-
Scheme: d.Get("scheme").(string),
327-
Host: host,
328-
Port: port,
329-
Username: username,
330-
Password: password,
331-
DatabaseUsername: d.Get("database_username").(string),
332-
Superuser: d.Get("superuser").(bool),
333-
SSLMode: sslMode,
334-
ApplicationName: "Terraform provider",
335-
ConnectTimeoutSec: d.Get("connect_timeout").(int),
336-
MaxConns: d.Get("max_connections").(int),
337-
ExpectedVersion: version,
338-
SSLRootCertPath: d.Get("sslrootcert").(string),
334+
Scheme: d.Get("scheme").(string),
335+
Host: host,
336+
Port: port,
337+
Username: username,
338+
Password: password,
339+
DatabaseUsername: d.Get("database_username").(string),
340+
Superuser: d.Get("superuser").(bool),
341+
SSLMode: sslMode,
342+
ApplicationName: "Terraform provider",
343+
ConnectTimeoutSec: d.Get("connect_timeout").(int),
344+
MaxConns: d.Get("max_connections").(int),
345+
ExpectedVersion: version,
346+
SSLRootCertPath: d.Get("sslrootcert").(string),
347+
GCPIAMImpersonateServiceAccount: d.Get("gcp_iam_impersonate_service_account").(string),
339348
}
340349

341350
if value, ok := d.GetOk("clientcert"); ok {

website/docs/index.html.markdown

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,28 @@ To enable GoCloud for GCP SQL, set `scheme` to `gcppostgres` and `host` to the c
214214
For GCP, GoCloud also requires the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to be set to the service account credentials file.
215215
These credentials can be created here: https://console.cloud.google.com/iam-admin/serviceaccounts
216216

217-
See also: https://cloud.google.com/docs/authentication/production
217+
In addition, the provider supports service account impersonation with the `gcp_iam_impersonate_service_account` option. You mush ensure:
218+
219+
- the IAM database user has sufficient permissions to connect to the database, e.g., `roles/cloudsql.instanceUser`
220+
- the principle behind the `GOOGLE_APPLICATION_CREDENTIALS` has sufficient permissions to impersonate the provided service account
221+
222+
```hcl
223+
provider "postgresql" {
224+
scheme = "gcppostgres"
225+
host = "test-project/europe-west3/test-instance"
226+
port = 5432
227+
228+
username = "service_account_id@$project_id.iam"
229+
gcp_iam_impersonate_service_account = "service_account_id@$project_id.iam.gserviceaccount.com"
230+
231+
superuser = false
232+
}
233+
```
234+
235+
See also:
236+
237+
- https://cloud.google.com/docs/authentication/production
238+
- https://cloud.google.com/sql/docs/postgres/iam-logins
218239

219240
---
220241
**Note**

0 commit comments

Comments
 (0)