diff --git a/cliconfig/config.go b/cliconfig/config.go index bdd75871a4..87b92acdf8 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -51,6 +51,7 @@ type AuthConfig struct { Auth string `json:"auth"` Email string `json:"email"` ServerAddress string `json:"serveraddress,omitempty"` + RegistryToken string `json:"registrytoken,omitempty"` } // ConfigFile ~/.docker/config.json file info diff --git a/distribution/registry.go b/distribution/registry.go index 5ec45530bc..ed8b8c20e5 100644 --- a/distribution/registry.go +++ b/distribution/registry.go @@ -2,6 +2,7 @@ package distribution import ( "errors" + "fmt" "net" "net/http" "net/url" @@ -91,10 +92,15 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd return nil, err } - creds := dumbCredentialStore{auth: authConfig} - tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName.Name(), actions...) - basicHandler := auth.NewBasicHandler(creds) - modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) + if authConfig.RegistryToken != "" { + passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken} + modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) + } else { + creds := dumbCredentialStore{auth: authConfig} + tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName.Name(), actions...) + basicHandler := auth.NewBasicHandler(creds) + modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) + } tr := transport.NewTransport(base, modifiers...) return client.NewRepository(ctx, repoName.Name(), endpoint.URL, tr) @@ -113,3 +119,16 @@ func digestFromManifest(m *schema1.SignedManifest, localName string) (digest.Dig } return manifestDigest, len(payload), nil } + +type existingTokenHandler struct { + token string +} + +func (th *existingTokenHandler) Scheme() string { + return "bearer" +} + +func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token)) + return nil +} diff --git a/distribution/registry_unit_test.go b/distribution/registry_unit_test.go new file mode 100644 index 0000000000..bf13934622 --- /dev/null +++ b/distribution/registry_unit_test.go @@ -0,0 +1,95 @@ +package distribution + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/Sirupsen/logrus" + "github.com/docker/distribution/reference" + "github.com/docker/distribution/registry/client/auth" + "github.com/docker/docker/cliconfig" + "github.com/docker/docker/pkg/streamformatter" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +func TestTokenPassThru(t *testing.T) { + authConfig := &cliconfig.AuthConfig{ + RegistryToken: "mysecrettoken", + } + gotToken := false + handler := func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.Header.Get("Authorization"), authConfig.RegistryToken) { + logrus.Debug("Detected registry token in auth header") + gotToken = true + } + if r.RequestURI == "/v2/" { + w.Header().Set("WWW-Authenticate", `Bearer realm="foorealm"`) + w.WriteHeader(401) + } + } + ts := httptest.NewServer(http.HandlerFunc(handler)) + defer ts.Close() + + tmp, err := utils.TestDirectory("") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + endpoint := registry.APIEndpoint{ + Mirror: false, + URL: ts.URL, + Version: 2, + Official: false, + TrimHostname: false, + TLSConfig: nil, + //VersionHeader: "verheader", + Versions: []auth.APIVersion{ + { + Type: "registry", + Version: "2", + }, + }, + } + n, _ := reference.ParseNamed("testremotename") + repoInfo := ®istry.RepositoryInfo{ + Index: ®istry.IndexInfo{ + Name: "testrepo", + Mirrors: nil, + Secure: false, + Official: false, + }, + RemoteName: n, + LocalName: n, + CanonicalName: n, + Official: false, + } + imagePullConfig := &ImagePullConfig{ + MetaHeaders: http.Header{}, + AuthConfig: authConfig, + } + sf := streamformatter.NewJSONStreamFormatter() + puller, err := newPuller(endpoint, repoInfo, imagePullConfig, sf) + if err != nil { + t.Fatal(err) + } + p := puller.(*v2Puller) + p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") + if err != nil { + t.Fatal(err) + } + + logrus.Debug("About to pull") + // We expect it to fail, since we haven't mock'd the full registry exchange in our handler above + tag, _ := reference.WithTag(n, "tag_goes_here") + _ = p.pullV2Repository(tag) + + if !gotToken { + t.Fatal("Failed to receive registry token") + } + +} diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index b4310aa651..50c809325b 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -101,6 +101,7 @@ This section lists each version from latest to oldest. Each listing includes a * `GET /networks/(name)` now returns a `Name` field for each container attached to the network. * `GET /version` now returns the `BuildTime` field in RFC3339Nano format to make it consistent with other date/time values returned by the API. +* `AuthConfig` now supports a `registrytoken` for token based authentication ### v1.21 API changes diff --git a/docs/reference/api/docker_remote_api_v1.22.md b/docs/reference/api/docker_remote_api_v1.22.md index b59a5e64a0..cf6c2f3c0c 100644 --- a/docs/reference/api/docker_remote_api_v1.22.md +++ b/docs/reference/api/docker_remote_api_v1.22.md @@ -1540,7 +1540,24 @@ Query Parameters: Request Headers: -- **X-Registry-Auth** – base64-encoded AuthConfig object +- **X-Registry-Auth** – base64-encoded AuthConfig object, containing either login information, or a token + - Credential based login: + + ``` + { + "username": "jdoe", + "password": "secret", + "email": "jdoe@acme.com", + } + ``` + + - Token based login: + + ``` + { + "registrytoken": "9cbaf023786cd7..." + } + ``` Status Codes: @@ -1749,8 +1766,24 @@ Query Parameters: Request Headers: -- **X-Registry-Auth** – Include a base64-encoded AuthConfig. - object. +- **X-Registry-Auth** – base64-encoded AuthConfig object, containing either login information, or a token + - Credential based login: + + ``` + { + "username": "jdoe", + "password": "secret", + "email": "jdoe@acme.com", + } + ``` + + - Token based login: + + ``` + { + "registrytoken": "9cbaf023786cd7..." + } + ``` Status Codes: