Skip to content

Commit

Permalink
feat: make ADC the default option for GCP authentication when using g…
Browse files Browse the repository at this point in the history
…o-containerregistry (#9456)
  • Loading branch information
renzodavid9 authored Jul 2, 2024
1 parent 59d2f1f commit e976e81
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 17 deletions.
48 changes: 42 additions & 6 deletions pkg/skaffold/docker/authenticators.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ limitations under the License.
package docker

import (
"context"
"strings"
"sync"

"github.com/docker/cli/cli/config"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/google"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
)

var primaryKeychain = &Keychain{
Expand Down Expand Up @@ -76,17 +79,17 @@ func (a *lockedAuthenticator) Authorization() (*authn.AuthConfig, error) {
}

// Create a new authenticator for a given reference
// 1. If `gcloud` is configured, we use google.NewGcloudAuthenticator(). It is more efficient because it reuses tokens.
// 1. If `gcloud` is configured with given registry, we try to use a Google authenticator
// 2. If something else is configured, we use that authenticator
// 3. If nothing is configured, we check if `gcloud` can be used
// 4. Default to anonymous
func (a *Keychain) newAuthenticator(res authn.Resource) authn.Authenticator {
registry := res.RegistryStr()

// 1. Use google.NewGcloudAuthenticator() authenticator if `gcloud` is configured
// 1. Try getting a Google authenticator if docker config configured to use gcloud
cfg, err := config.Load(a.configDir)
if err == nil && cfg.CredentialHelpers[registry] == "gcloud" {
if auth, err := google.NewGcloudAuthenticator(); err == nil {
if auth := getGoogleAuthenticator(); auth != nil {
return auth
}
}
Expand All @@ -97,13 +100,46 @@ func (a *Keychain) newAuthenticator(res authn.Resource) authn.Authenticator {
return auth
}

// 3. Try gcloud for *.gcr.io
if registry == "gcr.io" || strings.HasSuffix(registry, ".gcr.io") {
if auth, err := google.NewGcloudAuthenticator(); err == nil {
// 3. Try Google authenticator for known registries (same logic used by go-containerregistry)
if isGoogleRegistry(registry) {
if auth := getGoogleAuthenticator(); auth != nil {
return auth
}
}

// 4. Default to anonymous
return authn.Anonymous
}

func getGoogleAuthenticator() authn.Authenticator {
// 1. First we try to create an authenticator that uses Application Default Credentials
auth, err := google.NewEnvAuthenticator()
if err == nil && auth != authn.Anonymous {
log.Entry(context.TODO()).Debugf("using Application Default Credentials authenticator")
return auth
}

if err != nil {
log.Entry(context.TODO()).Debugf("failed to get Application Default Credentials auth: %v", err)
}

// 2. Try to create authenticator that uses gcloud
auth, err = google.NewGcloudAuthenticator()
if err == nil && auth != authn.Anonymous {
log.Entry(context.TODO()).Debugf("using gcloud authenticator")
return auth
}

if err != nil {
log.Entry(context.TODO()).Debugf("failed to get gcloud auth: %v", err)
}

return nil
}

func isGoogleRegistry(host string) bool {
return host == "gcr.io" ||
strings.HasSuffix(host, ".gcr.io") ||
strings.HasSuffix(host, ".pkg.dev") ||
strings.HasSuffix(host, ".google.com")
}
67 changes: 56 additions & 11 deletions pkg/skaffold/docker/authenticators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ limitations under the License.
package docker

import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"runtime"
"testing"
Expand All @@ -34,13 +37,28 @@ func TestResolve(t *testing.T) {
}

tests := []struct {
description string
dockerConfig string
registry string
gcloudOutput string
gcloudInPath bool
expectAnonymous bool
description string
dockerConfig string
registry string
gcloudOutput string
credentialsValues map[string]string
tokenURIRequestOutput string
gcloudInPath bool
expectAnonymous bool
}{
{
description: "Application Default Credentials configured and working",
registry: "gcr.io",
dockerConfig: `{"credHelpers":{"anydomain.io": "gcloud"}}`,
credentialsValues: map[string]string{
"client_id": "123456.apps.googleusercontent.com",
"client_secret": "THE-SECRET",
"refresh_token": "REFRESH-TOKEN",
"type": "authorized_user",
},
tokenURIRequestOutput: `{"access_token":"TOKEN","expires_in": 3599}`,
expectAnonymous: false,
},
{
description: "gcloud is configured and working",
registry: "gcr.io",
Expand Down Expand Up @@ -91,17 +109,25 @@ func TestResolve(t *testing.T) {
testutil.Run(t, test.description, func(t *testutil.T) {
tmpDir := t.NewTempDir().Write("config.json", test.dockerConfig)

var path string
var path = tmpDir.Root()
if test.gcloudInPath {
path = tmpDir.Root() + ":" + os.Getenv("PATH")
tmpDir.Write("gcloud", test.gcloudOutput)
} else {
path = tmpDir.Root()
}

var adc string
if test.credentialsValues != nil {
url := startTokenServer(t, test.tokenURIRequestOutput)
credentialsFile := getCredentialsFile(t, test.credentialsValues, url)
tmpDir.Write("credentials.json", credentialsFile)
adc = tmpDir.Path("credentials.json")
}

t.SetEnvs(map[string]string{
"DOCKER_CONFIG": tmpDir.Path("config.json"),
"PATH": path,
"DOCKER_CONFIG": tmpDir.Path("config.json"),
"PATH": path,
"HOME": tmpDir.Root(), // This is to prevent the go-containerregistry library from using ADCs that are already present on the computer.
"GOOGLE_APPLICATION_CREDENTIALS": adc,
})

registry, err := name.NewRegistry(test.registry)
Expand All @@ -122,3 +148,22 @@ func TestResolve(t *testing.T) {
})
}
}

func startTokenServer(t *testutil.T, reqOutput string) string {
t.Helper()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(reqOutput))
}))
t.Cleanup(server.Close)
return server.URL
}

func getCredentialsFile(t *testutil.T, credValues map[string]string, tokenRefreshURL string) string {
credValues["token_uri"] = tokenRefreshURL
credFile, err := json.Marshal(credValues)
if err != nil {
t.Fatalf("error generating credential files: %v", err)
}
return string(credFile)
}

0 comments on commit e976e81

Please sign in to comment.