Skip to content

cli/config/credentials: refactor DetectDefaultStore and add tests - #5568

Open
thaJeztah wants to merge 1 commit into
docker:masterfrom
thaJeztah:refactor_DetectDefaultStore
Open

cli/config/credentials: refactor DetectDefaultStore and add tests#5568
thaJeztah wants to merge 1 commit into
docker:masterfrom
thaJeztah:refactor_DetectDefaultStore

Conversation

@thaJeztah

Copy link
Copy Markdown
Member

Refactor the DetectDefaultStore to allow testing it cross-platform, and without the actual helpers installed.

This also makes a small change in the logic for detecting the preferred helper. Previously, it only detected the "helper" binary ("pass"), but would fall back to using plain-text if the pass credentials-helper was not installed.

With this patch, it falls back to the platform default (secretservice), before falling back to using no credentials helper (and storing unencrypted).

- Description for the changelog

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

@thaJeztahthaJeztah added this to the 28.0.0 milestone Oct 21, 2024
@thaJeztahthaJeztah self-assigned this Oct 21, 2024
@codecov-commenter

codecov-commenter commented Oct 21, 2024

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
cli/config/credentials/default_store.go87.50%1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@laurazardlaurazard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Left some nits/comments. I'm not super convinced about having the whole pass logic (check for both the pass binary and docker-credential-pass) outside of _linux.go.

Maybe we could have a separate checkForPreferredHelper func that we can override instead, or some other solution that allows to keep this code separate while still making it possible to test cross-platform?

Comment threadcli/config/credentials/default_store.go Outdated
Comment on lines +14 to 18
func DetectDefaultStore(customStore string) string {
if customStore != "" {
// use user-defined
return store
return customStore
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sidenote, but

funcbork(foostring) string {
iffoo!="" {
returnfoo
}
...
}

is definitely something 😅 we should see if we could change this for the next major.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Right, so I the logic here was that if the config-file has a credentials-store configured, we accept it whatever it is, and consider it an option set by the user; when used, it may error, but that's intentional (you configured a helper, we tried using it, and it failed! intervention is needed).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

But now the fun bit;

cli/cli/config/config.go

Lines 162 to 172 in 32ff200

funcLoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
configFile, err:=load(Dir())
iferr!=nil {
// FIXME(thaJeztah): we should not proceed here to prevent overwriting existing (but malformed) config files; see https://github.com/docker/cli/issues/5075
_, _=fmt.Fprintln(stderr, "WARNING: Error", err)
}
if!configFile.ContainsAuth() {
configFile.CredentialsStore=credentials.DetectDefaultStore(configFile.CredentialsStore)
}
returnconfigFile
}

See what's wrong in the logic there? Hint:

if!configFile.ContainsAuth() {

That nice porcelain ContainsAuth() function checks if either a credentials-store is configured or per-registry helpers, or contains credentials;

func (configFile*ConfigFile) ContainsAuth() bool {
returnconfigFile.CredentialsStore!=""||
len(configFile.CredentialHelpers) >0||
len(configFile.AuthConfigs) >0
}

I guess we could consider it an extra line of defence to never overwrite what's there, but definitely a bit iffy, and looks like it was there from the start moby/moby@cf721c2

Comment on lines +19 to +29
if preferred := findPreferredHelper(); preferred != "" {
return preferred
}
if defaultHelper == "" {
return ""
}
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultHelper); err != nil {
return ""
}
return defaultHelper
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are we testing for defaultHelper's existence, but not preferred's?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nevermind, I see that the test for the preferred helper is inside findPreferredHelper. I find this a bit hard to read, maybe because it mixes levels of abstraction. Maybe instead we could do something like

Suggested change
ifpreferred:=findPreferredHelper(); preferred!="" {
returnpreferred
}
ifdefaultHelper=="" {
return""
}
if_, err:=exec.LookPath(remoteCredentialsPrefix+defaultHelper); err!=nil {
return""
}
returndefaultHelper
}
ifpreferred:=findPreferredHelper(); preferred!="" {
returnpreferred
}
returnfindDefaultHelper()
}
funcfindDefaultHelper() string {
ifdefaultHelper=="" {
return""
}
if_, err:=exec.LookPath(remoteCredentialsPrefix+defaultHelper); err!=nil {
return""
}
returndefaultHelper
}

Comment on lines +52 to +55
// Note that the logic below is specific to detection needed for the
// "pass" credentials-helper on Linux (which is the only platform with
// a "preferred" and "default" helper. This logic may change if a similar
// order of preference is needed on other platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The "detection" logic below is generic, right? It's only used for linux (because linux is the only platform with a preferred helper), but would work for any other platform all the same?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

// a "preferred" and "default" helper. This logic may change if a similar// order of preference is needed on other platforms.

If this comment as a whole is about the preferred vs default helper order of preference, then I'd suggest placing it above, inside DetectDefaultStore near the operations regarding both the preferred and default helpers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The "detection" logic below is generic, right? It's only used for linux (because linux is the only platform with a preferred helper), but would work for any other platform all the same?

AAAHH, this is about checking for both pass and docker-credential-pass. I wonder if we can make this clearer. Maybe separate this comment from the comment about the order (preferred vs default)? It was somewhat hard to figure out what this comment was about.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I realize you moved this here in order to make this easier to test cross-platform, but this is exactly the type of thing that would make sense to put in a _linux.go file and not leak to the rest of the code – otherwise, we need to have it here with a number of comments explaining why, while it's unnecessary for anyone worrying about any other platform to look at).

@thaJeztah
thaJeztahforce-pushed the refactor_DetectDefaultStore branch from e8e05fd to 555aba2CompareOctober 28, 2024 22:26
@thaJeztah

Copy link
Copy Markdown
MemberAuthor

Thanks for reviewing! I fixed the typo; will have a look at the other bits tomorrow.

I guess we could generalize the for a credentials helper to have a "prerequisites check" (for docker-credentials-pass that would be "check if the pass binary is found". Other helpers would then have an empty func for that.

Perhaps ultimately, the credentials-helpers themselves could have some check (docker-credential-foo --check), but not sure if we want to go that route 😅

@laurazard

laurazard commented Oct 30, 2024

Copy link
Copy Markdown
Member

Perhaps ultimately, the credentials-helpers themselves could have some check (docker-credential-foo --check), but not sure if we want to go that route

Honestly that sounds like the most sane idea to me – the CLI can't be expected to account for all the different credential helpers and their requirements, they should do it themselves. Maybe even something like the metadata we have for plugins, credential-helpers could have a similar thing that would include if they are "ready" to be used or not, and some error message.

Or the API could just be exit_code == 0 means it's fine, otherwise it's not and it prints some error message.

@thaJeztahthaJeztah modified the milestones: 28.0.0, 28.0.5Mar 31, 2025
@thaJeztahthaJeztah modified the milestones: 28.1.1, 28.1.2Apr 23, 2025
@thaJeztahthaJeztah modified the milestones: 28.1.2, 28.2.0, 29.0.0May 15, 2025
@thaJeztahthaJeztah modified the milestones: 29.0.0, 29.2.0Jan 13, 2026
@thaJeztahthaJeztah modified the milestones: 29.2.0, 29.2.1, 29.2.2Jan 26, 2026
@thaJeztahthaJeztah modified the milestones: 29.2.2, 29.3.1Mar 2, 2026
@thaJeztahthaJeztah modified the milestones: 29.4.0, 29.4.1Apr 3, 2026
@thaJeztahthaJeztah removed this from the 29.4.1 milestone Apr 20, 2026
@thaJeztahthaJeztah added this to the 29.4.2 milestone Apr 20, 2026
@vvolandvvoland modified the milestones: 29.4.2, 29.4.3, 29.5.0May 1, 2026
@thaJeztah
thaJeztahforce-pushed the refactor_DetectDefaultStore branch 2 times, most recently from d6d5f2b to 4ce9681CompareMay 7, 2026 15:21
Comment threadcli/config/credentials/default_store_test.go Outdated
@thaJeztah
thaJeztahforce-pushed the refactor_DetectDefaultStore branch from 4ce9681 to a378b19CompareMay 8, 2026 11:59
Refactor the DetectDefaultStore to allow testing it cross-platform, and
without the actual helpers installed.
This also makes a small change in the logic for detecting the preferred
helper. Previously, it only detected the "helper" binary ("pass"), but
would fall back to using plain-text if the pass credentials-helper was
not installed.
With this patch, it falls back to the platform default (secretservice),
before falling back to using no credentials helper (and storing unencrypted).
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
@thaJeztah
thaJeztahforce-pushed the refactor_DetectDefaultStore branch from a378b19 to b3d49efCompareMay 8, 2026 12:03
@thaJeztah

Copy link
Copy Markdown
MemberAuthor

I can move this one to 29.5.1 - it's been pending for some time, so not urgent

@thaJeztahthaJeztah modified the milestones: 29.5.0, 29.5.1May 8, 2026
@vvolandvvoland modified the milestones: 29.5.1, 29.5.2May 18, 2026
@thaJeztahthaJeztah modified the milestones: 29.5.2, 29.5.3May 20, 2026
@vvolandvvoland modified the milestones: 29.5.3, 29.6.0Jun 3, 2026
@thaJeztahthaJeztah modified the milestones: 29.6.0, 29.6.1Jun 12, 2026
@vvolandvvoland modified the milestones: 29.6.1, 29.7.0Jun 30, 2026
@vvolandvvoland modified the milestones: 29.7.0, 29.8.0Jul 30, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/authenticationkind/refactorPR's that refactor, or clean-up codestatus/2-code-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@thaJeztah@codecov-commenter@laurazard@vvoland