-
Notifications
You must be signed in to change notification settings - Fork 16
feat: add support for service account impersonation #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,7 @@ export ALLOYDB_PASS="postgres-password" | |
| export ALLOYDB_DB="postgres-db-name" | ||
|
|
||
| export GOOGLE_APPLICATION_CREDENTIALS="path/to/credentials" | ||
|
|
||
| # Requires the impersonating IAM principal to have | ||
| # roles/iam.serviceAccountTokenCreator | ||
| export IMPERSONATED_USER="[email protected]" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -221,6 +221,19 @@ func TestNewCommandArguments(t *testing.T) { | |
| CredentialsJSON: `{"json":"goes-here"}`, | ||
| }), | ||
| }, | ||
| { | ||
| desc: "", | ||
| args: []string{"--impersonate-service-account", | ||
| "[email protected],[email protected],[email protected]", | ||
| "projects/proj/locations/region/clusters/clust/instances/inst"}, | ||
| want: withDefaults(&proxy.Config{ | ||
| ImpersonateTarget: "[email protected]", | ||
| ImpersonateDelegates: []string{ | ||
| "[email protected]", | ||
| "[email protected]", | ||
| }, | ||
| }), | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range tcs { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,9 @@ import ( | |
| "github.com/GoogleCloudPlatform/alloydb-auth-proxy/alloydb" | ||
| "github.com/GoogleCloudPlatform/alloydb-auth-proxy/internal/gcloud" | ||
| "golang.org/x/oauth2" | ||
| "google.golang.org/api/impersonate" | ||
| "google.golang.org/api/option" | ||
| "google.golang.org/api/sqladmin/v1" | ||
| ) | ||
|
|
||
| // InstanceConnConfig holds the configuration for an individual instance | ||
|
|
@@ -104,43 +107,102 @@ type Config struct { | |
| // regardless of any open connections. | ||
| WaitOnClose time.Duration | ||
|
|
||
| // ImpersonateTarget is the service account to impersonate. The IAM | ||
| // principal doing the impersonation must have the | ||
| // roles/iam.serviceAccountTokenCreator role. | ||
| ImpersonateTarget string | ||
| // ImpersonateDelegates are the intermediate service accounts through which | ||
| // the impersonation is achieved. Each delegate must have the | ||
| // roles/iam.serviceAccountTokenCreator role. | ||
| ImpersonateDelegates []string | ||
|
|
||
| // StructuredLogs sets all output to use JSON in the LogEntry format. | ||
| // See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry | ||
| StructuredLogs bool | ||
| } | ||
|
|
||
| // DialerOptions builds appropriate list of options from the Config | ||
| // values for use by alloydbconn.NewClient() | ||
| func (c *Config) DialerOptions(l alloydb.Logger) ([]alloydbconn.Option, error) { | ||
| opts := []alloydbconn.Option{ | ||
| alloydbconn.WithUserAgent(c.UserAgent), | ||
| func (c *Config) credentialsOpt(l alloydb.Logger) (alloydbconn.Option, error) { | ||
| // If service account impersonation is configured, set up an impersonated | ||
| // credentials token source. | ||
| if c.ImpersonateTarget != "" { | ||
| var iopts []option.ClientOption | ||
| switch { | ||
| case c.Token != "": | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this switch statement from line 130-150 are much identical to lines 166-189. Could you somehow
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are making significant changes to this PR we should make sure to update the Cloud SQL go connector as well
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's fix this in the v2 proxy and port the change here. |
||
| l.Infof("Impersonating service account with OAuth2 token") | ||
| iopts = append(iopts, option.WithTokenSource( | ||
| oauth2.StaticTokenSource(&oauth2.Token{AccessToken: c.Token}), | ||
| )) | ||
| case c.CredentialsFile != "": | ||
| l.Infof("Impersonating service account with the credentials file at %q", c.CredentialsFile) | ||
| iopts = append(iopts, option.WithCredentialsFile(c.CredentialsFile)) | ||
| case c.CredentialsJSON != "": | ||
| l.Infof("Impersonating service account with JSON credentials environment variable") | ||
| iopts = append(iopts, option.WithCredentialsJSON([]byte(c.CredentialsJSON))) | ||
| case c.GcloudAuth: | ||
| l.Infof("Impersonating service account with gcloud user credentials") | ||
| ts, err := gcloud.TokenSource() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| iopts = append(iopts, option.WithTokenSource(ts)) | ||
| default: | ||
| l.Infof("Impersonating service account with Application Default Credentials") | ||
| } | ||
| ts, err := impersonate.CredentialsTokenSource( | ||
| context.Background(), | ||
| impersonate.CredentialsConfig{ | ||
| TargetPrincipal: c.ImpersonateTarget, | ||
| Delegates: c.ImpersonateDelegates, | ||
| Scopes: []string{sqladmin.SqlserviceAdminScope}, | ||
| }, | ||
| iopts..., | ||
| ) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return alloydbconn.WithTokenSource(ts), nil | ||
| } | ||
| opts = append(opts, alloydbconn.WithAdminAPIEndpoint(c.APIEndpointURL)) | ||
| // Otherwise, configure credentials as usual. | ||
| switch { | ||
| case c.Token != "": | ||
| l.Infof("Authorizing with the -token flag") | ||
| opts = append(opts, alloydbconn.WithTokenSource( | ||
| l.Infof("Authorizing with OAuth2 token") | ||
| return alloydbconn.WithTokenSource( | ||
| oauth2.StaticTokenSource(&oauth2.Token{AccessToken: c.Token}), | ||
| )) | ||
| ), nil | ||
| case c.CredentialsFile != "": | ||
| l.Infof("Authorizing with the credentials file at %q", c.CredentialsFile) | ||
| opts = append(opts, alloydbconn.WithCredentialsFile( | ||
| c.CredentialsFile, | ||
| )) | ||
| return alloydbconn.WithCredentialsFile(c.CredentialsFile), nil | ||
| case c.CredentialsJSON != "": | ||
| l.Infof("Authorizing with JSON credentials environment variable") | ||
| return alloydbconn.WithCredentialsJSON([]byte(c.CredentialsJSON)), nil | ||
| case c.GcloudAuth: | ||
| l.Infof("Authorizing with gcloud user credentials") | ||
| ts, err := gcloud.TokenSource() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| opts = append(opts, alloydbconn.WithTokenSource(ts)) | ||
| case c.CredentialsJSON != "": | ||
| l.Infof("Authorizing with JSON credentials environment variable") | ||
| opts = append(opts, alloydbconn.WithCredentialsJSON( | ||
| []byte(c.CredentialsJSON), | ||
| )) | ||
| return alloydbconn.WithTokenSource(ts), nil | ||
| default: | ||
| l.Infof("Authorizing with Application Default Credentials") | ||
| // Return no-op options to avoid having to handle nil in caller code | ||
| return alloydbconn.WithOptions(), nil | ||
| } | ||
| } | ||
|
|
||
| // DialerOptions builds appropriate list of options from the Config | ||
| // values for use by alloydbconn.NewClient() | ||
| func (c *Config) DialerOptions(l alloydb.Logger) ([]alloydbconn.Option, error) { | ||
| opts := []alloydbconn.Option{ | ||
| alloydbconn.WithUserAgent(c.UserAgent), | ||
| } | ||
| co, err := c.credentialsOpt(l) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| opts = append(opts, co) | ||
|
|
||
| if c.APIEndpointURL != "" { | ||
| opts = append(opts, alloydbconn.WithAdminAPIEndpoint(c.APIEndpointURL)) | ||
| } | ||
|
|
||
| return opts, nil | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.