Skip to content
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

feat: add support for JSON credentials #188

Merged
merged 4 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
43 changes: 40 additions & 3 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -329,13 +342,36 @@ func TestNewCommandWithErrors(t *testing.T) {
desc: "when both token and gcloud auth are set",
args: []string{
"--token", "my-token",
"--gcloud-auth", "proj:region:inst"},
"--gcloud-auth",
"projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
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",
"projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
desc: "when both token and credentials JSON are set",
args: []string{
"--token", "a-token",
"--json-credentials", `{"json":"here"}`,
"projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
desc: "when both credentials file and credentials JSON are set",
args: []string{
"--credentials-file", "/a/file",
"--json-credentials", `{"json":"here"}`,
"projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
desc: "when both gcloud auth and credentials JSON are set",
args: []string{
"--gcloud-auth",
"--json-credentials", `{"json":"here"}`,
"projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
desc: "when the unix socket query param contains multiple values",
Expand All @@ -359,7 +395,8 @@ func TestNewCommandWithErrors(t *testing.T) {
},
{
desc: "using an invalid url for host flag",
args: []string{"--host", "https://invalid:url[/]", "proj:region:inst"},
args: []string{"--host", "https://invalid:url[/]",
"projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
desc: "using fuse-tmp-dir without fuse",
Expand Down
8 changes: 8 additions & 0 deletions internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
Expand Down
16 changes: 16 additions & 0 deletions tests/alloydb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,19 @@ func TestAuthWithGcloudAuth(t *testing.T) {
[]string{"--gcloud-auth", *alloydbConnName},
"pgx", dsn)
}

func TestPostgresAuthWithCredentialsJSON(t *testing.T) {
if testing.Short() {
t.Skip("skipping Postgres integration tests")
}
requirePostgresVars(t)
creds := keyfile(t)
_, _, cleanup := removeAuthEnvVar(t)
defer cleanup()

dsn := fmt.Sprintf("host=localhost user=%s password=%s database=%s sslmode=disable",
*alloydbUser, *alloydbPass, *alloydbDB)
proxyConnTest(t,
[]string{"--json-credentials", string(creds), *alloydbConnName},
"pgx", dsn)
}
13 changes: 13 additions & 0 deletions tests/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package tests
import (
"context"
"database/sql"
"io/ioutil"
"os"
"testing"
"time"
Expand Down Expand Up @@ -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)
Expand Down