Skip to content

Commit

Permalink
docker: fix a bug where auth for private registries wasn't parsed cor…
Browse files Browse the repository at this point in the history
…rectly (#24215)

In #23966 we introduced an official Docker client and did not notice that in
contrast to our previous 3rd party client, the official SDK PullOptions object
expects a base64 encoded JSON with username and password, instead of username/
password pair.
  • Loading branch information
pkazmierczak authored Oct 16, 2024
1 parent a0d7fb6 commit f9cbaaf
Show file tree
Hide file tree
Showing 14 changed files with 577 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .changelog/24215.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
docker: Fix incorrect auth parsing for private registries
```
5 changes: 5 additions & 0 deletions drivers/docker/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2481,6 +2481,7 @@ func TestDockerDriver_AuthConfiguration(t *testing.T) {
{
Repo: "redis:7",
AuthConfig: &registry.AuthConfig{
Auth: "eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6IjEyMzQifQ==",
Username: "test",
Password: "1234",
Email: "",
Expand All @@ -2490,6 +2491,7 @@ func TestDockerDriver_AuthConfiguration(t *testing.T) {
{
Repo: "quay.io/redis:7",
AuthConfig: &registry.AuthConfig{
Auth: "eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6IjU2NzgifQ==",
Username: "test",
Password: "5678",
Email: "",
Expand All @@ -2499,6 +2501,7 @@ func TestDockerDriver_AuthConfiguration(t *testing.T) {
{
Repo: "other.io/redis:7",
AuthConfig: &registry.AuthConfig{
Auth: "eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6ImFiY2QifQ==",
Username: "test",
Password: "abcd",
Email: "",
Expand Down Expand Up @@ -2535,6 +2538,7 @@ func TestDockerDriver_AuthFromTaskConfig(t *testing.T) {
ServerAddr: "www.foobar.com",
},
AuthConfig: &registry.AuthConfig{
Auth: "eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoiYmFyIn0=",
Username: "foo",
Password: "bar",
Email: "[email protected]",
Expand All @@ -2549,6 +2553,7 @@ func TestDockerDriver_AuthFromTaskConfig(t *testing.T) {
ServerAddr: "www.foobar.com",
},
AuthConfig: &registry.AuthConfig{
Auth: "eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoiYmFyIn0=",
Username: "foo",
Password: "bar",
ServerAddress: "www.foobar.com",
Expand Down
35 changes: 33 additions & 2 deletions drivers/docker/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package docker

import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
Expand Down Expand Up @@ -102,12 +103,19 @@ func authFromTaskConfig(driverConfig *TaskConfig) authBackend {
if len(driverConfig.Auth.Username) == 0 && len(driverConfig.Auth.Password) == 0 && len(driverConfig.Auth.Email) == 0 && len(driverConfig.Auth.ServerAddr) == 0 {
return nil, nil
}
return &registrytypes.AuthConfig{

authConfig := &registrytypes.AuthConfig{
Username: driverConfig.Auth.Username,
Password: driverConfig.Auth.Password,
Email: driverConfig.Auth.Email,
ServerAddress: driverConfig.Auth.ServerAddr,
}, nil
}

if err := encodeAuth(authConfig); err != nil {
return nil, err
}

return authConfig, nil
}
}

Expand Down Expand Up @@ -140,6 +148,11 @@ func authFromDockerConfig(file string) authBackend {
IdentityToken: dockerAuthConfig.IdentityToken,
RegistryToken: dockerAuthConfig.RegistryToken,
}

if err := encodeAuth(auth); err != nil {
return nil, err
}

if authIsEmpty(auth) {
return nil, nil
}
Expand Down Expand Up @@ -187,6 +200,9 @@ func authFromHelper(helperName string) authBackend {
Username: response["Username"],
Password: response["Secret"],
}
if err := encodeAuth(auth); err != nil {
return nil, err
}

if authIsEmpty(auth) {
return nil, nil
Expand All @@ -195,6 +211,21 @@ func authFromHelper(helperName string) authBackend {
}
}

// some docker api calls require a base64 encoded basic auth string
func encodeAuth(cfg *registrytypes.AuthConfig) error {
auth := &registrytypes.AuthConfig{
Username: cfg.Username,
Password: cfg.Password,
}
encodedJSON, err := json.Marshal(auth)
if err != nil {
return fmt.Errorf("error encoding basic auth: %v", err)
}

cfg.Auth = base64.URLEncoding.EncodeToString(encodedJSON)
return nil
}

// authIsEmpty returns if auth is nil or an empty structure
func authIsEmpty(auth *registrytypes.AuthConfig) bool {
if auth == nil {
Expand Down
5 changes: 5 additions & 0 deletions e2e/docker/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

// Package docker contains test cases related to the docker task driver.
package docker
125 changes: 125 additions & 0 deletions e2e/docker/docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package docker

import (
"fmt"
"strconv"
"testing"
"time"

"github.com/hashicorp/nomad/e2e/e2eutil"
"github.com/hashicorp/nomad/e2e/v3/cluster3"
"github.com/hashicorp/nomad/e2e/v3/jobs3"
"github.com/shoenig/test/must"
)

const (
registryService = "registry"
)

func TestDocker(t *testing.T) {
cluster3.Establish(t,
cluster3.Leader(),
cluster3.LinuxClients(1),
)

runRegistry(t)

t.Run("testRedis", testRedis)
t.Run("testAuthBasic", testAuthBasic)
t.Run("testAuthFileStatic", testAuthFileStatic)
t.Run("testAuthHelper", testAuthHelper)
}

func findService(t *testing.T, name string) (string, int) {
services, _, err := e2eutil.NomadClient(t).Services().Get(name, nil)
must.NoError(t, err, must.Sprintf("failed to find %q service", name))
must.Len(t, 1, services, must.Sprintf("expected 1 %q service", name))
return services[0].Address, services[0].Port
}

func runRegistry(t *testing.T) {
_, regCleanup := jobs3.Submit(t,
"../docker_registry/registry.hcl",
jobs3.Timeout(40*time.Second), // pulls an image
)
t.Cleanup(regCleanup)

// lookup registry address
addr, port := findService(t, registryService)
address := fmt.Sprintf("%s:%d", addr, port)

t.Logf("Setting up insecure private registry at %v", address)

// run the sed job to fixup the auth.json file with correct address and make
// sure the registry is marked as insecure for docker, otherwise pulls will
// fail
_, sedCleanup := jobs3.Submit(t,
"./input/registry-auths.hcl",
jobs3.Var("registry_address", address),
jobs3.Var("user", "root"),
jobs3.Var("helper_dir", "/usr/local/bin"),
jobs3.Var("auth_dir", "/etc"),
jobs3.Var("docker_conf_dir", "/etc/docker"),
jobs3.WaitComplete("create-files"),
jobs3.Timeout(20*time.Second),
)
t.Cleanup(sedCleanup)
}

func testRedis(t *testing.T) {
job, cleanup := jobs3.Submit(t, "./input/redis.hcl")
t.Cleanup(cleanup)

logs := job.TaskLogs("cache", "redis")
must.StrContains(t, logs.Stdout, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo")
}

func testAuthBasic(t *testing.T) {
// find the private registry service
regAddr, regPort := findService(t, "registry")

// run the private bash image
bashJob, bashCleanup := jobs3.Submit(t, "./input/auth_basic.hcl",
jobs3.Var("registry_address", regAddr),
jobs3.Var("registry_port", strconv.Itoa(regPort)),
jobs3.WaitComplete("basic"),
)
t.Cleanup(bashCleanup)
logs := bashJob.TaskLogs("basic", "echo")
must.StrContains(t, logs.Stdout, "The auth basic test is OK!")
}

func testAuthFileStatic(t *testing.T) {
// find the private registry service
regAddr, regPort := findService(t, "registry")

// run the private _static bash image
bashJob, bashCleanup := jobs3.Submit(t, "./input/auth_static.hcl",
jobs3.Var("registry_address", regAddr),
jobs3.Var("registry_port", strconv.Itoa(regPort)),
jobs3.WaitComplete("static"),
)
t.Cleanup(bashCleanup)
logs := bashJob.TaskLogs("static", "echo")
must.StrContains(t, logs.Stdout, "The static auth test is OK!")
}

func testAuthHelper(t *testing.T) {
// find the private registry service
regAddr, regPort := findService(t, "registry")

t.Log("registry", regAddr, regPort)

// run the private _helper bash image
bashJob, bashCleanup := jobs3.Submit(t, "./input/auth_helper.hcl",
jobs3.Var("registry_address", regAddr),
jobs3.Var("registry_port", strconv.Itoa(regPort)),
jobs3.WaitComplete("helper"),
)
t.Cleanup(bashCleanup)
logs := bashJob.TaskLogs("helper", "echo")
must.StrContains(t, logs.Stdout, "The credentials helper auth test is OK!")
}
75 changes: 75 additions & 0 deletions e2e/docker/input/auth_basic.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

# This job runs a docker task using a container stored in a private registry
# configured with basic authentication. The registry.hcl job should be running
# and healthy before running this job. The registry_address and registry_port
# HCL variables must be provided.

variable "registry_address" {
type = string
description = "The HTTP address of the local registry"
default = "localhost"
}

variable "registry_port" {
type = number
description = "The HTTP port of the local registry"
default = "7511"
}

variable "registry_username" {
type = string
description = "The Basic Auth username of the local registry"
default = "auth_basic_user"
}

variable "registry_password" {
type = string
description = "The Basic Auth password of the local registry"
default = "auth_basic_pass"
}

locals {
registry_auth = base64encode("${var.registry_username}:${var.registry_password}")
}

job "auth_basic" {
type = "batch"

constraint {
attribute = "${attr.kernel.name}"
value = "linux"
}

group "basic" {
reschedule {
attempts = 0
unlimited = false
}

network {
mode = "host"
}

task "echo" {
driver = "docker"

config {
image = "${var.registry_address}:${var.registry_port}/docker.io/library/bash_auth_basic:private"
args = ["echo", "The auth basic test is OK!"]
auth_soft_fail = true

auth {
username = "${var.registry_username}"
password = "${var.registry_password}"
}
}

resources {
cpu = 100
memory = 64
}
}
}
}
55 changes: 55 additions & 0 deletions e2e/docker/input/auth_helper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

# This job runs a docker task using a container stored in a private registry
# configured with credentials helper authentication. The registry.hcl job should
# be running and healthy before running this job.

variable "registry_address" {
type = string
description = "The HTTP address of the local registry"
default = "localhost"
}

variable "registry_port" {
type = number
description = "The HTTP port of the local registry"
default = "7511"
}

job "auth_static" {
type = "batch"

constraint {
attribute = "${attr.kernel.name}"
value = "linux"
}

group "helper" {
reschedule {
attempts = 0
unlimited = false
}

network {
mode = "host"
}

task "echo" {
driver = "docker"

config {
image = "${var.registry_address}:${var.registry_port}/docker.io/library/bash_auth_helper:private"
args = ["echo", "The credentials helper auth test is OK!"]

# usename and password come from [docker-credential-]test.sh found on
# $PATH as specified by "helper=test.sh" in plugin config
}

resources {
cpu = 100
memory = 64
}
}
}
}
Loading

0 comments on commit f9cbaaf

Please sign in to comment.