Skip to content

Commit

Permalink
Add support for TLS/SSL settings to Bucket
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicoretti committed Feb 15, 2024
1 parent 499c0a3 commit fe017dd
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 11 deletions.
37 changes: 27 additions & 10 deletions exasol/bucketfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ def __init__(
Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
"""
self._url = _parse_service_url(url)
self._authenticator = defaultdict(
Expand Down Expand Up @@ -176,20 +175,36 @@ def __getitem__(self, item: str) -> "Bucket":


class Bucket:
def __init__(self, name: str, service: str, username: str, password: str):
def __init__(
self,
name: str,
service: str,
username: str,
password: str,
verify: bool | str = True,
):
"""
Create a new bucket instance.
Args:
name: of the bucket.
service: url where this bucket is hosted on.
username: used for authentication.
password: used for authentication.
name:
Name of the bucket.
service:
Url where this bucket is hosted on.
username:
Username used for authentication.
password:
Password used for authentication.
verify:
Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
"""
self._name = name
self._service = _parse_service_url(service)
self._username = username
self._password = password
self._verify = verify

def __str__(self):
return f"Bucket<{self.name} | on: {self._service}>"
Expand All @@ -205,7 +220,7 @@ def _auth(self) -> HTTPBasicAuth:
@property
def files(self) -> Iterable[str]:
url = _build_url(service_url=self._service, bucket=self.name)
response = requests.get(url, auth=self._auth)
response = requests.get(url, auth=self._auth, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -228,7 +243,7 @@ def upload(
data: raw content of the file.
"""
url = _build_url(service_url=self._service, bucket=self.name, path=path)
response = requests.put(url, data=data, auth=self._auth)
response = requests.put(url, data=data, auth=self._auth, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -245,7 +260,7 @@ def delete(self, path) -> None:
A BucketFsError if the operation couldn't be executed successfully.
"""
url = _build_url(service_url=self._service, bucket=self.name, path=path)
response = requests.delete(url, auth=self._auth)
response = requests.delete(url, auth=self._auth, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -263,7 +278,9 @@ def download(self, path: str, chunk_size: int = 8192) -> Iterable[ByteString]:
An iterable of binary chunks representing the downloaded file.
"""
url = _build_url(service_url=self._service, bucket=self.name, path=path)
with requests.get(url, stream=True, auth=self._auth) as response:
with requests.get(
url, stream=True, auth=self._auth, verify=self._verify
) as response:
try:
response.raise_for_status()
except HTTPError as ex:
Expand Down
5 changes: 4 additions & 1 deletion exasol/bucketfs/version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# ATTENTION:
# This file is generated, do not edit it manually!
# This file is generated by exasol/toolbox/pre_commit_hooks/package_version.py when using:
# * either "poetry run nox -s fix"
# * or "poetry run version-check <path/version.py> --fix"
# Do not edit this file manually!
# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`.
MAJOR = 0
MINOR = 9
Expand Down
136 changes: 136 additions & 0 deletions test/integration/bucketfs_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import random
import string
from contextlib import contextmanager
from inspect import cleandoc
from typing import (
ByteString,
Expand All @@ -20,9 +21,22 @@
Bucket,
Service,
as_bytes,
as_string,
)


@contextmanager
def does_not_raise(exception_type: Exception = Exception):
try:
yield

except exception_type as ex:
raise AssertionError(f"Raised exception {ex} when it should not!") from ex

except Exception as ex:
raise AssertionError(f"An unexpected exception {ex} raised.") from ex


@pytest.mark.parametrize(
"expected",
[
Expand Down Expand Up @@ -156,3 +170,125 @@ def test_ssl_verification_for_bucketfs_service_can_be_bypassed(httpsserver):
expected = ["default", "demo_foo", "demo_bar"]
actual = [bucket for bucket in bucketfs]
assert expected == actual


def test_ssl_verification_for_bucket_files_fails(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
)

with pytest.raises(requests.exceptions.SSLError) as execinfo:
_ = {file for file in bucket}
assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo)


def test_ssl_verification_for_bucket_files_can_be_bypassed(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
verify=False,
)

with does_not_raise(requests.exceptions.SSLError):
_ = {file for file in bucket}


def test_ssl_verification_for_bucket_upload_fails(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
)

with pytest.raises(requests.exceptions.SSLError) as execinfo:
data = bytes([65, 65, 65, 65])
bucket.upload("some/other/path/file2.bin", data)
assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo)


def test_ssl_verification_for_bucket_upload_can_be_bypassed(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
verify=False,
)

with does_not_raise(requests.exceptions.SSLError):
data = bytes([65, 65, 65, 65])
bucket.upload("some/other/path/file2.bin", data)


def test_ssl_verification_for_bucket_delete_fails(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
)

with pytest.raises(requests.exceptions.SSLError) as execinfo:
bucket.delete("some/other/path/file2.bin")
assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo)


def test_ssl_verification_for_bucket_delete_can_be_bypassed(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
verify=False,
)

with does_not_raise(requests.exceptions.SSLError):
bucket.delete("some/other/path/file2.bin")


def test_ssl_verification_for_bucket_download_fails(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
)

with pytest.raises(requests.exceptions.SSLError) as execinfo:
_ = as_string(bucket.download("some/other/path/file2.bin"))
assert "CERTIFICATE_VERIFY_FAILED" in str(execinfo)


def test_ssl_verification_for_bucket_download_can_be_bypassed(httpsserver):
response = "Client should not be able to retrieve this!"
httpsserver.serve_content(response, 200)
bucket = Bucket(
name="foo",
service=httpsserver.url,
username="user",
password="pw",
verify=False,
)

with does_not_raise(requests.exceptions.SSLError):
_ = as_string(bucket.download("some/other/path/file2.bin"))

0 comments on commit fe017dd

Please sign in to comment.