1
+ import json
1
2
import os
2
3
import secrets
3
4
import shutil
4
5
import socket
5
6
import subprocess
7
+ from tempfile import TemporaryDirectory
8
+ from base64 import b64encode
6
9
import time
10
+ import bcrypt
7
11
from pathlib import Path
8
12
9
13
import pytest
16
20
17
21
18
22
@pytest .fixture (scope = "session" )
19
- def dind (registry , host_ip ):
23
+ def dind (registry ):
20
24
port = get_free_port ()
25
+ registry_host , _ , _ = registry
21
26
22
27
# docker daemon will generate certs here, that we can then use to connect to it.
23
28
# put it in current dir than in /tmp because on macos, current dir is likely to
@@ -42,7 +47,7 @@ def dind(registry, host_ip):
42
47
"--host" ,
43
48
"0.0.0.0:2376" ,
44
49
"--insecure-registry" ,
45
- registry ,
50
+ registry_host ,
46
51
]
47
52
proc = subprocess .Popen (cmd )
48
53
time .sleep (5 )
@@ -80,11 +85,27 @@ def host_ip():
80
85
@pytest .fixture (scope = "session" )
81
86
def registry (host_ip ):
82
87
port = get_free_port ()
88
+ username = "user"
89
+ password = secrets .token_hex (16 )
90
+ bcrypted_pw = bcrypt .hashpw (password .encode ("utf-8" ), bcrypt .gensalt (rounds = 12 )).decode ("utf-8" )
91
+
92
+ # We put our password here, and mount it into the container.
93
+ # put it in current dir than in /tmp because on macos, current dir is likely to
94
+ # shared with docker VM so it can be mounted, unlike /tmp
95
+ htpasswd_dir = HERE / f"tmp-certs-{ secrets .token_hex (8 )} "
96
+ htpasswd_dir .mkdir ()
97
+ (htpasswd_dir / "htpasswd.conf" ).write_text (f"{ username } :{ bcrypted_pw } " )
98
+
83
99
# Explicitly pull the image first so it runs on time
84
100
registry_image = "registry:3.0.0-rc.3"
85
101
subprocess .check_call (["docker" , "pull" , registry_image ])
86
102
87
- cmd = ["docker" , "run" , "--rm" , "-p" , f"{ port } :5000" , registry_image ]
103
+ cmd = ["docker" , "run" , "--rm" ,
104
+ "-e" , "REGISTRY_AUTH=htpasswd" ,
105
+ "-e" , "REGISTRY_AUTH_HTPASSWD_REALM=basic" ,
106
+ "-e" , "REGISTRY_AUTH_HTPASSWD_PATH=/opt/htpasswd/htpasswd.conf" ,
107
+ "--mount" , f"type=bind,src={ htpasswd_dir } ,dst=/opt/htpasswd" ,
108
+ "-p" , f"{ port } :5000" , registry_image ]
88
109
proc = subprocess .Popen (cmd )
89
110
health_url = f"http://{ host_ip } :{ port } /v2"
90
111
# Wait for the registry to actually come up
@@ -101,14 +122,18 @@ def registry(host_ip):
101
122
raise TimeoutError ("Test registry did not come up in time" )
102
123
103
124
try :
104
- yield f"{ host_ip } :{ port } "
125
+ yield f"{ host_ip } :{ port } " , username , password
105
126
finally :
106
127
proc .terminate ()
107
128
proc .wait ()
108
129
109
130
110
- def test_registry (registry , dind ):
111
- image_name = f"{ registry } /{ secrets .token_hex (8 )} :latest"
131
+ def test_registry_explicit_creds (registry , dind ):
132
+ """
133
+ Test that we can push to registry when given explicit credentials
134
+ """
135
+ registry_host , username , password = registry
136
+ image_name = f"{ registry_host } /{ secrets .token_hex (8 )} :latest"
112
137
r2d = make_r2d (["--image" , image_name , "--push" , "--no-run" , str (HERE )])
113
138
114
139
docker_host , cert_dir = dind
@@ -119,10 +144,55 @@ def test_registry(registry, dind):
119
144
os .environ ["DOCKER_HOST" ] = docker_host
120
145
os .environ ["DOCKER_CERT_PATH" ] = str (cert_dir / "client" )
121
146
os .environ ["DOCKER_TLS_VERIFY" ] = "1"
147
+ os .environ ["CONTAINER_ENGINE_REGISTRY_CREDENTIALS" ] = json .dumps ({
148
+ "registry" : f"http://{ registry_host } " ,
149
+ "username" : username ,
150
+ "password" : password
151
+ })
122
152
r2d .start ()
123
153
154
+
124
155
proc = subprocess .run (["docker" , "manifest" , "inspect" , "--insecure" , image_name ])
125
156
assert proc .returncode == 0
157
+
158
+ # Validate that we didn't leak our registry creds into existing docker config
159
+ docker_config_path = Path (os .environ .get ("DOCKER_CONFIG" , "~/.docker/config.json" )).expanduser ()
160
+ if docker_config_path .exists ():
161
+ # Just check that our randomly generated password is not in this file
162
+ # Can this cause a conflict? Sure, if there's a different randomly generated password in here
163
+ # that matches our own randomly generated password. But if you're that unlucky, take cover from the asteroid.
164
+ assert password not in docker_config_path .read_text ()
165
+ finally :
166
+ os .environ .clear ()
167
+ os .environ .update (old_environ )
168
+
169
+
170
+ def test_registry_no_explicit_creds (registry , dind ):
171
+ """
172
+ Test that we can push to registry *without* explicit credentials but reading from a DOCKER_CONFIG
173
+ """
174
+ registry_host , username , password = registry
175
+ image_name = f"{ registry_host } /{ secrets .token_hex (8 )} :latest"
176
+ r2d = make_r2d (["--image" , image_name , "--push" , "--no-run" , str (HERE )])
177
+
178
+ docker_host , cert_dir = dind
179
+
180
+ old_environ = os .environ .copy ()
181
+
182
+ try :
183
+ os .environ ["DOCKER_HOST" ] = docker_host
184
+ os .environ ["DOCKER_CERT_PATH" ] = str (cert_dir / "client" )
185
+ os .environ ["DOCKER_TLS_VERIFY" ] = "1"
186
+ with TemporaryDirectory () as d :
187
+ (Path (d ) / "config.json" ).write_text (json .dumps (
188
+ ({"auths" :{f"http://{ registry_host } " :{"auth" :b64encode (f"{ username } :{ password } " .encode ()).decode ()}}})
189
+ ))
190
+ os .environ ["DOCKER_CONFIG" ] = d
191
+ r2d .start ()
192
+
193
+
194
+ proc = subprocess .run (["docker" , "manifest" , "inspect" , "--insecure" , image_name ])
195
+ assert proc .returncode == 0
126
196
finally :
127
197
os .environ .clear ()
128
198
os .environ .update (old_environ )
0 commit comments