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 credentials JSON flag #1433

Merged
merged 5 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ func NewCommand(opts ...Option) *Command {
"Use bearer token as a source of IAM credentials.")
cmd.PersistentFlags().StringVarP(&c.conf.CredentialsFile, "credentials-file", "c", "",
"Use service account key file as a source of IAM credentials.")
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 credentials as a source of IAM credentials.")
cmd.PersistentFlags().BoolVarP(&c.conf.StructuredLogs, "structured-logs", "l", false,
Expand Down Expand Up @@ -309,6 +311,15 @@ 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("http-port") && !userHasSet("prometheus") && !userHasSet("health-check") {
cmd.logger.Infof("Ignoring --http-port because --prometheus or --health-check was not set")
Expand Down
34 changes: 33 additions & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ func TestNewCommandArguments(t *testing.T) {
CredentialsFile: "/path/to/file",
}),
},
{
desc: "using the JSON credentials",
args: []string{"--json-credentials", `{"json":"goes-here"}`, "proj:region:inst"},
want: withDefaults(&proxy.Config{
CredentialsJSON: `{"json":"goes-here"}`,
}),
},
{
desc: "using the (short) JSON credentials",
args: []string{"-j", `{"json":"goes-here"}`, "proj:region:inst"},
want: withDefaults(&proxy.Config{
CredentialsJSON: `{"json":"goes-here"}`,
}),
},
{
desc: "using the gcloud auth flag",
args: []string{"--gcloud-auth", "proj:region:inst"},
Expand Down Expand Up @@ -493,7 +507,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",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
go.uber.org/zap v1.23.0
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8
google.golang.org/api v0.96.0
google.golang.org/api v0.97.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1705,8 +1705,8 @@ google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6r
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM=
google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ=
google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
Expand Down
8 changes: 8 additions & 0 deletions internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,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 @@ -201,6 +204,11 @@ func (c *Config) DialerOptions(l cloudsql.Logger) ([]cloudsqlconn.Option, error)
opts = append(opts, cloudsqlconn.WithCredentialsFile(
c.CredentialsFile,
))
case c.CredentialsJSON != "":
l.Infof("Authorizing with JSON credentials environment variable")
opts = append(opts, cloudsqlconn.WithCredentialsJSON(
[]byte(c.CredentialsJSON),
))
case c.GcloudAuth:
l.Infof("Authorizing with gcloud user credentials")
ts, err := gcloud.TokenSource()
Expand Down
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"
"net/http"
"net/http/httputil"
"os"
Expand Down Expand Up @@ -54,6 +55,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
22 changes: 20 additions & 2 deletions tests/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,24 @@ func TestMySQLAuthWithCredentialsFile(t *testing.T) {
"mysql", cfg.FormatDSN())
}

func TestMySQLHealthCheck(t *testing.T) {
testHealthCheck(t, *mysqlConnName)
func TestMySQLAuthWithCredentialsJSON(t *testing.T) {
if testing.Short() {
t.Skip("skipping MySQL integration tests")
}
requireMySQLVars(t)
creds := keyfile(t)
_, _, cleanup := removeAuthEnvVar(t)
defer cleanup()

cfg := mysql.Config{
User: *mysqlUser,
Passwd: *mysqlPass,
DBName: *mysqlDB,
AllowNativePasswords: true,
Addr: "127.0.0.1:3306",
Net: "tcp",
}
proxyConnTest(t,
[]string{"--json-credentials", creds, *mysqlConnName},
"mysql", cfg.FormatDSN())
}
16 changes: 16 additions & 0 deletions tests/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,22 @@ func TestPostgresAuthWithCredentialsFile(t *testing.T) {
"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",
*postgresUser, *postgresPass, *postgresDB)
proxyConnTest(t,
[]string{"--json-credentials", string(creds), *postgresConnName},
"pgx", dsn)
}

func TestAuthWithGcloudAuth(t *testing.T) {
if testing.Short() {
t.Skip("skipping Postgres integration tests")
Expand Down
16 changes: 16 additions & 0 deletions tests/sqlserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ func TestSQLServerAuthWithCredentialsFile(t *testing.T) {
"sqlserver", dsn)
}

func TestSQLServerAuthWithCredentialsJSON(t *testing.T) {
if testing.Short() {
t.Skip("skipping SQL Server integration tests")
}
requireSQLServerVars(t)
creds := keyfile(t)
_, _, cleanup := removeAuthEnvVar(t)
defer cleanup()

dsn := fmt.Sprintf("sqlserver://%s:%[email protected]?database=%s",
*sqlserverUser, *sqlserverPass, *sqlserverDB)
proxyConnTest(t,
[]string{"--json-credentials", creds, *sqlserverConnName},
"sqlserver", dsn)
}

func TestSQLServerHealthCheck(t *testing.T) {
testHealthCheck(t, *sqlserverConnName)
}