Skip to content

Commit

Permalink
Enable service account keys to be provided in base64 (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
soeirosantos authored Oct 13, 2021
1 parent 484c4a5 commit 2909135
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 8 deletions.
2 changes: 1 addition & 1 deletion DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ steps:

`drone-gke` requires a Google service account and uses its [JSON credential file][service-account] to authenticate.

This must be passed to the plugin under the target `token`.
This must be passed to the plugin under the target `token`. If provided in base64 format, the plugin will decode it internally.

The plugin infers the GCP project from the JSON credentials (`token`) and retrieves the GKE cluster credentials.

Expand Down
21 changes: 17 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,13 @@ func run(c *cli.Context) error {
return err
}

token := decodeToken(c.String("token"))

// Use project if explicitly stated, otherwise infer from the service account token.
project := c.String("project")
if project == "" {
log("Parsing Project ID from credentials\n")
project = getProjectFromToken(c.String("token"))
project = getProjectFromToken(token)
if project == "" {
return fmt.Errorf("Missing required param: project")
}
Expand Down Expand Up @@ -250,7 +252,7 @@ func run(c *cli.Context) error {
runner := NewBasicRunner("", environ, os.Stdout, os.Stderr)

// Auth with gcloud and fetch kubectl credentials
if err := fetchCredentials(c, project, runner); err != nil {
if err := fetchCredentials(c, token, project, runner); err != nil {
return err
}

Expand Down Expand Up @@ -320,6 +322,17 @@ func run(c *cli.Context) error {
return nil
}

// decodeToken decodes the service account key if provided as base64
func decodeToken(token string) string {
if decodedToken, err := base64.StdEncoding.DecodeString(token); err == nil {
// if no error then the SA key is base64 encoded
token = string(decodedToken)
} else {
fmt.Println("info: skipping base64 credentials decode")
}
return token
}

// checkParams checks required params
func checkParams(c *cli.Context) error {
if c.String("token") == "" {
Expand Down Expand Up @@ -506,10 +519,10 @@ func parseSecrets() (map[string]string, error) {
}

// fetchCredentials authenticates with gcloud and fetches credentials for kubectl
func fetchCredentials(c *cli.Context, project string, runner Runner) error {
func fetchCredentials(c *cli.Context, token, project string, runner Runner) error {
// Write credentials to tmp file to be picked up by the 'gcloud' command.
// This is inside the ephemeral plugin container, not on the host.
err := ioutil.WriteFile(keyPath, []byte(c.String("token")), 0600)
err := ioutil.WriteFile(keyPath, []byte(token), 0600)
if err != nil {
return fmt.Errorf("Error writing token file: %s\n", err)
}
Expand Down
60 changes: 57 additions & 3 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ func TestFetchCredentials(t *testing.T) {
testRunner.On("Run", []string{"gcloud", "auth", "activate-service-account", "--key-file", "/tmp/gcloud.json"}).Return(nil)
testRunner.On("Run", []string{"gcloud", "container", "clusters", "get-credentials", "cluster-0", "--project", "test-project", "--zone", "us-east1-b"}).Return(nil)
testRunner.On("Run", []string{"gcloud", "container", "clusters", "get-credentials", "cluster-0", "--project", "test-project", "--region", "us-west1"}).Return(nil)
zonalErr := fetchCredentials(zonalContext, "test-project", testRunner)
regionalErr := fetchCredentials(regionalContext, "test-project", testRunner)
zonalErr := fetchCredentials(zonalContext, zonalContext.String("token"), "test-project", testRunner)
regionalErr := fetchCredentials(regionalContext, regionalContext.String("token"), "test-project", testRunner)
testRunner.AssertExpectations(t)
assert.NoError(t, zonalErr)
assert.NoError(t, regionalErr)
Expand All @@ -202,7 +202,7 @@ func TestFetchCredentials(t *testing.T) {
// Run() error
testRunner = new(MockedRunner)
testRunner.On("Run", []string{"gcloud", "auth", "activate-service-account", "--key-file", "/tmp/gcloud.json"}).Return(fmt.Errorf("e"))
err = fetchCredentials(zonalContext, "test-project", testRunner)
err = fetchCredentials(zonalContext, zonalContext.String("token"), "test-project", testRunner)
testRunner.AssertExpectations(t)
assert.Error(t, err)
}
Expand Down Expand Up @@ -838,3 +838,57 @@ func TestSetDryRunFlag(t *testing.T) {
})
}
}

func Test_decodeToken(t *testing.T) {
serviceAccountKey := `{
"type": "service_account",
"project_id": "nyt-project-dev",
"private_key_id": "key-id",
"private_key": "shhh",
"client_email": "[email protected]",
"client_id": "client-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gke-sa%40nyt-project-dev.iam.gserviceaccount.com"
}`

encodedServiceAccountKey := "ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAibnl0LXByb2plY3QtZGV2IiwK" +
"ICAicHJpdmF0ZV9rZXlfaWQiOiAia2V5LWlkIiwKICAicHJpdmF0ZV9rZXkiOiAic2hoaCIsCiAgImNsaWVudF9lbWFpbCI6ICJna2Utc2FAbnl0LX" +
"Byb2plY3QtZGV2LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogImNsaWVudC1pZCIsCiAgImF1dGhfdXJpIjogImh0dHBz" +
"Oi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL2" +
"9hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEv" +
"Y2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2" +
"drZS1zYSU0MG55dC1wcm9qZWN0LWRldi5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQ=="

type args struct {
token string
}
tests := []struct {
name string
args args
want string
}{
{
name: "encoded-token",
args: args{
encodedServiceAccountKey,
},
want: serviceAccountKey,
},
{
name: "non-encoded-token",
args: args{
serviceAccountKey,
},
want: serviceAccountKey,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := decodeToken(tt.args.token); got != tt.want {
t.Errorf("decodeToken() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 2909135

Please sign in to comment.