Skip to content

Commit adf24ef

Browse files
authored
Add settings for providing Skopeo credentials #203 (#1098)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 6e8aa2a commit adf24ef

File tree

6 files changed

+121
-13
lines changed

6 files changed

+121
-13
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ v33.2.0 (unreleased)
4848
action providing a value for the new `output_format` parameter.
4949
https://github.com/nexB/scancode.io/issues/1091
5050

51-
- Add multiple settings related to fetching private files. Those settings allow to
51+
- Add settings related to fetching private files. Those settings allow to
5252
define credentials for various authentication types.
5353
https://github.com/nexB/scancode.io/issues/620
54+
https://github.com/nexB/scancode.io/issues/203
5455

5556
- Update matchcode-toolkit to v3.0.0
5657

docs/application-settings.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,22 @@ If your credentials are stored in a
373373
location on disk using::
374374

375375
SCANCODEIO_NETRC_LOCATION="~/.netrc"
376+
377+
.. _scancodeio_settings_skopeo_credentials:
378+
379+
SCANCODEIO_SKOPEO_CREDENTIALS
380+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
381+
382+
You can define the username and password for Skopeo to access containers private
383+
registries using the ``host=user:password`` syntax::
384+
385+
SCANCODEIO_SKOPEO_CREDENTIALS="host1=user:password,host2=user:password"
386+
387+
.. _scancodeio_settings_skopeo_authfile_location:
388+
389+
SCANCODEIO_SKOPEO_AUTHFILE_LOCATION
390+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
391+
392+
Specify the path of the Skopeo authentication file using the following setting::
393+
394+
SCANCODEIO_SKOPEO_AUTHFILE_LOCATION="/path/to/auth.json"

docs/faq.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,12 @@ authentication type:
209209
- :ref:`Digest authentication <scancodeio_settings_fetch_digest_auth>`
210210
- :ref:`HTTP request headers <scancodeio_settings_fetch_headers>`
211211
- :ref:`.netrc file <scancodeio_settings_netrc_location>`
212+
- :ref:`Docker private repository <scancodeio_settings_skopeo_credentials>`
212213

213214
Example for GitHub private repository files::
214215

215216
SCANCODEIO_FETCH_HEADERS="github.com=Authorization=token <YOUR_TOKEN>"
217+
218+
Example for Docker private repository::
219+
220+
SCANCODEIO_SKOPEO_CREDENTIALS="registry.com=user:password"

scancodeio/settings.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@
145145
# Propagate the location to the environ for `requests.utils.get_netrc_auth`
146146
env.ENVIRON["NETRC"] = SCANCODEIO_NETRC_LOCATION
147147

148+
# SCANCODEIO_SKOPEO_CREDENTIALS="host1=user:password,host2=user:password"
149+
SCANCODEIO_SKOPEO_CREDENTIALS = env.dict("SCANCODEIO_SKOPEO_CREDENTIALS", default={})
150+
151+
# SCANCODEIO_SKOPEO_AUTHFILE_LOCATION="/path/to/auth.json"
152+
SCANCODEIO_SKOPEO_AUTHFILE_LOCATION = env.str(
153+
"SCANCODEIO_SKOPEO_AUTHFILE_LOCATION", default=""
154+
)
155+
148156
# Application definition
149157

150158
INSTALLED_APPS = [

scanpipe/pipes/fetch.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,35 @@ def _get_skopeo_location(_cache=[]):
179179
return cmd_loc
180180

181181

182-
def get_docker_image_platform(docker_reference):
182+
def get_docker_image_platform(docker_url):
183183
"""
184184
Return a platform mapping of a docker reference.
185185
If there are more than one, return the first one by default.
186186
"""
187187
skopeo_executable = _get_skopeo_location()
188+
189+
authentication_args = []
190+
authfile = settings.SCANCODEIO_SKOPEO_AUTHFILE_LOCATION
191+
if authfile:
192+
authentication_args.append(f"--authfile {authfile}")
193+
194+
netloc = urlparse(docker_url).netloc
195+
if credential := settings.SCANCODEIO_SKOPEO_CREDENTIALS.get(netloc):
196+
# Username and password for accessing the registry.
197+
authentication_args.append(f"--creds {credential}")
198+
elif not authfile:
199+
# Access the registry anonymously.
200+
authentication_args.append("--no-creds")
201+
188202
cmd_args = (
189203
str(skopeo_executable),
190204
"inspect",
191205
"--insecure-policy",
192206
"--raw",
193-
"--no-creds",
194-
docker_reference,
207+
*authentication_args,
208+
docker_url,
195209
)
210+
196211
logger.info(f"Fetching image os/arch data: {cmd_args}")
197212
output = run_command_safely(cmd_args)
198213
logger.info(output)
@@ -232,28 +247,28 @@ def get_docker_image_platform(docker_reference):
232247
)
233248

234249

235-
def fetch_docker_image(download_url, to=None):
250+
def fetch_docker_image(docker_url, to=None):
236251
"""
237-
Fetch a docker image from the provided Docker image `download_url`,
252+
Fetch a docker image from the provided Docker image `docker_url`,
238253
using the "docker://reference" URL syntax.
239254
Return a `Download` object.
240255
241256
Docker references are documented here:
242257
https://github.com/containers/skopeo/blob/0faf16017/docs/skopeo.1.md#image-names
243258
"""
244259
whitelist = r"^docker://[a-zA-Z0-9_.:/@-]+$"
245-
if not re.match(whitelist, download_url):
260+
if not re.match(whitelist, docker_url):
246261
raise ValueError("Invalid Docker reference.")
247262

248-
reference = download_url.replace("docker://", "")
263+
reference = docker_url.replace("docker://", "")
249264
filename = f"{python_safe_name(reference)}.tar"
250265
download_directory = to or tempfile.mkdtemp()
251266
output_file = Path(download_directory, filename)
252267
target = f"docker-archive:{output_file}"
253268
skopeo_executable = _get_skopeo_location()
254269

255270
platform_args = []
256-
if platform := get_docker_image_platform(download_url):
271+
if platform := get_docker_image_platform(docker_url):
257272
os, arch, variant = platform
258273
if os:
259274
platform_args.append(f"--override-os={os}")
@@ -262,12 +277,22 @@ def fetch_docker_image(download_url, to=None):
262277
if variant:
263278
platform_args.append(f"--override-variant={variant}")
264279

280+
authentication_args = []
281+
if authfile := settings.SCANCODEIO_SKOPEO_AUTHFILE_LOCATION:
282+
authentication_args.append(f"--authfile {authfile}")
283+
284+
netloc = urlparse(docker_url).netloc
285+
if credential := settings.SCANCODEIO_SKOPEO_CREDENTIALS.get(netloc):
286+
# Credentials for accessing the source registry.
287+
authentication_args.append(f"--src-creds {credential}")
288+
265289
cmd_args = (
266290
str(skopeo_executable),
267291
"copy",
268292
"--insecure-policy",
269293
*platform_args,
270-
download_url,
294+
*authentication_args,
295+
docker_url,
271296
target,
272297
)
273298
logger.info(f"Fetching image with: {cmd_args}")
@@ -277,7 +302,7 @@ def fetch_docker_image(download_url, to=None):
277302
checksums = multi_checksums(output_file, ("md5", "sha1"))
278303

279304
return Download(
280-
uri=download_url,
305+
uri=docker_url,
281306
directory=download_directory,
282307
filename=filename,
283308
path=output_file,

scanpipe/tests/pipes/test_fetch.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def test_scanpipe_pipes_fetch_docker_image(
9696
expected = "Invalid Docker reference."
9797
self.assertEqual(expected, str(cm.exception))
9898

99-
url = "docker://debian:10.9"
99+
url = "docker://registry.com/debian:10.9"
100100
mock_platform.return_value = "linux", "amd64", ""
101101
mock_skopeo.return_value = "skopeo"
102102
mock_run_command_safely.side_effect = Exception
@@ -112,11 +112,61 @@ def test_scanpipe_pipes_fetch_docker_image(
112112
"--insecure-policy",
113113
"--override-os=linux",
114114
"--override-arch=amd64",
115-
"docker://debian:10.9",
115+
url,
116116
)
117117
self.assertEqual(expected, cmd_args[0:6])
118118
self.assertTrue(cmd_args[-1].endswith("debian_10_9.tar"))
119119

120+
with override_settings(SCANCODEIO_SKOPEO_AUTHFILE_LOCATION="auth.json"):
121+
with self.assertRaises(Exception):
122+
fetch.fetch_docker_image(url)
123+
cmd_args = mock_run_command_safely.call_args[0][0]
124+
self.assertIn("--authfile auth.json", cmd_args)
125+
126+
credentials = {"registry.com": "user:password"}
127+
with override_settings(SCANCODEIO_SKOPEO_CREDENTIALS=credentials):
128+
with self.assertRaises(Exception):
129+
fetch.fetch_docker_image(url)
130+
cmd_args = mock_run_command_safely.call_args[0][0]
131+
self.assertIn("--src-creds user:password", cmd_args)
132+
133+
@mock.patch("scanpipe.pipes.fetch._get_skopeo_location")
134+
@mock.patch("scanpipe.pipes.fetch.run_command_safely")
135+
def test_scanpipe_pipes_fetch_get_docker_image_platform(
136+
self,
137+
mock_run_command_safely,
138+
mock_skopeo,
139+
):
140+
url = "docker://registry.com/busybox"
141+
mock_skopeo.return_value = "skopeo"
142+
mock_run_command_safely.return_value = "{}"
143+
144+
fetch.get_docker_image_platform(url)
145+
mock_run_command_safely.assert_called_once()
146+
cmd_args = mock_run_command_safely.call_args[0][0]
147+
expected = (
148+
"skopeo",
149+
"inspect",
150+
"--insecure-policy",
151+
"--raw",
152+
"--no-creds",
153+
url,
154+
)
155+
self.assertEqual(expected, cmd_args)
156+
157+
with override_settings(SCANCODEIO_SKOPEO_AUTHFILE_LOCATION="auth.json"):
158+
fetch.get_docker_image_platform(url)
159+
cmd_args = mock_run_command_safely.call_args[0][0]
160+
self.assertIn("--authfile auth.json", cmd_args)
161+
self.assertNotIn("--no-creds", cmd_args)
162+
163+
credentials = {"registry.com": "user:password"}
164+
with override_settings(SCANCODEIO_SKOPEO_CREDENTIALS=credentials):
165+
fetch.get_docker_image_platform(url)
166+
cmd_args = mock_run_command_safely.call_args[0][0]
167+
self.assertIn("--creds user:password", cmd_args)
168+
self.assertNotIn("--no-creds", cmd_args)
169+
120170
def test_scanpipe_pipes_fetch_docker_image_string_injection_protection(self):
121171
url = 'docker://;echo${IFS}"PoC"${IFS}"'
122172
with self.assertRaises(ValueError) as cm:

0 commit comments

Comments
 (0)