-
Notifications
You must be signed in to change notification settings - Fork 385
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
types: Add SystemContext.GetAuth #588
Conversation
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.
Could you back out and explain what is it you are, at the high level, trying to implement?
“difficult to configure” motivating a single +768-647 commit makes it pretty difficult to reason about, and this introduces rather many abstractions and public APIs that, at a first glance, seem either overly general for the task, or, worse, look general but are actually quite restricted.
Right now, it’s pretty difficult to evaluate.
Providing a callback to get docker/distribution credentials, sure, I can imagine uses, in principle. I can’t see at all how the complaint about a config file being on-disk motivates the store abstraction or making the data format a public API (guessing – is this to use OpenShift secrets as []byte
data? That might be useful, but the proposed config.AuthStore
seems like a very inconvenient way to attach it to a types.SystemContext
).
The detailed review comments are just random observations on the current code, but really we should talk about the use cases and needs first.
@@ -1,29 +1,67 @@ | |||
package config |
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.
+284-180 lines in a single commit 😭
@@ -0,0 +1,42 @@ | |||
// Package single implements a basic credentials helper with a single username and secret. |
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.
(It’s not at all obvious to me that this warrants a top-level namespace here, but that depends on much else.)
} | ||
|
||
// Config holds the full authorization configuration. | ||
type Config struct { |
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.
Why should this be public? (Note that this is not the full definition of the Docker structure.)
If the goal is to provide callback-based GetAuth
capabilities, fine, then the callers have no reason to care about these fields at all; they can submit a callback without dealing with the Docker format. And if the goal is to allow callers to submit a Docker-like config in memory without creating a file, they still don’t need this type exposed.
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.
And if the goal is to allow callers to submit a Docker-like config in memory without creating a file, they still don’t need this type exposed.
That's the goal (for the OpenShift installer, which has the pull-secret portion of this file in memory as []byte
). And while we could have an internal config
holding the structured data with a LoadFromBytes([]byte)
or similar method, it seemed easier and more convenient to just make the structured configuration public (e.g. see the unit tests, which no longer need the makeTestAuthConfig
helper). Do you have concerns about having this be public?
type DockerAuthConfig struct { | ||
Username string | ||
Password string | ||
} | ||
|
||
// AuthGetter retrieves credentials for HTTP(S) access. It returns | ||
// the username and secret as strings. | ||
type AuthGetter func(serverURL string) (string, string, 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.
It’s very much not obvious to me that it makes sense to have a single callback for all kinds of “HTTP(S)” access, no matter the protocol on top of HTTPS, or authentication mechanism. (Sure, the AuthFilePath
name is similarly misleading; we originally thought that it would somehow be generic, but it clearly is not.)
WRT serverURL
, in docker/distribution the lookup key is not an URL!
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.
WRT
serverURL
, in docker/distribution the lookup key is not an URL!
The interface I'm implementing is credentials.Helper
, which uses serverURL
. This getter is the Get
method from that interface. I'm fine using a different string if you want for the input argument (personally, I prefer "authority", or adjusting the docstring to whatever you want me to use instead, just tell me what to paste in ;).
... we originally thought that it would somehow be generic, but it clearly is not...
HTTP(S) auth is a generic thing. Unfortunately, the Docker APIs don't really conform, since they don't provide a way to feed in a challenge string. Should we come up with our own API (or lean on one that already exists in Go) here, and then special-case the Docker approach with dummy challenge?
// If not "", overrides the default path for the authentication file | ||
// If not "", overrides the default path for the authentication file. Ignored for reading if AuthConfig is not "". | ||
// | ||
// Deprecated: Use the GetAuth property and github.com/containers/image/pkg/docker/config's AuthStore instead. |
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.
It’s not obviously attractive to force the users to create extra objects when they could just set a clearly-named field. Sure, the consumer of the data might need to define what happens if multiple fields are set, or refuse such configurations, but that seems worth it.
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.
Sure, the consumer of the data might need to define what happens if multiple fields are set, or refuse such configurations, but that seems worth it.
SystemContext
seems complicated enough already, to me it is a clear win to factor out the auth-configuration properties into other locations. For folks who currently use SystemContext
, the transitions seem pretty clear to me, although I can work up migration docs if that would help address your concerns (or even just give us something more concrete to anchor this discussion).
// SetAuthentication stores the username and password in the auth.json file | ||
// | ||
// Deprecated: Use an AuthStore. | ||
func SetAuthentication(sys *types.SystemContext, registry, username, password string) 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.
If AuthStore
is supposed to be a replacement, it needs to support SystemContext
.
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
AuthStore
is supposed to be a replacement, it needs to supportSystemContext
.
I don't think so. The Docker client needs a way to get credentials for a given authority, and that's what the new GetAuth
property provides. Users will need a way to create that getter, and that's what the two new AuthStore
structures provide. The old SystemContext
properties either configure a single credential (which can be replaced easily with the new single.AuthStore
) or the path lookup for the Docker-config AuthStore
(which can be replaced by creating your own config.AuthStore
with Path
set however they like). The rest of SystemContext
seems unrelated to managing authentication, right?
}) | ||
} | ||
|
||
// GetAuthentication returns the registry credentials stored in | ||
// either auth.json file or .docker/config.json | ||
// If an entry is not found empty strings are returned for the username and password | ||
// | ||
// Deprecated: Use an AuthStore. |
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 AuthStore
is supposed to be a replacement, it needs to make it easy to use all relevant fields of SystemContext
.
// if nil, the library tries to parse ~/.docker/config.json to retrieve credentials | ||
// if nil, the library tries to parse ~/.docker/config.json to retrieve credentials. | ||
// | ||
// Deprecated: Use the GetAuth property and github.com/containers/image/credentials/single's AuthStore instead. |
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.
It’s not obviously attractive to force the users to create extra objects when they could just set a clearly-named field. Sure, the consumer of the data might need to define what happens if multiple fields are set, or refuse such configurations, but that seems worth it.
Plumbing together creation of a single.Credential
+single.AuthStore
, and somehow making an AuthGetter
out of that, seems way too much code compared to a DockerAuthConfig: &types.DockerAuthConfig{…}
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.
Plumbing together creation of a
single.Credential
+single.AuthStore
, and somehow making anAuthGetter
out of that...
$ git describe
v1.3-49-g44801dd
$ cat test.go
package main
import (
"fmt"
"github.com/containers/image/types"
"github.com/containers/image/credentials/single"
"github.com/docker/docker-credential-helpers/credentials"
)
func main() {
authStore := single.AuthStore(credentials.Credentials{
ServerURL: "example.org",
Username: "alice",
Secret: "secret",
})
sys := &types.SystemContext{
GetAuth: authStore.Get,
}
username, secret, err := sys.GetAuth("example.org")
fmt.Println(username)
fmt.Println(secret)
fmt.Println(err)
}
$ go run test.go
alice
secret
<nil>
That doesn't seem too complicated to me, and it saves you from having to explain how DockerAuthConfig
interacts with a GetAuth
or other SystemContext
properties.
5e011a3
to
44801dd
Compare
I'll rebase this once #589 lands, so I can rebase this on top to shrink the diff size. |
Looks like it has merged. |
The previous system is difficult to configure, with several SystemContext properties feeding the auth-getting logic. It also assumed that there was either a single username/password (DockerAuthConfig) or that the auth information was on disk somewhere. With this commit, I'm pushing all of the complexity down into a user-supplied getter (falling back to the old logic if the getter is unset). That makes it easy to plug in your own alternative without worrying about the default logic. The Docker-config AuthStore has a public Config property to make it easy for callers to drop in their own configuration for read-only access without having to go through either the filesystem or JSON. I've made the backing types public to support that use-case, and set custom JSON serialization for the newly-public Auth structure to allow Go callers to avoid having to understand the base64 wrapping used in the JSON serialization. Ideally the auth interface would support challenge/response auth like [1], but for now it just uses Docker's "we'll be able to hard-code authorization for each authority" approach. URI authorities are specified in [2]. [1]: https://tools.ietf.org/html/rfc7235 [2]: https://tools.ietf.org/html/rfc3986#section-3.2 Signed-off-by: W. Trevor King <[email protected]>
Since e2b31b2 (bootkube: Supply machine-os-content to MCO, 2019-01-29, openshift#1149), we have been using the machine-os-content image to seed the machine-config operator. With this commit, use the RHCOS build ID from that image's annotations to calculate our AMI, etc. as well. This gives one less degree of freedom for breaking things ;). Users who want to test clusters based on a different RHCOS build should bump the value in their release image, just like users testing operator updates and other changes. The new pkg/asset/release subpackage allows users to continue using pkg/rhcos without pulling in all of containers/image as a dependency. The pull-secret handling is a bit of a hack, leaning on the fact that users are likely providing clean secrets from [1]. Hopefully soon containers/image will grow an API for injecting in-memory bytes into their more-robust Docker-auth-config parser, but my attempt at that [2] is unlikely to land in the next few days, so I've cludged together a minimal implementation here. [1]: https://cloud.openshift.com/clusters/install#pull-secret [2]: containers/image#588
I'm going to hold off on rebasing this until #597 settles. |
I took a run at rebasing this, but the new keyring stuff that started in #619 is too confusing for me ;). If anyone else wants to pick this up and rebase it, please go ahead without waiting on me. |
Four years later, the credential code has changed a lot. The implementation approach of mirroring the semantics of History has also shown that as the capabilities keep growing, file formats must accommodate both old and new readers/writers, making it unattractive to expose the details of the data structures like I do, now, agree that many of the c/image subpackages should have their own configuration API without adding everything to Overall, I’m afraid this would need to be re-implemented from scratch either way, by now. |
The previous system is difficult to configure, with several
SystemContext
properties feeding the auth-getting logic. It also assumed that there was either a single username/password (DockerAuthConfig
) or that the auth information was on disk somewhere. With this pull request, I'm pushing all of the complexity down into a user-supplied getter (falling back to the old logic if the getter is unset). That makes it easy to plug in your own alternative without worrying about the default logic.The Docker-config
AuthStore
has a publicConfig
property to make it easy for callers to drop in their own configuration for read-only access without having to go through either the filesystem or JSON. I've made the backing types public to support that use-case, and set custom JSON serialization for the newly-public Auth structure to allow Go callers to avoid having to understand the base64 wrapping used in the JSON serialization.I've also shifted a number of auth-getting unit tests from
docker_client_test.go
intoconfig_test.go
, since they only excerciseconfig.go
logic.Ideally the auth interface would support challenge/response auth like HTTP's, but for now it just uses Docker's "we'll be able to hard-code authorization for each authority" approach.