ソースを参照

Add token pass-thru for AuthConfig

This change allows API clients to retrieve an authentication token from
a registry, and then pass that token directly to the API.

Example usage:

    REPO_USER=dhiltgen
    read -s PASSWORD
    REPO=privateorg/repo
    AUTH_URL=https://auth.docker.io/token
    TOKEN=$(curl -s -u "${REPO_USER}:${PASSWORD}" "${AUTH_URL}?scope=repository:${REPO}:pull&service=registry.docker.io" |
        jq -r ".token")

    HEADER=$(echo "{\"registrytoken\":\"${TOKEN}\"}"|base64 -w 0 )
    curl -s -D - -H "X-Registry-Auth: ${HEADER}" -X POST "http://localhost:2376/images/create?fromImage=${REPO}"

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
Daniel Hiltgen 9 年 前
コミット
8dce8e9901

+ 1 - 0
cliconfig/config.go

@@ -51,6 +51,7 @@ type AuthConfig struct {
 	Auth          string `json:"auth"`
 	Auth          string `json:"auth"`
 	Email         string `json:"email"`
 	Email         string `json:"email"`
 	ServerAddress string `json:"serveraddress,omitempty"`
 	ServerAddress string `json:"serveraddress,omitempty"`
+	RegistryToken string `json:"registrytoken,omitempty"`
 }
 }
 
 
 // ConfigFile ~/.docker/config.json file info
 // ConfigFile ~/.docker/config.json file info

+ 23 - 4
distribution/registry.go

@@ -2,6 +2,7 @@ package distribution
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"fmt"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
@@ -91,10 +92,15 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
 		return nil, err
 		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...)
 	tr := transport.NewTransport(base, modifiers...)
 
 
 	return client.NewRepository(ctx, repoName.Name(), endpoint.URL, tr)
 	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
 	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
+}

+ 95 - 0
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 := &registry.RepositoryInfo{
+		Index: &registry.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")
+	}
+
+}

+ 1 - 0
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 /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 
 * `GET /version` now returns the `BuildTime` field in RFC3339Nano format to make it 
   consistent with other date/time values returned by the API.
   consistent with other date/time values returned by the API.
+* `AuthConfig` now supports a `registrytoken` for token based authentication
 
 
 ### v1.21 API changes
 ### v1.21 API changes
 
 

+ 36 - 3
docs/reference/api/docker_remote_api_v1.22.md

@@ -1532,7 +1532,24 @@ Query Parameters:
 
 
     Request Headers:
     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:
 Status Codes:
 
 
@@ -1741,8 +1758,24 @@ Query Parameters:
 
 
 Request Headers:
 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:
 Status Codes: