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

types: Add SystemContext.GetAuth #588

Closed
wants to merge 1 commit into from
Closed

Conversation

wking
Copy link
Contributor

@wking wking commented Feb 22, 2019

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 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.

I've also shifted a number of auth-getting unit tests from docker_client_test.go into config_test.go, since they only excercise config.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.

Copy link
Collaborator

@mtrmac mtrmac left a 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
Copy link
Collaborator

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 😭

credentials/single/single.go Outdated Show resolved Hide resolved
@@ -0,0 +1,42 @@
// Package single implements a basic credentials helper with a single username and secret.
Copy link
Collaborator

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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

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?

pkg/docker/config/config.go Outdated Show resolved Hide resolved
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)
Copy link
Collaborator

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!

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

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.
Copy link
Collaborator

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.
Copy link
Collaborator

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{…}

Copy link
Contributor Author

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 an AuthGetter 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.

@wking
Copy link
Contributor Author

wking commented Feb 27, 2019

I'll rebase this once #589 lands, so I can rebase this on top to shrink the diff size.

@rhatdan
Copy link
Member

rhatdan commented Mar 1, 2019

Looks like it has merged.

@wking
Copy link
Contributor Author

wking commented Mar 1, 2019

Rebased onto master (around #589) with 44801dd -> 245b3b3, which also fixes a few bugs.

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]>
@wking
Copy link
Contributor Author

wking commented Mar 6, 2019

Rebased onto master (no conflicts) with 245b3b3 -> 4800205 to resolve the "This branch is out-of-date with the base branch" warning.

wking added a commit to wking/openshift-installer that referenced this pull request Mar 12, 2019
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
@wking
Copy link
Contributor Author

wking commented Mar 18, 2019

I'm going to hold off on rebasing this until #597 settles.

@rhatdan
Copy link
Member

rhatdan commented Apr 25, 2019

@wking #597 merged can you rebase.

@wking
Copy link
Contributor Author

wking commented Sep 24, 2019

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.

@mtrmac mtrmac added the kind/feature A request for, or a PR adding, new functionality label Dec 7, 2022
@mtrmac
Copy link
Collaborator

mtrmac commented Nov 27, 2023

Four years later, the credential code has changed a lot.

The implementation approach of mirroring the semantics of github.com/docker/docker-credential-helpers/credentials.Helper is no longer sufficient (if it ever was, because the helper implementations are IIRC wildly inconsistent about the lookup keys the support) because at least some of those helpers key off the host name, but we now support more granular credential namespaces (quay.io/openshift-release-dev).

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 Config.{Auths,CredHelpers}.

I do, now, agree that many of the c/image subpackages should have their own configuration API without adding everything to SystemContext. pkg/docker/config is one of the places where introducing that is hard, because it depends on pkg/sysregistriesv2, so that one would have to be extended to have a non-SystemContext API first. (And, now that we unwisely bumped the major number to opt into Go module semantics, we would still need to keep all the SystemContext APIs around.)

Overall, I’m afraid this would need to be re-implemented from scratch either way, by now.

@mtrmac mtrmac closed this Nov 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature A request for, or a PR adding, new functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants