summaryrefslogtreecommitdiffstats
path: root/pkg/v1/google/auth.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/v1/google/auth.go')
-rw-r--r--pkg/v1/google/auth.go179
1 files changed, 179 insertions, 0 deletions
diff --git a/pkg/v1/google/auth.go b/pkg/v1/google/auth.go
new file mode 100644
index 0000000..11ae397
--- /dev/null
+++ b/pkg/v1/google/auth.go
@@ -0,0 +1,179 @@
+// Copyright 2018 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "os/exec"
+ "time"
+
+ "github.com/google/go-containerregistry/pkg/authn"
+ "github.com/google/go-containerregistry/pkg/logs"
+ "golang.org/x/oauth2"
+ googauth "golang.org/x/oauth2/google"
+)
+
+const cloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
+
+// GetGcloudCmd is exposed so we can test this.
+var GetGcloudCmd = func() *exec.Cmd {
+ // This is odd, but basically what docker-credential-gcr does.
+ //
+ // config-helper is undocumented, but it's purportedly the only supported way
+ // of accessing tokens (`gcloud auth print-access-token` is discouraged).
+ //
+ // --force-auth-refresh means we are getting a token that is valid for about
+ // an hour (we reuse it until it's expired).
+ return exec.Command("gcloud", "config", "config-helper", "--force-auth-refresh", "--format=json(credential)")
+}
+
+// NewEnvAuthenticator returns an authn.Authenticator that generates access
+// tokens from the environment we're running in.
+//
+// See: https://godoc.org/golang.org/x/oauth2/google#FindDefaultCredentials
+func NewEnvAuthenticator() (authn.Authenticator, error) {
+ ts, err := googauth.DefaultTokenSource(context.Background(), cloudPlatformScope)
+ if err != nil {
+ return nil, err
+ }
+
+ token, err := ts.Token()
+ if err != nil {
+ return nil, err
+ }
+
+ return &tokenSourceAuth{oauth2.ReuseTokenSource(token, ts)}, nil
+}
+
+// NewGcloudAuthenticator returns an oauth2.TokenSource that generates access
+// tokens by shelling out to the gcloud sdk.
+func NewGcloudAuthenticator() (authn.Authenticator, error) {
+ if _, err := exec.LookPath("gcloud"); err != nil {
+ // gcloud is not available, fall back to anonymous
+ logs.Warn.Println("gcloud binary not found")
+ return authn.Anonymous, nil
+ }
+
+ ts := gcloudSource{GetGcloudCmd}
+
+ // Attempt to fetch a token to ensure gcloud is installed and we can run it.
+ token, err := ts.Token()
+ if err != nil {
+ return nil, err
+ }
+
+ return &tokenSourceAuth{oauth2.ReuseTokenSource(token, ts)}, nil
+}
+
+// NewJSONKeyAuthenticator returns a Basic authenticator which uses Service Account
+// as a way of authenticating with Google Container Registry.
+// More information: https://cloud.google.com/container-registry/docs/advanced-authentication#json_key_file
+func NewJSONKeyAuthenticator(serviceAccountJSON string) authn.Authenticator {
+ return &authn.Basic{
+ Username: "_json_key",
+ Password: serviceAccountJSON,
+ }
+}
+
+// NewTokenAuthenticator returns an oauth2.TokenSource that generates access
+// tokens by using the Google SDK to produce JWT tokens from a Service Account.
+// More information: https://godoc.org/golang.org/x/oauth2/google#JWTAccessTokenSourceFromJSON
+func NewTokenAuthenticator(serviceAccountJSON string, scope string) (authn.Authenticator, error) {
+ ts, err := googauth.JWTAccessTokenSourceFromJSON([]byte(serviceAccountJSON), scope)
+ if err != nil {
+ return nil, err
+ }
+
+ return &tokenSourceAuth{oauth2.ReuseTokenSource(nil, ts)}, nil
+}
+
+// NewTokenSourceAuthenticator converts an oauth2.TokenSource into an authn.Authenticator.
+func NewTokenSourceAuthenticator(ts oauth2.TokenSource) authn.Authenticator {
+ return &tokenSourceAuth{ts}
+}
+
+// tokenSourceAuth turns an oauth2.TokenSource into an authn.Authenticator.
+type tokenSourceAuth struct {
+ oauth2.TokenSource
+}
+
+// Authorization implements authn.Authenticator.
+func (tsa *tokenSourceAuth) Authorization() (*authn.AuthConfig, error) {
+ token, err := tsa.Token()
+ if err != nil {
+ return nil, err
+ }
+
+ return &authn.AuthConfig{
+ Username: "_token",
+ Password: token.AccessToken,
+ }, nil
+}
+
+// gcloudOutput represents the output of the gcloud command we invoke.
+//
+// `gcloud config config-helper --format=json(credential)` looks something like:
+//
+// {
+// "credential": {
+// "access_token": "supersecretaccesstoken",
+// "token_expiry": "2018-12-02T04:08:13Z"
+// }
+// }
+type gcloudOutput struct {
+ Credential struct {
+ AccessToken string `json:"access_token"`
+ TokenExpiry string `json:"token_expiry"`
+ } `json:"credential"`
+}
+
+type gcloudSource struct {
+ // This is passed in so that we mock out gcloud and test Token.
+ exec func() *exec.Cmd
+}
+
+// Token implements oauath2.TokenSource.
+func (gs gcloudSource) Token() (*oauth2.Token, error) {
+ cmd := gs.exec()
+ var out bytes.Buffer
+ cmd.Stdout = &out
+
+ // Don't attempt to interpret stderr, just pass it through.
+ cmd.Stderr = logs.Warn.Writer()
+
+ if err := cmd.Run(); err != nil {
+ return nil, fmt.Errorf("error executing `gcloud config config-helper`: %w", err)
+ }
+
+ creds := gcloudOutput{}
+ if err := json.Unmarshal(out.Bytes(), &creds); err != nil {
+ return nil, fmt.Errorf("failed to parse `gcloud config config-helper` output: %w", err)
+ }
+
+ expiry, err := time.Parse(time.RFC3339, creds.Credential.TokenExpiry)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse gcloud token expiry: %w", err)
+ }
+
+ token := oauth2.Token{
+ AccessToken: creds.Credential.AccessToken,
+ Expiry: expiry,
+ }
+
+ return &token, nil
+}