Skip to content

Commit aac8629

Browse files
committed
Propagate the tls_verify parameter to the auth backends
Signed-off-by: Jonathan GAYVALLET <jonathan.gayvallet@orange.com>
1 parent 938390a commit aac8629

File tree

8 files changed

+90
-5
lines changed

8 files changed

+90
-5
lines changed

oras/auth/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ class AuthenticationException(Exception):
1414
pass
1515

1616

17-
def get_auth_backend(name="token", session=None, insecure=False, **kwargs):
17+
def get_auth_backend(
18+
name="token", session=None, insecure=False, tls_verify=True, **kwargs
19+
):
1820
backend = auth_backends.get(name)
1921
if not backend:
2022
raise ValueError(f"Authentication backend {backend} is not known.")
2123
backend = backend(**kwargs)
2224
backend.session = session or requests.Session()
2325
backend.prefix = "http" if insecure else "https"
26+
backend._tls_verify = tls_verify
2427
return backend

oras/auth/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from typing import Optional
77

8+
import requests
9+
810
import oras.auth.utils as auth_utils
911
import oras.container
1012
import oras.decorator as decorator
@@ -17,6 +19,9 @@ class AuthBackend:
1719
Generic (and default) auth backend.
1820
"""
1921

22+
session: requests.Session
23+
_tls_verify: bool
24+
2025
def __init__(self, *args, **kwargs):
2126
self._auths: dict = {}
2227
self.prefix: str = "https"

oras/auth/token.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def request_token(self, h: auth_utils.authHeader) -> bool:
127127
headers["Authorization"] = "Basic %s" % self._basic_auth
128128

129129
logger.debug(f"Requesting auth token for: {h}")
130-
authResponse = self.session.get(h.realm, headers=headers, params=params) # type: ignore
130+
authResponse = self.session.get(h.realm, headers=headers, params=params, verify=self._tls_verify) # type: ignore
131131

132132
if authResponse.status_code != 200:
133133
logger.debug(f"Auth response was not successful: {authResponse.text}")
@@ -154,7 +154,9 @@ def request_anonymous_token(self, h: auth_utils.authHeader) -> bool:
154154
params["scope"] = h.scope
155155

156156
logger.debug(f"Requesting anon token with params: {params}")
157-
response = self.session.request("GET", h.realm, params=params)
157+
response = self.session.request(
158+
"GET", h.realm, params=params, verify=self._tls_verify
159+
)
158160
if response.status_code != 200:
159161
logger.debug(f"Response for anon token failed: {response.text}")
160162
return

oras/decorator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import time
66
from functools import wraps
77

8+
import requests.exceptions
9+
810
import oras.auth
911
from oras.logger import logger
1012

@@ -50,6 +52,8 @@ def inner(*args, **kwargs):
5052
return res
5153
except oras.auth.AuthenticationException as e:
5254
raise e
55+
except requests.exceptions.SSLError:
56+
raise
5357
except Exception as e:
5458
sleep = timeout + 3**attempt
5559
logger.info(f"Retrying in {sleep} seconds - error: {e}")

oras/provider.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ def __init__(
7878
self.session.cookies.set_policy(DefaultCookiePolicy(allowed_domains=[]))
7979

8080
# Get custom backend, pass on session to share
81-
self.auth = oras.auth.get_auth_backend(auth_backend, self.session, insecure)
81+
self.auth = oras.auth.get_auth_backend(
82+
auth_backend, self.session, insecure, tls_verify=tls_verify
83+
)
8284

8385
def __repr__(self) -> str:
8486
return str(self)

oras/tests/snakeoil.crt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDATCCAemgAwIBAgIUVD3r8T8WSZDh9kfPNus7RgWf+mIwDQYJKoZIhvcNAQEL
3+
BQAwFzEVMBMGA1UEAwwMeWQtb3I2MjkyNDkyMB4XDTI1MDEwNzE5MzgyN1oXDTM1
4+
MDEwNTE5MzgyN1owFzEVMBMGA1UEAwwMeWQtb3I2MjkyNDkyMIIBIjANBgkqhkiG
5+
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoiQ4lEoFDmoCFCLWH9gRyebWlnv15OPdykwV
6+
N2P7nLf+fFFOyQgE59jc2gVw9TeOVmnXxncDOYQrLKikHko8vGKg+daT1DLIufjz
7+
ijOtdUz9Zgd0rt5Ar7ocnjxkOYJRSW9cBd51O6jpUGN3r1BziQMdvIywDSw+BX2F
8+
IMLvg2tlHuMb1dR0GHemwqwwdzAsw9QJB3MbGSfVsH4/QQxRsQhpq/5D3WyZJPlJ
9+
/tYmJhUPVVF94ZLAhqfDueWj+6LaiDUVLQ0CUml8S33Q9570e8JvAYYHwveRIkw/
10+
W7lVoKu0OnkCKrKWjqgweBfszVz6ARTd8QPCCZO6At99RLjZBwIDAQABo0UwQzAJ
11+
BgNVHRMEAjAAMBcGA1UdEQQQMA6CDHlkLW9yNjI5MjQ5MjAdBgNVHQ4EFgQUSNhV
12+
pFANLtcSQsnuT4qrZ7TdADUwDQYJKoZIhvcNAQELBQADggEBABNaYMotGee4GT4J
13+
6lw6eRq/Zzy6xN1n0iSJW4QJvPkbOoFa+CBRtQ/Vk5yiKOB3yX7SOdilm5yzQbBd
14+
QKsYgEhkPi8F/LN3UYDgE69soIBJv9Fx/EcmbsqLZqz60xSuwtVcqtJ9MuewZhZm
15+
5ny2zbCNQxK6JehscFKVeR/W088jfWmtp4kIQIdK9h4301dAeNu8Si35HGtQupIw
16+
+jliOQtOFu3nikbBH+k8wwi4/2tMtEAsKbr83ixfkjkkivT7B5A9NNHNpNTzYw/T
17+
HGQaoWWIMf6Zcea33lM087raIC83qU/CuJYiNVvUkw7CnV2P/yg/FpCxsP0AudGC
18+
4FLB77A=
19+
-----END CERTIFICATE-----

oras/tests/test_oras.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44

55
import os
66
import shutil
7+
import tempfile
8+
import time
9+
from pathlib import Path
710

811
import pytest
12+
from requests.exceptions import SSLError
913

1014
import oras.client
1115

@@ -227,3 +231,49 @@ def test_custom_docker_config_path(tmp_path, registry, credentials, target_dir):
227231
assert "artifact.txt" in os.listdir(files[0])
228232

229233
client.logout(registry)
234+
235+
236+
@pytest.fixture
237+
def empty_request_ca():
238+
old_ca = os.environ.get("REQUESTS_CA_BUNDLE", None)
239+
try:
240+
# we're setting a fake CA since an empty one won't work
241+
os.environ["REQUESTS_CA_BUNDLE"] = str(Path(__file__).parent / "snakeoil.crt")
242+
yield
243+
finally:
244+
if old_ca is not None:
245+
os.environ["REQUESTS_CA_BUNDLE"] = old_ca
246+
else:
247+
del os.environ["REQUESTS_CA_BUNDLE"]
248+
249+
250+
def test_ssl_no_verify(empty_request_ca):
251+
"""
252+
Make sure the client works without a CA file and tls_verify set to False
253+
"""
254+
client = oras.client.OrasClient(
255+
hostname="ghcr.io", insecure=False, tls_verify=False
256+
)
257+
client.get_tags("channel-mirrors/conda-forge/linux-aarch64/arrow-cpp", N=1)
258+
259+
260+
def test_ssl_verify_fails_if_bad_ca(empty_request_ca):
261+
"""
262+
Make sure the client fails without a CA file and tls_verify set to True
263+
"""
264+
client = oras.client.OrasClient(hostname="ghcr.io", insecure=False, tls_verify=True)
265+
266+
with pytest.raises(SSLError):
267+
client.get_tags("channel-mirrors/conda-forge/linux-aarch64/arrow-cpp", N=1)
268+
269+
270+
def test_ssl_verify_fails_fast_if_bad_ca(empty_request_ca):
271+
"""
272+
The client should fail fast in case of SSL errors
273+
"""
274+
client = oras.client.OrasClient(hostname="ghcr.io", insecure=False, tls_verify=True)
275+
st = time.monotonic()
276+
with pytest.raises(SSLError):
277+
client.get_tags("channel-mirrors/conda-forge/linux-aarch64/arrow-cpp", N=1)
278+
et = time.monotonic()
279+
assert et - st < 5

oras/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
__copyright__ = "Copyright The ORAS Authors."
33
__license__ = "Apache-2.0"
44

5-
__version__ = "0.2.27"
5+
__version__ = "0.2.28"
66
AUTHOR = "Vanessa Sochat"
77
EMAIL = "vsoch@users.noreply.github.com"
88
NAME = "oras"

0 commit comments

Comments
 (0)