-
Notifications
You must be signed in to change notification settings - Fork 108
feat: default to OS credstore for storing registry creds #1653
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
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: ayush-panta <[email protected]>
74be291 to
a291907
Compare
| if credHelper == "finch" { | ||
| cmdArr = append(cmdArr, configureFinchCredHelperTemplate) | ||
| // Add as default in credStore to work with nerdctl command flow. | ||
| // TODO: support multi-registry workflows. Will involve set registries as credStore vs credHelpers in config.json. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much more work is this? It'd be nice to have this in the initial implementation so we don't have to think about what happens when user's update
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm giving this some thought right now. I think it is a matter of making ecr-login behave like acr-login:
{
...
"credHelpers": {
"dkr.acr.builder-tools.aws.dev": "acr-login",
"dkr.ecr.public.aws....": "ecr-login",
}
...
}
docker-credential-acr-login has a --setup flag that allows it to populate the config file by itself, but docker-credential-ecr-login does not.
I think what I will do is make it so that if ecr-login is set as a credhelper in finch.yaml, we store it as a credHelper for generic ecr links
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ Going to make this a mini-feature after the project.
| case "darwin": | ||
| helperName = "docker-credential-osxkeychain" | ||
| case "windows": | ||
| helperName = "docker-credential-wincred.exe" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we expecting the user to install the credential helpers specifically in the finch path? Other tools just require the credential helpers to be in the PATH. This might not be expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user does not have to install anything. I just symlinked the binaries that are packaged with finch-core to .finch/cred-helpers, and then depend on these for credhelper operations.
I did this because as it is right now, if someone configures ecr-login in finch.yaml (for example), docker-credential-ecr-login gets installed into ./finch/cred-helpers.
However, I can try to make it just check if helpers are in PATH while also creating the symlink.
| @@ -0,0 +1,113 @@ | |||
| //go:build darwin || windows | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we create a new directory/package for all of the credential handling files? Namely: cred_socket.go and cred_helper.go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do
| return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS) | ||
| } | ||
|
|
||
| helperPath := filepath.Join(homeDir, ".finch", "cred-helpers", helperName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This path neednot be in the home dir for example
/opt/homebrew/bin/docker-credential-osxkeychain
i would say we neednot handle packaging them at all. Just have the process in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to find the path depending on OS you can do a which or where
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should think a little more here whether to package it or not. Packaging it will potentially add maintenance so i am not inclined, but makes it easier for customers
@pendo324 what do you think?
| cmd := exec.Command(helperPath, action) | ||
|
|
||
| // Set input based on action | ||
| if action == "store" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if else is not usually desirable in function, try to have single functionality per function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes it easier to test also.
| } | ||
| credJSON, err := json.Marshal(cred) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to marshal credentials: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dont print the err here, it might leak creds.
|
|
||
| output, err := cmd.CombinedOutput() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("credential helper failed: %w - %s", err, string(output)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above.
|
|
||
| // Break if already running | ||
| if cs.listener != nil { | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need an error saying concurrent cred request not supported in finch mac and windows version.
| return nil | ||
| } | ||
|
|
||
| socketPath := filepath.Join(finchRootPath, "lima", "data", "finch", "sock", "creds.sock") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be part of makefile, similar to finch,sock
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if socket not there we just throw an error.
| "load": "Load an image from a tar archive or STDIN", | ||
| "login": "Log in to a container registry", | ||
| "logout": "Log out from a container registry", | ||
| // "login": "Log in to a container registry", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: commented out code.
| finchRootPath := filepath.Dir(filepath.Dir(execPath)) | ||
|
|
||
| // Wrap nerdctl execution with credential socket | ||
| return withCredSocket(finchRootPath, func() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will prevent any concurrent execution but thats fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its good to wrap it for functionalities we actually need creds, otherwise it might un-necessary hold it. This part is actually flimsy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also it can slow down the run of other commands.
| return nil | ||
| } | ||
|
|
||
| func (iva *initVMAction) ensureNativeCredentialHelpers() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would like to avoid having to manage the installation rather find it from the environment.
| return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS) | ||
| } | ||
|
|
||
| helperPath := filepath.Join(homeDir, ".finch", "cred-helpers", helperName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://pkg.go.dev/os/exec -> this probably can be used to get the path.
Not sure it works for windows but we can try.
| } | ||
|
|
||
| // withCredSocket wraps command execution with credential socket lifecycle. | ||
| func withCredSocket(finchRootPath string, fn func() error) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how will this handle detach case?
Wont it keep holding the console, Lets do a check for it
finch run -d alpine sleep 10
something like that.
Also lets verify the behavior if a image exists then what happens will the request still come?
| script: | | ||
| # Create user-writable directory in /run for credential socket | ||
| mkdir -p /run/finch-user-sockets | ||
| chmod 777 /run/finch-user-sockets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems overly permissive.
| return loginWithNativeCredStore(serverAddress, username, password, cmd.OutOrStdout()) | ||
| } | ||
|
|
||
| func loginWithNativeCredStore(serverAddress, username, password string, stdout io.Writer) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we not use login package from nerdctl:
https://github.com/containerd/nerdctl/blob/53e7b272af14b075a5e8d7b95a5c2d862a1620f8/pkg/cmd/login/login.go#L44
we just use it.
| if !scanner.Scan() { | ||
| return | ||
| } | ||
| serverURL := strings.TrimSpace(scanner.Text()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serverURL probably needs some sanitization with regex to check its a valid server request, but probably docker cred helper also does it?
| defer cs.mu.Unlock() | ||
|
|
||
| // Break if already running | ||
| if cs.listener != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what it does, if there are separate request you wont see them across CLI calls. so this is not a valid check for concurrent connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can probably do flock on the socket to check if its in use or not
"github.com/go-frs/flock"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should handle crashes as it uses system calls.
| testNonDefaultOptions(o, *e2e.Installed) | ||
| testSupportBundle(o) | ||
| testCredHelper(o, *e2e.Installed, *e2e.Registry) | ||
| testNativeCredHelper(o, *e2e.Installed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
windows also will need it?
| @@ -0,0 +1,51 @@ | |||
| //go:build darwin || windows | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the naming or is for linux i think, for vm we use remote as we are seeing from perspective of from inside the vm.
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //go:build darwin || windows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need test for:
docker login: This is the most direct. It triggers a store action to save new credentials into your helper/keychain.
docker push: Before uploading layers, Docker calls get to retrieve the registry token or password.
docker pull: Even for public images on Docker Hub, Docker often calls get to check for an account to avoid "Pull Rate Limits." For private registries, it is mandatory.
docker logout: Triggers the erase action to remove credentials from the store.
docker run: Implicitly pulls the image if it’s missing. Should check the detached mode (only run has a detached mode which is relevant for our case)
docker create: Same as run; pulls the image metadata and layers.
docker build / docker buildx: If the FROM line in your Dockerfile references a private image or a remote registry, the builder will request credentials to fetch that base image
| socketPath := filepath.Join(finchRootPath, "lima", "data", "finch", "sock", "creds.sock") | ||
| if err := os.MkdirAll(filepath.Dir(socketPath), 0750); err != nil { | ||
| return fmt.Errorf("failed to create socket directory: %w", err) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how will the credential behave if the command crashes. i think it will just release the conncetion the other end will get a 500 internal error but lets confirm its graceful enough and doesnt hold the connection. If it does we cannot run any further commands.
Description of changes: This PR enables Finch to integrate with native OS credential stores (Keychain on macOS, Credential Manager on Windows) for container registry authentication.
How it works:
Dependencies:
Testing done: Tested locally; works alongside changes to finch-daemon for standard workflows. Added E2E tests for macOS, which worked locally (see below). Must add tests for Windows.
License Acceptance
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.