Skip to content

Use DOCKER_AUTH_CONFIG env as credential store #6008

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Benehiko
Copy link
Member

@Benehiko Benehiko commented Apr 15, 2025

This patch enables the CLI to natively pick up the DOCKER_AUTH_CONFIG
environment variable and use it as a credential store.

The DOCKER_AUTH_CONFIG value should be a JSON object and must store
the credentials in a base64 encoded string under the auth key.

Credentials stored in DOCKER_AUTH_CONFIG would take precedence over any
credential stored in the file store (~/.docker/config.json) or native store
(credential helper).

Destructive actions, such as deleting a credential would result in a noop if
found in the environment credential. Credentials found in the file or
native store would get removed.

- What I did

- How I did it

- How to verify it

printf "username:pat" | openssl base64 -A

Setup the DOCKER_AUTH_CONFIG environment variable

DOCKER_AUTH_CONFIG='{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "aGk6KTpkY2tyX3BhdF9oZWxsbw=="
    }
  }
}'
docker buildx bake --set binary.platform=linux/arm64

docker run -it -v ./build/docker-linux-arm64:/bin/docker -v /var/run/docker.sock:/var/run/docker.sock --env DOCKER_AUTH_CONFIG alpine:latest /bin/ash

/ # docker login
...
Login Succeeded

- Human readable description for the release notes

Use `DOCKER_AUTH_CONFIG` as a credential store

- A picture of a cute animal (not mandatory but encouraged)

@Benehiko Benehiko requested a review from Copilot April 15, 2025 05:37
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

@codecov-commenter
Copy link

codecov-commenter commented Apr 15, 2025

Codecov Report

Attention: Patch coverage is 77.68595% with 27 lines in your changes missing coverage. Please review.

Project coverage is 55.13%. Comparing base (9e50654) to head (63c990c).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6008      +/-   ##
==========================================
+ Coverage   55.03%   55.13%   +0.09%     
==========================================
  Files         361      362       +1     
  Lines       30153    30271     +118     
==========================================
+ Hits        16596    16690      +94     
- Misses      12599    12620      +21     
- Partials      958      961       +3     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Benehiko Benehiko force-pushed the env-credentials-store branch 2 times, most recently from 866c70a to eb43744 Compare April 15, 2025 06:27
@Benehiko Benehiko requested review from thaJeztah and a team April 15, 2025 06:27
@Benehiko Benehiko marked this pull request as ready for review April 15, 2025 06:27
@Benehiko Benehiko force-pushed the env-credentials-store branch from e7c6f4d to b3aa07f Compare May 27, 2025 08:21
@Benehiko Benehiko force-pushed the env-credentials-store branch from de90f77 to b7770d7 Compare May 27, 2025 12:36
@Benehiko Benehiko requested a review from thaJeztah May 27, 2025 13:02
@Benehiko Benehiko force-pushed the env-credentials-store branch 2 times, most recently from d801989 to 0417f99 Compare June 3, 2025 08:19
@Benehiko Benehiko requested a review from thaJeztah June 3, 2025 08:45
@Benehiko Benehiko force-pushed the env-credentials-store branch from 0417f99 to 5e95a08 Compare June 6, 2025 09:30
This patch enables the CLI to natively pick up the `DOCKER_AUTH_CONFIG`
environment variable and use it as a credential store.

The `DOCKER_AUTH_CONFIG` value should be a JSON object and must store
the credentials in a base64 encoded string under the `auth` key.

For example:
`printf "username:pat" | openssl base64 -A`

`export DOCKER_AUTH_CONFIG='{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "aGk6KTpkY2tyX3BhdF9oZWxsbw=="
    }
  }
}'`

Credentials stored in `DOCKER_AUTH_CONFIG` would take precedence over any
credential stored in the file store (`~/.docker/config.json`) or native store
(credential helper).

Destructive actions, such as deleting a credential would result in a noop if
found in the environment credential. Credentials found in the file or
native store would get removed.

Signed-off-by: Alano Terblanche <[email protected]>
@Benehiko Benehiko force-pushed the env-credentials-store branch from 5e95a08 to 63c990c Compare June 6, 2025 11:15
@Benehiko Benehiko requested a review from thaJeztah June 6, 2025 11:35
@@ -46,6 +48,49 @@ type ConfigFile struct {
Experimental string `json:"experimental,omitempty"`
}

type configEnvAuth struct {
Auth string `json:"auth"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably keep the omitempty to match the "actual" types.AuthConfig;

Auth string `json:"auth,omitempty"`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, we can't here. This is enforced if the environment variable is set.

if c.AuthConfigs == nil {
c.AuthConfigs = make(map[string]configEnvAuth)
}
if err := json.NewDecoder(strings.NewReader(v)).Decode(c); err != nil && !errors.Is(err, io.EOF) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using json.NewDecoder because it was designed to handle JSON streams (multiple records). We're doing that wrong in various places (some were probably intended as optimisation), but in this case we only expect a single JSON document, so better to not use;

https://go.dev/play/p/ehHfvnCkGDL

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
)

type Foo struct {
	Name string
}

func main() {
	const data = `{"Name":"foo"}{"Name":"bar"}{"Name":"baz"}`
	decoder := json.NewDecoder(strings.NewReader(data))

	var p Foo
	for {
		err := decoder.Decode(&p)
		if err != nil {
			if errors.Is(err, io.EOF) {
				fmt.Println("end of stream")
				break
			}
			fmt.Println(err)
			return
		}
		fmt.Println(p)
	}

	err := json.Unmarshal([]byte(data), &p)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(p)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied it from the current implementation -

if err := json.NewDecoder(configData).Decode(configFile); err != nil && !errors.Is(err, io.EOF) {
return err
}

authConfigs := make(map[string]types.AuthConfig)
for addr, envAuth := range c.AuthConfigs {
if envAuth.Auth == "" {
return authConfigs, fmt.Errorf("DOCKER_AUTH_CONFIG environment variable is missing auth for %s", addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should just ignore these; when using credential-stores, these will always be an empty struct;

cat ~/.docker/config.json
{
	"auths": {
		"https://index.docker.io/v1/": {},
		"https://index.docker.io/v1/access-token": {},
		"https://index.docker.io/v1/refresh-token": {}
	},
	"credsStore": "desktop"
}

I think it's fine to ignore those; while we probably don't expect mixed credentials-store vs "plain-text" entries, I guess technically this file is valid;

{
	"auths": {
		"env.example.test": {
			"auth": "ZW52X3VzZXI6ZW52X3Bhc3M="
		},
		"https://index.docker.io/v1/": {},
		"https://index.docker.io/v1/access-token": {},
		"https://index.docker.io/v1/refresh-token": {}
	}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't agree, this is a very explicit thing to set and would be up to the user to set it correctly. I think returning an error when the config isn't what we expect would be better.

Comment on lines +62 to +64
v := os.Getenv("DOCKER_AUTH_CONFIG")
if v == "" {
return errDockerAuthConfigNotSet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... so we're always returning an error unless the env-var is set.

Wondering if we should keep the logic to construct a memory store in the first place "conditional", and only try to use these options if it's either set, or set and non-empty

Comment on lines +333 to +343
envConfig := &configEnv{}
err := envConfig.LoadFromEnv()
if err != nil {
// ignore if DOCKER_AUTH_CONFIG is not set
if errors.Is(err, errDockerAuthConfigNotSet) {
return nil
}
return err
}

authConfigs, err := envConfig.GetAuthConfigs()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit convoluted; in the end, what we need is;

  • A string containing a (valid) JSON-encoded authconfig
  • That's decoded to a map[string]types.AuthConfig

i.e., this could look something like;

authConfigs, err := unmarshalAuthConfig(v)
if err != nil {
	return err
}
return memorystore.WithAuthConfig(authConfigs)(c)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try to draw this up quickly; will post later

Comment on lines +495 to +499
"auths": {
"env.example.test": {
"auth": "ZW52X3VzZXI6ZW52X3Bhc3M="
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment about "empty" values here; perhaps we should have that as a test-case to verify that behavior;

Suggested change
"auths": {
"env.example.test": {
"auth": "ZW52X3VzZXI6ZW52X3Bhc3M="
}
}
"auths": {
"env.example.test": {
"auth": "ZW52X3VzZXI6ZW52X3Bhc3M="
},
"https://index.docker.io/v1/": {},
"https://index.docker.io/v1/access-token": {},
"https://index.docker.io/v1/refresh-token": {}
}

@thaJeztah
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants