Skip to content

Commit

Permalink
Add support for GCP IAM impersonation
Browse files Browse the repository at this point in the history
  • Loading branch information
michaellzc committed Aug 29, 2024
1 parent 5213579 commit 9743058
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 32 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
gocloud.dev v0.34.0
golang.org/x/net v0.26.0
golang.org/x/oauth2 v0.10.0
google.golang.org/api v0.134.0
)

require (
Expand Down Expand Up @@ -91,7 +92,6 @@ require (
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/api v0.134.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect
google.golang.org/grpc v1.57.0 // indirect
Expand Down
55 changes: 39 additions & 16 deletions postgresql/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import (

"github.com/blang/semver"
_ "github.com/lib/pq" // PostgreSQL db
"gocloud.dev/gcp"
"gocloud.dev/gcp/cloudsql"
"gocloud.dev/postgres"
_ "gocloud.dev/postgres/awspostgres"
_ "gocloud.dev/postgres/gcppostgres"
"gocloud.dev/postgres/gcppostgres"
"google.golang.org/api/impersonate"
)

type featureName uint
Expand Down Expand Up @@ -157,21 +160,22 @@ type ClientCertificateConfig struct {

// Config - provider config
type Config struct {
Scheme string
Host string
Port int
Username string
Password string
DatabaseUsername string
Superuser bool
SSLMode string
ApplicationName string
Timeout int
ConnectTimeoutSec int
MaxConns int
ExpectedVersion semver.Version
SSLClientCert *ClientCertificateConfig
SSLRootCertPath string
Scheme string
Host string
Port int
Username string
Password string
DatabaseUsername string
Superuser bool
SSLMode string
ApplicationName string
Timeout int
ConnectTimeoutSec int
MaxConns int
ExpectedVersion semver.Version
SSLClientCert *ClientCertificateConfig
SSLRootCertPath string
GCPIAMImpersonateServiceAccount string
}

// Client struct holding connection string
Expand Down Expand Up @@ -280,6 +284,25 @@ func (c *Client) Connect() (*DBConnection, error) {
var err error
if c.config.Scheme == "postgres" {
db, err = sql.Open(proxyDriverName, dsn)
} else if c.config.Scheme == "gcppostgres" && c.config.GCPIAMImpersonateServiceAccount != "" {
ts, err := impersonate.CredentialsTokenSource(context.Background(), impersonate.CredentialsConfig{
TargetPrincipal: c.config.GCPIAMImpersonateServiceAccount,
Scopes: []string{"https://www.googleapis.com/auth/sqlservice.admin"},
})
if err != nil {
return nil, fmt.Errorf("Error creating token source with service account impersonation of %s: %w", c.config.GCPIAMImpersonateServiceAccount, err)
}
client, err := gcp.NewHTTPClient(gcp.DefaultTransport(), ts)
if err != nil {
return nil, fmt.Errorf("Error creating HTTP client with service account impersonation of %s: %w", c.config.GCPIAMImpersonateServiceAccount, err)
}
certSource := cloudsql.NewCertSourceWithIAM(client, ts)
opener := gcppostgres.URLOpener{CertSource: certSource}
dbURL, err := url.Parse(dsn)
if err != nil {
return nil, fmt.Errorf("Error parsing connection string: %w", err)
}
db, err = opener.OpenPostgresURL(context.Background(), dbURL)
} else {
db, err = postgres.Open(context.Background(), dsn)
}
Expand Down
37 changes: 23 additions & 14 deletions postgresql/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package postgresql
import (
"context"
"fmt"
"os"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"os"

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

"gcp_iam_impersonate_service_account": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: "Service account to impersonate when using GCP IAM authentication.",
},

// Conection username can be different than database username with user name mapas (e.g.: in Azure)
// See https://www.postgresql.org/docs/current/auth-username-maps.html
"database_username": {
Expand Down Expand Up @@ -323,19 +331,20 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
}

config := Config{
Scheme: d.Get("scheme").(string),
Host: host,
Port: port,
Username: username,
Password: password,
DatabaseUsername: d.Get("database_username").(string),
Superuser: d.Get("superuser").(bool),
SSLMode: sslMode,
ApplicationName: "Terraform provider",
ConnectTimeoutSec: d.Get("connect_timeout").(int),
MaxConns: d.Get("max_connections").(int),
ExpectedVersion: version,
SSLRootCertPath: d.Get("sslrootcert").(string),
Scheme: d.Get("scheme").(string),
Host: host,
Port: port,
Username: username,
Password: password,
DatabaseUsername: d.Get("database_username").(string),
Superuser: d.Get("superuser").(bool),
SSLMode: sslMode,
ApplicationName: "Terraform provider",
ConnectTimeoutSec: d.Get("connect_timeout").(int),
MaxConns: d.Get("max_connections").(int),
ExpectedVersion: version,
SSLRootCertPath: d.Get("sslrootcert").(string),
GCPIAMImpersonateServiceAccount: d.Get("gcp_iam_impersonate_service_account").(string),
}

if value, ok := d.GetOk("clientcert"); ok {
Expand Down
23 changes: 22 additions & 1 deletion website/docs/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,28 @@ To enable GoCloud for GCP SQL, set `scheme` to `gcppostgres` and `host` to the c
For GCP, GoCloud also requires the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to be set to the service account credentials file.
These credentials can be created here: https://console.cloud.google.com/iam-admin/serviceaccounts

See also: https://cloud.google.com/docs/authentication/production
In addition, the provider supports service account impersonation with the `gcp_iam_impersonate_service_account` option. You must ensure:

- the IAM database user has sufficient permissions to connect to the database, e.g., `roles/cloudsql.instanceUser`
- The principle (IAM user or IAM service accont) behind the `GOOGLE_APPLICATION_CREDENTIALS` has sufficient permissions to impersonate the provided service account. Learn more from [roles for service account authentication](https://cloud.google.com/iam/docs/service-account-permissions).

```hcl
provider "postgresql" {
scheme = "gcppostgres"
host = "test-project/europe-west3/test-instance"
port = 5432
username = "service_account_id@$project_id.iam"
gcp_iam_impersonate_service_account = "service_account_id@$project_id.iam.gserviceaccount.com"
superuser = false
}
```

See also:

- https://cloud.google.com/docs/authentication/production
- https://cloud.google.com/sql/docs/postgres/iam-logins

---
**Note**
Expand Down

0 comments on commit 9743058

Please sign in to comment.