Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: default to ADC when gcloud cred helper is configured in docker/config.json when using docker go library #9469

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading