Skip to content

Commit a210ba6

Browse files
Merge pull request #89 from lisongmin/add-registry-authentication
Add registry authentication
2 parents c9b0383 + b0bfb21 commit a210ba6

File tree

7 files changed

+128
-3
lines changed

7 files changed

+128
-3
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ More detailed instructions are in the [`example README.md`](https://github.com/R
111111
| **cap_add** | []string | no | Add individual capabilities. |
112112
| **cap_drop** | []string | no | Drop invidual capabilities. |
113113
| **devices** | []string | no | A list of devices to be exposed to the container. |
114+
| **auth** | block | no | Provide authentication for a private registry. See [Auth](#auth) for more details. |
114115
| **mounts** | []block | no | A list of mounts to be mounted in the container. Volume, bind and tmpfs type mounts are supported. fstab style [`mount options`](https://github.com/containerd/containerd/blob/master/mount/mount_linux.go#L211-L235) are supported. |
115116

116117
**Mount block**<br/>
@@ -162,6 +163,21 @@ config {
162163
}
163164
```
164165

166+
### auth
167+
168+
If you want to pull from a private repository e.g. docker hub, you can specify `username` and `password` in the `auth` stanza. See example below.
169+
170+
**NOTE**: In the below example, `user` and `pass` are just placeholder values which need to be replaced by actual `username` and `password`, when specifying the credentials.
171+
172+
```
173+
config {
174+
auth {
175+
username = "user"
176+
password = "pass"
177+
}
178+
}
179+
```
180+
165181
## Networking
166182

167183
`nomad-driver-containerd` supports **host** and **bridge** networks.<br/>

containerd/containerd.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/containerd/containerd/contrib/seccomp"
2929
"github.com/containerd/containerd/oci"
3030
refdocker "github.com/containerd/containerd/reference/docker"
31+
remotesdocker "github.com/containerd/containerd/remotes/docker"
3132
specs "github.com/opencontainers/runtime-spec/specs-go"
3233
)
3334

@@ -61,7 +62,26 @@ func (d *Driver) getContainerdVersion() (containerd.Version, error) {
6162
return d.client.Version(ctxWithTimeout)
6263
}
6364

64-
func (d *Driver) pullImage(imageName, imagePullTimeout string) (containerd.Image, error) {
65+
type CredentialsOpt func(string) (string, string, error)
66+
67+
func parshAuth(auth *RegistryAuth) CredentialsOpt {
68+
return func(string) (string, string, error) {
69+
if auth == nil {
70+
return "", "", nil
71+
}
72+
return auth.Username, auth.Password, nil
73+
}
74+
}
75+
76+
func withResolver(creds CredentialsOpt) containerd.RemoteOpt {
77+
resolver := remotesdocker.NewResolver(remotesdocker.ResolverOptions{
78+
Hosts: remotesdocker.ConfigureDefaultRegistries(remotesdocker.WithAuthorizer(
79+
remotesdocker.NewAuthorizer(nil, creds))),
80+
})
81+
return containerd.WithResolver(resolver)
82+
}
83+
84+
func (d *Driver) pullImage(imageName, imagePullTimeout string, auth *RegistryAuth) (containerd.Image, error) {
6585
pullTimeout, err := time.ParseDuration(imagePullTimeout)
6686
if err != nil {
6787
return nil, fmt.Errorf("Failed to parse image_pull_timeout: %v", err)
@@ -75,7 +95,12 @@ func (d *Driver) pullImage(imageName, imagePullTimeout string) (containerd.Image
7595
return nil, err
7696
}
7797

78-
return d.client.Pull(ctxWithTimeout, named.String(), containerd.WithPullUnpack)
98+
pullOpts := []containerd.RemoteOpt{
99+
containerd.WithPullUnpack,
100+
withResolver(parshAuth(auth)),
101+
}
102+
103+
return d.client.Pull(ctxWithTimeout, named.String(), pullOpts...)
79104
}
80105

81106
func (d *Driver) createContainer(containerConfig *ContainerConfig, config *TaskConfig) (containerd.Container, error) {

containerd/driver.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ var (
116116
"sysctl": hclspec.NewAttr("sysctl", "list(map(string))", false),
117117
"readonly_rootfs": hclspec.NewAttr("readonly_rootfs", "bool", false),
118118
"host_network": hclspec.NewAttr("host_network", "bool", false),
119+
"auth": hclspec.NewBlock("auth", false, hclspec.NewObject(map[string]*hclspec.Spec{
120+
"username": hclspec.NewAttr("username", "string", false),
121+
"password": hclspec.NewAttr("password", "string", false),
122+
})),
119123
"mounts": hclspec.NewBlockList("mounts", hclspec.NewObject(map[string]*hclspec.Spec{
120124
"type": hclspec.NewDefault(
121125
hclspec.NewAttr("type", "string", false),
@@ -155,6 +159,12 @@ type Mount struct {
155159
Options []string `codec:"options"`
156160
}
157161

162+
// Auth info to pull image from registry.
163+
type RegistryAuth struct {
164+
Username string `codec:"username"`
165+
Password string `codec:"password"`
166+
}
167+
158168
// TaskConfig contains configuration information for a task that runs with
159169
// this plugin
160170
type TaskConfig struct {
@@ -177,6 +187,7 @@ type TaskConfig struct {
177187
Entrypoint []string `codec:"entrypoint"`
178188
ReadOnlyRootfs bool `codec:"readonly_rootfs"`
179189
HostNetwork bool `codec:"host_network"`
190+
Auth RegistryAuth `codec:"auth"`
180191
Mounts []Mount `codec:"mounts"`
181192
}
182193

@@ -411,7 +422,7 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
411422
containerConfig.ContainerName = containerName
412423

413424
var err error
414-
containerConfig.Image, err = d.pullImage(driverConfig.Image, driverConfig.ImagePullTimeout)
425+
containerConfig.Image, err = d.pullImage(driverConfig.Image, driverConfig.ImagePullTimeout, &driverConfig.Auth)
415426
if err != nil {
416427
return nil, nil, fmt.Errorf("Error in pulling image %s: %v", driverConfig.Image, err)
417428
}

example/auth.nomad

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
job "auth" {
2+
datacenters = ["dc1"]
3+
4+
reschedule {
5+
delay = "9s"
6+
delay_function = "constant"
7+
unlimited = true
8+
}
9+
10+
group "auth-group" {
11+
task "auth-task" {
12+
driver = "containerd-driver"
13+
14+
config {
15+
image = "shm32/hello-world:private"
16+
}
17+
18+
resources {
19+
cpu = 500
20+
memory = 256
21+
}
22+
}
23+
}
24+
}

tests/009-test-auth.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
3+
source $SRCDIR/utils.sh
4+
5+
job_name=auth
6+
7+
# test auth
8+
test_auth_nomad_job() {
9+
pushd ~/go/src/github.com/Roblox/nomad-driver-containerd/example
10+
11+
echo "INFO: Starting nomad $job_name job using nomad-driver-containerd."
12+
nomad job run $job_name.nomad
13+
14+
wait_nomad_job_status $job_name failed
15+
16+
echo "INFO: Checking can not pull image without auth info."
17+
local alloc_id
18+
alloc_id=$(nomad job status auth|grep Allocations -A2|tail -n 1 |awk '{print $1}')
19+
nomad status "$alloc_id"|grep -q "pull access denied, repository does not exist or may require authorization"
20+
if [ $? -ne 0 ];then
21+
echo "ERROR: Can not found pull access denied in alloc log."
22+
exit 1
23+
fi
24+
25+
echo "INFO: purge nomad ${job_name} job."
26+
nomad job stop -purge ${job_name}
27+
popd
28+
}
29+
30+
test_auth_nomad_job
File renamed without changes.

tests/utils.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
#!/bin/bash
22

3+
wait_nomad_job_status() {
4+
local job_name=$1
5+
local expected_status="$2"
6+
7+
local status
8+
local i=0
9+
while [ $i -lt 5 ]; do
10+
status=$(nomad job status $job_name|grep Allocations -A2|tail -n 1 |awk '{print $6}')
11+
if [ "$status" == "$expected_status" ]; then
12+
return
13+
fi
14+
sleep 4
15+
i=$((i + 1))
16+
done
17+
18+
echo "ERROR: ${job_name} didn't enter $expected_status status. exit 1."
19+
exit 1
20+
}
21+
322
is_container_active() {
423
local job_name=$1
524
local is_sleep=$2

0 commit comments

Comments
 (0)