Skip to content

Commit

Permalink
feat: default to ADC when gcloud cred helper is configured in docke…
Browse files Browse the repository at this point in the history
…r/config.json when using docker go library (#9469)
  • Loading branch information
renzodavid9 authored Jul 12, 2024
1 parent 9d68211 commit 2393d9d
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 17 deletions.
90 changes: 77 additions & 13 deletions pkg/skaffold/docker/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@ package docker

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/distribution/reference"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/distribution/reference"
clitypes "github.com/docker/cli/cli/config/types"
types "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/registry"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/google"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/gcp"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
Expand Down Expand Up @@ -79,14 +81,72 @@ func (h credsHelper) GetAuthConfig(registry string) (types.AuthConfig, error) {
return types.AuthConfig{}, err
}

return h.loadCredentials(cf, registry)
}

func (h credsHelper) loadCredentials(cf *configfile.ConfigFile, registry string) (types.AuthConfig, error) {
if helper := cf.CredentialHelpers[registry]; helper == "gcloud" {
authCfg, err := h.getGoogleAuthConfig(registry)
if err == nil {
return authCfg, nil
}
log.Entry(context.TODO()).Debugf("error getting google authenticator, falling back to docker auth: %v", err)
}

var anonymous clitypes.AuthConfig
auth, err := cf.GetAuthConfig(registry)
if err != nil {
return types.AuthConfig{}, err
}

// From go-containerrergistry logic, the ServerAddress is not considered when determining if returned auth is anonymous.
anonymous.ServerAddress = auth.ServerAddress
if auth != anonymous {
return types.AuthConfig(auth), nil
}

if isGoogleRegistry(registry) {
authCfg, err := h.getGoogleAuthConfig(registry)
if err == nil {
return authCfg, nil
}
}

return types.AuthConfig(auth), nil
}

func (h credsHelper) getGoogleAuthConfig(registry string) (types.AuthConfig, error) {
auth, err := google.NewEnvAuthenticator()
if err != nil {
return types.AuthConfig{}, err
}

if auth == authn.Anonymous {
return types.AuthConfig{}, fmt.Errorf("error getting google authenticator")
}

cfg, err := auth.Authorization()
if err != nil {
return types.AuthConfig{}, err
}

bCfg, err := cfg.MarshalJSON()
if err != nil {
return types.AuthConfig{}, err
}

var authCfg types.AuthConfig
err = json.Unmarshal(bCfg, &authCfg)
if err != nil {
return types.AuthConfig{}, err
}

// The docker library does the same when we request the credentials
authCfg.ServerAddress = registry

return authCfg, nil
}

// GetAllAuthConfigs retrieves all the auth configs.
// Because this can take a long time, we make sure it can be interrupted by the user.
func (h credsHelper) GetAllAuthConfigs(ctx context.Context) (map[string]types.AuthConfig, error) {
Expand All @@ -111,22 +171,31 @@ func (h credsHelper) GetAllAuthConfigs(ctx context.Context) (map[string]types.Au
}

func (h credsHelper) doGetAllAuthConfigs() (map[string]types.AuthConfig, error) {
credentials := make(map[string]types.AuthConfig)
cf, err := loadDockerConfig()
if err != nil {
return nil, err
}

credentials, err := cf.GetAllCredentials()
defaultCreds, err := cf.GetCredentialsStore("").GetAll()
if err != nil {
return nil, err
}

authConfigs := make(map[string]types.AuthConfig, len(credentials))
for k, auth := range credentials {
authConfigs[k] = types.AuthConfig(auth)
for registry, cred := range defaultCreds {
credentials[registry] = types.AuthConfig(cred)
}

for registry := range cf.CredentialHelpers {
authCfg, err := h.loadCredentials(cf, registry)
if err != nil {
log.Entry(context.TODO()).Debugf("failed to get credentials for registry %v: %v", registry, err)
continue
}
credentials[registry] = authCfg
}

return authConfigs, nil
return credentials, nil
}

func (l *localDaemon) encodedRegistryAuth(ctx context.Context, a AuthConfigHelper, image string) (string, error) {
Expand All @@ -150,12 +219,7 @@ func (l *localDaemon) encodedRegistryAuth(ctx context.Context, a AuthConfigHelpe
return "", fmt.Errorf("getting auth config: %w", err)
}

buf, err := json.Marshal(ac)
if err != nil {
return "", err
}

return base64.URLEncoding.EncodeToString(buf), nil
return types.EncodeAuthConfig(ac)
}

func (l *localDaemon) officialRegistry(ctx context.Context) string {
Expand Down
52 changes: 48 additions & 4 deletions pkg/skaffold/docker/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@ func TestGetAllAuthConfigs(t *testing.T) {
tmpDir := t.NewTempDir().
Write("config.json", `{"credHelpers":{"my.registry":"helper"}}`).
Write("docker-credential-gcloud", `#!/bin/sh
read server
echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`).
read server
echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`).
Write("docker-credential-helper", `#!/bin/sh
read server
echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`)
read server
echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`)
t.Override(&configDir, tmpDir.Root())
t.Setenv("PATH", tmpDir.Root())

// These env values will prevent the authenticator to use the Google authenticator, it will use docker logic instead.
t.Setenv("HOME", tmpDir.Root())
t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")

auth, err := DefaultAuthHelper.GetAllAuthConfigs(context.Background())

t.CheckNoError(err)
Expand All @@ -89,6 +93,46 @@ echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`)
t.CheckError(true, err)
t.CheckEmpty(auth)
})

testutil.Run(t, "Application Default Credentials authentication", func(t *testutil.T) {
if runtime.GOOS == "windows" {
t.Skip("test doesn't work on windows")
}

authToken := `{"access_token":"TOKEN","expires_in": 3599}`
authServerURL := startTokenServer(t, authToken)
credentialsFile := getCredentialsFile(t, map[string]string{
"client_id": "123456.apps.googleusercontent.com",
"client_secret": "THE-SECRET",
"refresh_token": "REFRESH-TOKEN",
"type": "authorized_user",
}, authServerURL)

tmpDir := t.NewTempDir().Write("credentials.json", credentialsFile)
tmpDir.Write("config.json", `{"credHelpers":{"my.registry1":"helper","my.registry2":"missinghelper","gcr.io":"gcloud","us-central1-docker.pkg.dev":"otherhelper","us-east1-docker.pkg.dev":"gcloud"}}`)
tmpDir.Write("docker-credential-helper", `#!/bin/sh
read server
echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`)
tmpDir.Write("docker-credential-otherhelper", `#!/bin/sh
read server
echo "{\"Username\":\"<token>\",\"Secret\":\"TOKEN_$server\"}"`)

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

auth, err := DefaultAuthHelper.GetAllAuthConfigs(context.Background())
t.CheckNoError(err)
t.CheckDeepEqual(map[string]registry.AuthConfig{
"gcr.io": {Username: "_token", Password: "TOKEN", Auth: "X3Rva2VuOlRPS0VO", ServerAddress: "gcr.io"},
"us-east1-docker.pkg.dev": {Username: "_token", Password: "TOKEN", Auth: "X3Rva2VuOlRPS0VO", ServerAddress: "us-east1-docker.pkg.dev"},
"us-central1-docker.pkg.dev": {IdentityToken: "TOKEN_us-central1-docker.pkg.dev", ServerAddress: "us-central1-docker.pkg.dev"},
"my.registry1": {IdentityToken: "TOKEN_my.registry1", ServerAddress: "my.registry1"},
}, auth)
})
}

func TestGetEncodedRegistryAuth(t *testing.T) {
Expand Down

0 comments on commit 2393d9d

Please sign in to comment.