Add token pass-thru for Authconfig

This augments the CreateContainer call to detect the AuthConfig header
and use any supplied auth for pull operations.  This will allow pulling
of protected image on to specific node during the create operation.

CLI usage example using username/password:

    # Calculate the header
    REPO_USER=yourusername
    read -s PASSWORD
    HEADER=$(echo "{\"username\":\"${REPO_USER}\",\"password\":\"${PASSWORD}\"}"|base64 -w 0 )
    unset PASSWORD
    echo HEADER=$HEADER

    # Then add the following to your ~/.docker/config.json
    "HttpHeaders": {
        "X-Registry-Auth": "<HEADER string from above>"
    }

    # Now run a private image against swarm:
    docker run --rm -it yourprivateimage:latest

CLI usage example using registry tokens: (Required engine 1.10 with new auth token support)

    REPO=yourrepo/yourimage
    REPO_USER=yourusername
    read -s PASSWORD
    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 )
    echo HEADER=$HEADER

    # Update the docker config as above, but the token will expire quickly...

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
This commit is contained in:
Daniel Hiltgen 2015-11-04 11:03:33 -08:00
parent f0b785a206
commit dde577d154
7 changed files with 71 additions and 19 deletions

View File

@ -427,7 +427,15 @@ func postContainersCreate(c *context, w http.ResponseWriter, r *http.Request) {
return
}
container, err := c.cluster.CreateContainer(cluster.BuildContainerConfig(config), name)
// Pass auth information along if present
var authConfig *dockerclient.AuthConfig
buf, err := base64.URLEncoding.DecodeString(r.Header.Get("X-Registry-Auth"))
if err == nil {
authConfig = &dockerclient.AuthConfig{}
json.Unmarshal(buf, authConfig)
}
container, err := c.cluster.CreateContainer(cluster.BuildContainerConfig(config), name, authConfig)
if err != nil {
if strings.HasPrefix(err.Error(), "Conflict") {
httpError(w, err.Error(), http.StatusConflict)

View File

@ -9,7 +9,7 @@ import (
// Cluster is exported
type Cluster interface {
// Create a container
CreateContainer(config *ContainerConfig, name string) (*Container, error)
CreateContainer(config *ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (*Container, error)
// Remove a container
RemoveContainer(container *Container, force, volumes bool) error

View File

@ -503,7 +503,7 @@ func (e *Engine) TotalCpus() int64 {
}
// Create a new container
func (e *Engine) Create(config *ContainerConfig, name string, pullImage bool) (*Container, error) {
func (e *Engine) Create(config *ContainerConfig, name string, pullImage bool, authConfig *dockerclient.AuthConfig) (*Container, error) {
var (
err error
id string
@ -521,17 +521,17 @@ func (e *Engine) Create(config *ContainerConfig, name string, pullImage bool) (*
dockerConfig.CpuShares = int64(math.Ceil(float64(config.CpuShares*1024) / float64(e.Cpus)))
dockerConfig.HostConfig.CpuShares = dockerConfig.CpuShares
if id, err = client.CreateContainer(&dockerConfig, name); err != nil {
if id, err = client.CreateContainer(&dockerConfig, name, nil); err != nil {
// If the error is other than not found, abort immediately.
if err != dockerclient.ErrImageNotFound || !pullImage {
return nil, err
}
// Otherwise, try to pull the image...
if err = e.Pull(config.Image, nil); err != nil {
if err = e.Pull(config.Image, authConfig); err != nil {
return nil, err
}
// ...And try again.
if id, err = client.CreateContainer(&dockerConfig, name); err != nil {
if id, err = client.CreateContainer(&dockerConfig, name, nil); err != nil {
return nil, err
}
}

View File

@ -193,13 +193,14 @@ func TestCreateContainer(t *testing.T) {
// Everything is ok
name := "test1"
id := "id1"
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
var auth *dockerclient.AuthConfig
client.On("CreateContainer", &mockConfig, name, auth).Return(id, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
container, err := engine.Create(config, name, false)
container, err := engine.Create(config, name, false, auth)
assert.Nil(t, err)
assert.Equal(t, container.Id, id)
assert.Len(t, engine.Containers(), 1)
@ -207,8 +208,8 @@ func TestCreateContainer(t *testing.T) {
// Image not found, pullImage == false
name = "test2"
mockConfig.CpuShares = int64(math.Ceil(float64(config.CpuShares*1024) / float64(mockInfo.NCPU)))
client.On("CreateContainer", &mockConfig, name).Return("", dockerclient.ErrImageNotFound).Once()
container, err = engine.Create(config, name, false)
client.On("CreateContainer", &mockConfig, name, auth).Return("", dockerclient.ErrImageNotFound).Once()
container, err = engine.Create(config, name, false, auth)
assert.Equal(t, err, dockerclient.ErrImageNotFound)
assert.Nil(t, container)
@ -217,14 +218,14 @@ func TestCreateContainer(t *testing.T) {
id = "id3"
mockConfig.CpuShares = int64(math.Ceil(float64(config.CpuShares*1024) / float64(mockInfo.NCPU)))
client.On("PullImage", config.Image+":latest", mock.Anything).Return(nil).Once()
client.On("CreateContainer", &mockConfig, name).Return("", dockerclient.ErrImageNotFound).Once()
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
client.On("CreateContainer", &mockConfig, name, auth).Return("", dockerclient.ErrImageNotFound).Once()
client.On("CreateContainer", &mockConfig, name, auth).Return(id, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
container, err = engine.Create(config, name, true)
container, err = engine.Create(config, name, true, auth)
assert.Nil(t, err)
assert.Equal(t, container.Id, id)
assert.Len(t, engine.Containers(), 2)

View File

@ -164,7 +164,7 @@ func (c *Cluster) RegisterEventHandler(h cluster.EventHandler) error {
}
// CreateContainer for container creation in Mesos task
func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string) (*cluster.Container, error) {
func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (*cluster.Container, error) {
if config.Memory == 0 && config.CpuShares == 0 {
return nil, errResourcesNeeded
}

View File

@ -115,8 +115,8 @@ func (c *Cluster) generateUniqueID() string {
}
// CreateContainer aka schedule a brand new container into the cluster.
func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string) (*cluster.Container, error) {
container, err := c.createContainer(config, name, false)
func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (*cluster.Container, error) {
container, err := c.createContainer(config, name, false, authConfig)
// fails with image not found, then try to reschedule with soft-image-affinity
if err != nil {
@ -125,14 +125,14 @@ func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string)
// Check if the image exists in the cluster
// If exists, retry with a soft-image-affinity
if image := c.Image(config.Image); image != nil {
container, err = c.createContainer(config, name, true)
container, err = c.createContainer(config, name, true, authConfig)
}
}
}
return container, err
}
func (c *Cluster) createContainer(config *cluster.ContainerConfig, name string, withSoftImageAffinity bool) (*cluster.Container, error) {
func (c *Cluster) createContainer(config *cluster.ContainerConfig, name string, withSoftImageAffinity bool, authConfig *dockerclient.AuthConfig) (*cluster.Container, error) {
c.scheduler.Lock()
// Ensure the name is available
@ -170,7 +170,7 @@ func (c *Cluster) createContainer(config *cluster.ContainerConfig, name string,
c.scheduler.Unlock()
container, err := engine.Create(config, name, true)
container, err := engine.Create(config, name, true, authConfig)
c.scheduler.Lock()
delete(c.pendingContainers, swarmID)

View File

@ -45,6 +45,49 @@ POST "/images/create" : "docker import" flow not implement
* `POST "/containers/create"`: `CpuShares` in `HostConfig` sets the number of CPU cores allocated to the container.
# Registry Authentication
During container create calls, the swarm API will optionally accept a X-Registry-Config header.
If provided, this header will be passed down to the engine if the image must be pulled
to complete the create operation.
The following two examples demonstrate how to utilize this using the existing docker CLI
* CLI usage example using username/password:
```bash
# Calculate the header
REPO_USER=yourusername
read -s PASSWORD
HEADER=$(echo "{\"username\":\"${REPO_USER}\",\"password\":\"${PASSWORD}\"}"|base64 -w 0 )
unset PASSWORD
echo HEADER=$HEADER
# Then add the following to your ~/.docker/config.json
"HttpHeaders": {
"X-Registry-Auth": "<HEADER string from above>"
}
# Now run a private image against swarm:
docker run --rm -it yourprivateimage:latest
```
* CLI usage example using registry tokens: (Requires engine 1.10 with new auth token support)
```bash
REPO=yourrepo/yourimage
REPO_USER=yourusername
read -s PASSWORD
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 )
echo HEADER=$HEADER
# Update the docker config as above, but the token will expire quickly...
```
## Docker Swarm documentation index
- [User guide](https://docs.docker.com/swarm/)