From c452b9cb0fb4e88acacb88e66f621fd57ffc909a Mon Sep 17 00:00:00 2001 From: Eno Compton Date: Tue, 6 Dec 2022 14:42:59 -0700 Subject: [PATCH] feat: add support for JSON credentials This is a port of https://github.com/GoogleCloudPlatform/cloud-sql-proxy/pull/1433. --- cmd/root.go | 12 ++++++++++++ cmd/root_test.go | 33 ++++++++++++++++++++++++++++++++- internal/proxy/proxy.go | 8 ++++++++ tests/connection_test.go | 13 +++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index a27c5071..a22ea6cb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -162,6 +162,8 @@ without having to manage any client SSL certificates.`, "Bearer token used for authorization.") cmd.PersistentFlags().StringVarP(&c.conf.CredentialsFile, "credentials-file", "c", "", "Path to a service account key to use for authentication.") + cmd.PersistentFlags().StringVarP(&c.conf.CredentialsJSON, "json-credentials", "j", "", + "Use service account key JSON as a source of IAM credentials.") cmd.PersistentFlags().BoolVarP(&c.conf.GcloudAuth, "gcloud-auth", "g", false, "Use gcloud's user configuration to retrieve a token for authentication.") cmd.PersistentFlags().BoolVarP(&c.conf.StructuredLogs, "structured-logs", "l", false, @@ -259,6 +261,16 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error { if conf.CredentialsFile != "" && conf.GcloudAuth { return newBadCommandError("cannot specify --credentials-file and --gcloud-auth flags at the same time") } + if conf.CredentialsJSON != "" && conf.Token != "" { + return newBadCommandError("cannot specify --json-credentials and --token flags at the same time") + } + if conf.CredentialsJSON != "" && conf.CredentialsFile != "" { + return newBadCommandError("cannot specify --json-credentials and --credentials-file flags at the same time") + } + if conf.CredentialsJSON != "" && conf.GcloudAuth { + return newBadCommandError("cannot specify --json-credentials and --gcloud-auth flags at the same time") + } + if userHasSet("alloydbadmin-api-endpoint") { _, err := url.Parse(conf.APIEndpointURL) if err != nil { diff --git a/cmd/root_test.go b/cmd/root_test.go index 310b3e37..be131c1f 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -208,6 +208,19 @@ func TestNewCommandArguments(t *testing.T) { APIEndpointURL: "https://test.googleapis.com", }), }, + { + desc: "using the JSON credentials", + args: []string{"--json-credentials", `{"json":"goes-here"}`, "projects/proj/locations/region/clusters/clust/instances/inst"}, want: withDefaults(&proxy.Config{ + CredentialsJSON: `{"json":"goes-here"}`, + }), + }, + { + desc: "using the (short) JSON credentials", + args: []string{"-j", `{"json":"goes-here"}`, "projects/proj/locations/region/clusters/clust/instances/inst"}, + want: withDefaults(&proxy.Config{ + CredentialsJSON: `{"json":"goes-here"}`, + }), + }, } for _, tc := range tcs { @@ -335,7 +348,25 @@ func TestNewCommandWithErrors(t *testing.T) { desc: "when both gcloud auth and credentials file are set", args: []string{ "--gcloud-auth", - "--credential-file", "/path/to/file", "proj:region:inst"}, + "--credentials-file", "/path/to/file", "proj:region:inst"}, + }, + { + desc: "when both token and credentials JSON are set", + args: []string{ + "--token", "a-token", + "--json-credentials", `{"json":"here"}`, "proj:region:inst"}, + }, + { + desc: "when both credentials file and credentials JSON are set", + args: []string{ + "--credentials-file", "/a/file", + "--json-credentials", `{"json":"here"}`, "proj:region:inst"}, + }, + { + desc: "when both gcloud auth and credentials JSON are set", + args: []string{ + "--gcloud-auth", + "--json-credentials", `{"json":"here"}`, "proj:region:inst"}, }, { desc: "when the unix socket query param contains multiple values", diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 9a8395ac..2b0bf6ef 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -60,6 +60,9 @@ type Config struct { // CredentialsFile is the path to a service account key. CredentialsFile string + // CredentialsJSON is a JSON representation of the service account key. + CredentialsJSON string + // GcloudAuth set whether to use Gcloud's config helper to retrieve a // token for authentication. GcloudAuth bool @@ -131,6 +134,11 @@ func (c *Config) DialerOptions(l alloydb.Logger) ([]alloydbconn.Option, error) { 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), + )) default: l.Infof("Authorizing with Application Default Credentials") } diff --git a/tests/connection_test.go b/tests/connection_test.go index 4cf9d7b3..59945878 100644 --- a/tests/connection_test.go +++ b/tests/connection_test.go @@ -17,6 +17,7 @@ package tests import ( "context" "database/sql" + "io/ioutil" "os" "testing" "time" @@ -51,6 +52,18 @@ func removeAuthEnvVar(t *testing.T) (*oauth2.Token, string, func()) { } } +func keyfile(t *testing.T) string { + path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") + if path == "" { + t.Fatal("GOOGLE_APPLICATION_CREDENTIALS not set") + } + creds, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("io.ReadAll(): %v", err) + } + return string(creds) +} + // proxyConnTest is a test helper to verify the proxy works with a basic connectivity test. func proxyConnTest(t *testing.T, args []string, driver, dsn string) { ctx, cancel := context.WithTimeout(context.Background(), connTestTimeout)