Skip to content

Commit

Permalink
service account impersonation
Browse files Browse the repository at this point in the history
  • Loading branch information
gartnera committed Jun 19, 2022
1 parent 63659b8 commit 313c63a
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 24 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@ wget -O - https://github.com/gartnera/gcloud/releases/download/v0.0.7/gcloud_0.0
go install github.com/gartnera/gcloud@latest
```

## Supported Commands
## Current Commands

- `gcloud auth application-default login` (code flow)
- `gcloud auth application-default print-access-token`
- `gcloud auth print-access-token`
- `gcloud auth configure-docker`
- `gcloud auth docker-helper`

- `gcloud container clusters get-credentials`
- `gcloud config config-helper --format=client.authentication.k8s.io/v1` (used by `gcloud container clusters get-credentials`)

## Current Features

- service account impersonation via `GOOGLE_IMPERSONATE_SERVICE_ACCOUNT`
2 changes: 2 additions & 0 deletions auth/root_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func GetRootCmd() *cobra.Command {

rootCmd.AddCommand(configureDockerCmd)
rootCmd.AddCommand(dockerHelperCmd)

rootCmd.AddCommand(printAccessTokenCmd)
rootCmdInitDone = true
}
return rootCmd
Expand Down
62 changes: 56 additions & 6 deletions auth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/kirsle/configdir"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/impersonate"
"google.golang.org/api/option"
)

type TokenSourceWithCacheKey interface {
Expand All @@ -21,29 +23,49 @@ type TokenSourceWithCacheKey interface {

// TokenSource returns a cached application default credentials or falls back to the compute token source
func TokenSource() (oauth2.TokenSource, error) {
return NewCachingTokenSource(context.Background())
return maybeGetImpersonatedTokenSource(context.Background())
}

func Token() (*oauth2.Token, error) {
ts, err := TokenSource()
if err != nil {
return nil, fmt.Errorf("unable to get tokensource: %w", err)
return nil, fmt.Errorf("unable to get token: %w", err)
}
return ts.Token()
}

func NewCachingTokenSource(ctx context.Context) (oauth2.TokenSource, error) {
func maybeGetImpersonatedTokenSource(ctx context.Context) (oauth2.TokenSource, error) {
mainTs, err := getMainTokenSource(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get main tokensource: %w", err)
}
email := os.Getenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT")
if email != "" {
impersonateTs, err := NewGoogleImpersonateTokenSourceWrapper(ctx, email, mainTs)
if err != nil {
return nil, err
}
return NewCachingTokenSource(impersonateTs)
}
return mainTs, nil
}

func getMainTokenSource(ctx context.Context) (oauth2.TokenSource, error) {
var ts TokenSourceWithCacheKey
var err error
ts, err = ReadApplicationCredentials()
if err != nil {
ts, err = NewGoogleComputeTokenSourceWrapper(context.Background())
ts, err = NewGoogleComputeTokenSourceWrapper(ctx)
}
if err != nil {
return nil, fmt.Errorf("unable to get tokensource: %w", err)
}
return NewCachingTokenSource(ts)
}

func NewCachingTokenSource(ts TokenSourceWithCacheKey) (oauth2.TokenSource, error) {
cacheDir := configdir.LocalCache("gcloud-gartnera-tokens")
err = os.MkdirAll(cacheDir, 0755)
err := os.MkdirAll(cacheDir, 0755)
if err != nil {
return nil, fmt.Errorf("unable to ensure token cache directory: %w", err)
}
Expand All @@ -57,7 +79,7 @@ type CachingTokenSource struct {
ts TokenSourceWithCacheKey
cacheDir string
tok *oauth2.Token
lock sync.RWMutex
lock sync.Mutex
}

func (c *CachingTokenSource) tokenFromDisk() (*oauth2.Token, error) {
Expand Down Expand Up @@ -151,3 +173,31 @@ func (m *GoogleComputeTokenSourceWrapper) Token() (*oauth2.Token, error) {
func (m *GoogleComputeTokenSourceWrapper) CacheKey() string {
return "compute-metadata"
}

func NewGoogleImpersonateTokenSourceWrapper(ctx context.Context, email string, parentTs oauth2.TokenSource) (*GoogleImpersonateTokenSourceWrapper, error) {
config := impersonate.CredentialsConfig{
TargetPrincipal: email,
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
}
ts, err := impersonate.CredentialsTokenSource(ctx, config, option.WithTokenSource(parentTs))
if err != nil {
return nil, fmt.Errorf("unable to get impersonated token source: %w", err)
}
return &GoogleImpersonateTokenSourceWrapper{
ts: ts,
email: email,
}, nil
}

type GoogleImpersonateTokenSourceWrapper struct {
ts oauth2.TokenSource
email string
}

func (m *GoogleImpersonateTokenSourceWrapper) Token() (*oauth2.Token, error) {
return m.ts.Token()
}

func (m *GoogleImpersonateTokenSourceWrapper) CacheKey() string {
return m.email
}
19 changes: 19 additions & 0 deletions auth/token_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package auth

import (
"fmt"

"github.com/spf13/cobra"
)

var printAccessTokenCmd = &cobra.Command{
Use: "print-access-token",
RunE: func(cmd *cobra.Command, args []string) error {
tok, err := Token()
if err != nil {
return fmt.Errorf("unable to get token: %w", err)
}
fmt.Println(tok.AccessToken)
return nil
},
}
19 changes: 10 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ go 1.18
require (
cloud.google.com/go/container v1.2.0
github.com/docker/cli v20.10.17+incompatible
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf
google.golang.org/api v0.84.0
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac
gopkg.in/yaml.v3 v3.0.1
k8s.io/client-go v0.24.2
)

require (
cloud.google.com/go/compute v1.3.0 // indirect
cloud.google.com/go/compute v1.6.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/go-logr/logr v1.2.0 // indirect
Expand All @@ -23,25 +25,24 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/imdario/mergo v0.3.5 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/api v0.70.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/grpc v1.44.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools/v3 v3.3.0 // indirect
Expand Down
Loading

0 comments on commit 313c63a

Please sign in to comment.