Skip to content

Commit e883dd1

Browse files
authored
Merge pull request #630 from aperture-data/release-0.4.52
Release 0.4.52
2 parents 1a0624a + 18f1289 commit e883dd1

File tree

8 files changed

+110
-49
lines changed

8 files changed

+110
-49
lines changed

aperturedb/CommonLibrary.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def __create_connector(configuration: Configuration):
4141
password=configuration.password,
4242
use_ssl=configuration.use_ssl,
4343
ca_cert=configuration.ca_cert,
44+
verify_hostname=configuration.verify_hostname,
4445
config=configuration)
4546
else:
4647
connector = Connector(
@@ -51,6 +52,7 @@ def __create_connector(configuration: Configuration):
5152
password=configuration.password,
5253
use_ssl=configuration.use_ssl,
5354
ca_cert=configuration.ca_cert,
55+
verify_hostname=configuration.verify_hostname,
5456
config=configuration)
5557
logger.debug(
5658
f"Created connector using: {configuration}. Will connect on query.")

aperturedb/Configuration.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
APERTUREDB_CLOUD = ".cloud.aperturedata.io"
88
APERTUREDB_KEY_VERSION = 2
99
APERTUREDB_OLD_KEY_VERSION = 1
10+
FLAG_VERIFY_HOSTNAME = 8
1011
FLAG_USE_COMPRESSED_HOST = 4
1112
FLAG_USE_REST = 2
1213
FLAG_USE_SSL = 1
@@ -35,15 +36,29 @@ class Configuration:
3536
token: str = None
3637
user_keys: dict = None
3738
ca_cert: str = None
39+
verify_hostname: bool = True
40+
41+
def __ssl_mode(self) -> str:
42+
if not self.use_ssl:
43+
mode = "SSL_OFF" # with not use_ssl, we don't use SSL
44+
elif not self.verify_hostname:
45+
mode = "SSL_NO_VERIFY" # with verify_hostname=False, we don't verify the hostname
46+
elif self.ca_cert:
47+
# with verify_hostname=True and ca_cert is provided, we verify the hostname using the provided ca_cert
48+
mode = "SSL_WITH_CA"
49+
else:
50+
mode = "SSL_DEFAULT"
51+
52+
return mode
3853

3954
def __repr__(self) -> str:
4055
mode = "REST" if self.use_rest else "TCP"
4156
auth_mode = "token" if self.token is not None else "password"
42-
return f"[{self.host}:{self.port} as {self.username} using {mode} with SSL={self.use_ssl} auth={auth_mode}]"
57+
return f"[{self.host}:{self.port} as {self.username} using {mode} with SSL={self.__ssl_mode()} auth={auth_mode}]"
4358

4459
def deflate(self) -> list:
4560
return self.create_aperturedb_key(self.host, self.port, self.token,
46-
self.use_rest, self.use_ssl, self.ca_cert, self.username, self.password)
61+
self.use_rest, self.use_ssl, self.ca_cert, self.username, self.password, self.verify_hostname)
4762

4863
def has_user_keys(self) -> bool:
4964
return self.user_keys is not None
@@ -65,16 +80,20 @@ def set_user_keys(self, keys: dict) -> None:
6580
self.user_keys = keys
6681

6782
@classmethod
68-
def config_to_key_type(cls, compressed_host: bool, use_rest: bool, use_ssl: bool):
69-
return (FLAG_USE_COMPRESSED_HOST if compressed_host else 0) + \
70-
(FLAG_USE_REST if use_rest else 0) + \
71-
(FLAG_USE_SSL if use_ssl else 0)
83+
def config_to_key_type(cls, compressed_host: bool, use_rest: bool, use_ssl: bool, verify_hostname: bool):
84+
return (
85+
(FLAG_USE_COMPRESSED_HOST if compressed_host else 0) |
86+
(FLAG_USE_REST if use_rest else 0) |
87+
(FLAG_USE_SSL if use_ssl else 0) |
88+
(FLAG_VERIFY_HOSTNAME if verify_hostname else 0)
89+
)
7290

7391
@classmethod
7492
def key_type_to_config(cls, key_type: int): \
7593
return [bool(key_type & FLAG_USE_COMPRESSED_HOST),
7694
bool(key_type & FLAG_USE_REST),
77-
bool(key_type & FLAG_USE_SSL)]
95+
bool(key_type & FLAG_USE_SSL),
96+
bool(key_type & FLAG_VERIFY_HOSTNAME)]
7897

7998
@classmethod
8099
def config_default_port(cls, use_rest: bool, use_ssl: bool):
@@ -87,7 +106,7 @@ def config_default_port(cls, use_rest: bool, use_ssl: bool):
87106
def create_aperturedb_key(
88107
cls, host: str, port: int, token_string: str,
89108
use_rest: bool, use_ssl: bool, ca_cert: str = None,
90-
username: str = None, password: str = None) -> None:
109+
username: str = None, password: str = None, verify_hostname: bool = True) -> None:
91110
compressed = False
92111
if token_string is not None and token_string.startswith("adbp_"):
93112
token_string = token_string[5:]
@@ -99,7 +118,8 @@ def create_aperturedb_key(
99118
host = "{}.{}".format(m.group(1), int(m.group(2)))
100119
compressed = True
101120

102-
key_type = cls.config_to_key_type(compressed, use_rest, use_ssl)
121+
key_type = cls.config_to_key_type(
122+
compressed, use_rest, use_ssl, verify_hostname)
103123
default_port = cls.config_default_port(use_rest, use_ssl)
104124
if port != default_port:
105125
host = f"{host}:{port}"
@@ -126,10 +146,14 @@ def reinflate(cls, encoded_str: list) -> object:
126146
if version not in (APERTUREDB_KEY_VERSION, APERTUREDB_OLD_KEY_VERSION):
127147
raise ValueError("version identifier of configuration was"
128148
f"{version}, which is not supported")
129-
is_compressed, use_rest, use_ssl = cls.key_type_to_config(as_list[1])
149+
is_compressed, use_rest, use_ssl, verify_hostname = cls.key_type_to_config(
150+
as_list[1])
130151
host = as_list[2]
131152
pem = None
132153

154+
if version == APERTUREDB_OLD_KEY_VERSION:
155+
verify_hostname = True
156+
133157
if version == APERTUREDB_KEY_VERSION:
134158
pem = as_list[3]
135159
list_offset = 4
@@ -160,7 +184,7 @@ def reinflate(cls, encoded_str: list) -> object:
160184
f"Unable to parse compressed host: {host} Error: {e}")
161185

162186
c = Configuration(
163-
host, port, username, password, name, use_ssl, use_rest, ca_cert=pem)
187+
host, port, username, password, name, use_ssl, use_rest, ca_cert=pem, verify_hostname=verify_hostname)
164188
if token:
165189
c.token = token
166190
return c

aperturedb/Connector.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def __init__(self, host="localhost", port=DEFAULT_PORT,
149149
shared_data=None,
150150
authenticate=True,
151151
use_keepalive=True,
152+
verify_hostname=True,
152153
retry_interval_seconds=DEFAULT_RETRY_INTERVAL_SECONDS,
153154
retry_max_attempts=DEFAULT_RETRY_MAX_ATTEMPTS,
154155
config: Optional[Configuration] = None,
@@ -179,6 +180,7 @@ def __init__(self, host="localhost", port=DEFAULT_PORT,
179180
port=port,
180181
use_ssl=use_ssl,
181182
ca_cert=ca_cert,
183+
verify_hostname=verify_hostname,
182184
username=user,
183185
password=password,
184186
name="runtime",
@@ -334,6 +336,26 @@ def _refresh_token(self):
334336
else:
335337
raise UnauthorizedException(response)
336338

339+
def _build_ssl_context(self):
340+
"""
341+
Builds an SSL context for the connection.
342+
There are 3 scenarios:
343+
1. verify_hostname is False, we don't verify the hostname
344+
2. verify_hostname is True and ca_cert is provided, we verify the hostname using the provided ca_cert
345+
3. verify_hostname is True and ca_cert is not provided, we verify the hostname using the system's default CA certificates
346+
"""
347+
self.context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
348+
if not self.config.verify_hostname:
349+
self.context.check_hostname = False
350+
self.context.verify_mode = ssl.CERT_NONE
351+
elif self.config.ca_cert:
352+
self.context.load_verify_locations(
353+
cafile=self.config.ca_cert
354+
)
355+
else:
356+
self.context.load_default_certs(ssl.Purpose.SERVER_AUTH)
357+
return self.context
358+
337359
def _connect(self):
338360
self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
339361
self.conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
@@ -375,35 +397,28 @@ def _connect(self):
375397

376398
if self.use_ssl:
377399

378-
# Server is ok with SSL, we switch over SSL.
379-
self.context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
380-
self.context.verify_mode = ssl.CERT_REQUIRED
381-
self.context.check_hostname = True
382-
if self.config.ca_cert:
383-
self.context.load_verify_locations(
384-
cafile=self.config.ca_cert
385-
)
386-
else:
387-
self.context.load_default_certs(ssl.Purpose.SERVER_AUTH)
400+
self.context = self._build_ssl_context()
388401

389402
# TODO, we need to add support for local certificates
390403
# For now, we let the server send us the certificate
391404
try:
392-
self.conn = self.context.wrap_socket(
393-
self.conn, server_hostname=self.host)
405+
if self.config.verify_hostname:
406+
self.conn = self.context.wrap_socket(
407+
self.conn, server_hostname=self.host)
408+
else:
409+
self.conn = self.context.wrap_socket(
410+
self.conn)
394411
except ssl.SSLCertVerificationError as e:
395-
logger.exception(
396-
f"The host name must match the certificate: {self.host}")
397412
logger.exception(
398413
f"You can use the ca_cert parameter to specify a custom CA certificate")
399414
assert False, "Certificate verification failed" + os.linesep + \
400415
f"The host name must match the certificate: {self.host} " + os.linesep + \
401416
f"You can use the ca_cert parameter to specify a custom CA certificate " + os.linesep + \
402417
f"Refer to the documentation for more information: {SETUP_URL}" + os.linesep + \
403-
f"Alternatively, SSL can be disabled by setting use_ssl=False (not recommended)" + os.linesep + \
404-
f"{e=}"
418+
f"Alternatively, SSL can be disabled by setting verify_hostname=False or use_ssl=False (not recommended)" + \
419+
os.linesep
405420
except ssl.SSLError as e:
406-
logger.error(f"Error wrapping socket: {e}")
421+
logger.exception(f"Error wrapping socket.")
407422
self.conn.close()
408423
self.connected = False
409424
raise
@@ -417,8 +432,8 @@ def _connect(self):
417432
f"The ca certificate file does not exist: {self.config.ca_cert} " + os.linesep + \
418433
f"You can use the ca_cert parameter to specify a custom CA certificate " + os.linesep + \
419434
f"Refer to the documentation for more information: {SETUP_URL} " + os.linesep + \
420-
f"Alternatively, SSL can be disabled by setting use_ssl=False (not recommended)" + os.linesep + \
421-
f"{e=}"
435+
f"Alternatively, SSL can be disabled by setting verify_hostname=False or use_ssl=False (not recommended)" + \
436+
os.linesep
422437
except BaseException as e:
423438
self.conn.close()
424439
self.connected = False

aperturedb/ConnectorRest.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class ConnectorRest(Connector):
8181

8282
def __init__(self, host="localhost", port=None,
8383
user="", password="", token="",
84-
use_ssl=True, ca_cert=None, shared_data=None,
84+
use_ssl=True, ca_cert=None, verify_hostname=True, shared_data=None,
8585
config: Optional[Configuration] = None,
8686
key: Optional[str] = None):
8787
self.use_keepalive = False
@@ -93,6 +93,7 @@ def __init__(self, host="localhost", port=None,
9393
token=token,
9494
use_ssl=use_ssl,
9595
ca_cert=ca_cert,
96+
verify_hostname=verify_hostname,
9697
shared_data=shared_data,
9798
config=config,
9899
key=key)
@@ -112,11 +113,12 @@ def __init__(self, host="localhost", port=None,
112113
# Since we will be making same call to the same URL, making a session
113114
# REF: https://requests.readthedocs.io/en/latest/user/advanced/
114115
self.http_session = requests.Session()
115-
if self.config.ca_cert:
116-
adapter = CustomHTTPAdapter(ca_cert=self.config.ca_cert)
117-
else:
118-
adapter = CustomHTTPAdapter(ca_cert=None)
119-
self.http_session.mount('https://', adapter=adapter)
116+
if self.config.verify_hostname:
117+
if self.config.ca_cert:
118+
adapter = CustomHTTPAdapter(ca_cert=self.config.ca_cert)
119+
else:
120+
adapter = CustomHTTPAdapter(ca_cert=None)
121+
self.http_session.mount('https://', adapter=adapter)
120122

121123
self.last_response = ''
122124
self.last_query_time = 0
@@ -155,10 +157,11 @@ def _query(self, query, blob_array = [], try_resume=True):
155157
while tries < self.config.retry_max_attempts:
156158
tries += 1
157159
try:
160+
# URL takes care of the scheme
158161
response = self.http_session.post(self.url,
159162
headers = headers,
160163
files = files,
161-
verify = self.use_ssl)
164+
verify = self.config.use_ssl and self.config.verify_hostname)
162165
if response.status_code == 200:
163166
# Parse response:
164167
json_response = json.loads(response.text)

aperturedb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import signal
1111
import sys
1212

13-
__version__ = "0.4.51"
13+
__version__ = "0.4.52"
1414

1515
logger = logging.getLogger(__name__)
1616

aperturedb/cli/configure.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ def get_configurations(file: str):
7777
token=config["token"] if "token" in config else None,
7878
use_rest=config["use_rest"],
7979
use_ssl=config["use_ssl"],
80-
ca_cert=config.get("ca_cert", None))
80+
ca_cert=config.get("ca_cert", None),
81+
verify_hostname=config.get("verify_hostname", True))
8182
if "user_keys" in config:
8283
configs[c].set_user_keys(config["user_keys"])
8384
active = configurations["active"]
@@ -147,7 +148,10 @@ def create(
147148
password: Annotated[str, typer.Option(help="Password")] = "admin",
148149
use_rest: Annotated[bool, typer.Option(help="Use REST")] = False,
149150
use_ssl: Annotated[bool, typer.Option(help="Use SSL")] = True,
150-
ca_cert: Annotated[str, typer.Option(help="CA certificate")] = None,
151+
ca_cert: Annotated[Optional[str],
152+
typer.Option(help="CA certificate")] = "",
153+
verify_hostname: Annotated[bool, typer.Option(
154+
help="Verify hostname")] = True,
151155
interactive: Annotated[bool, typer.Option(
152156
help="Interactive mode")] = True,
153157
overwrite: Annotated[bool, typer.Option(
@@ -179,6 +183,7 @@ def check_for_overwrite(name):
179183
db_use_rest = use_rest
180184
db_use_ssl = use_ssl
181185
db_ca_cert = ca_cert
186+
db_verify_hostname = verify_hostname
182187
config_path = _config_file_path(as_global)
183188
configs = {}
184189
try:
@@ -228,10 +233,15 @@ def check_for_overwrite(name):
228233
db_use_ssl = typer.confirm(
229234
f"Use SSL [Note: ApertureDB's defaults do not allow non SSL traffic]", default=db_use_ssl)
230235
db_ca_cert = typer.prompt(
231-
f"Enter {APP_NAME} CA certificate's (if custom CA is used)", default=db_ca_cert)
232-
db_ca_cert = os.path.abspath(db_ca_cert)
233-
assert os.path.exists(
234-
db_ca_cert), "CA certificate file does not exist"
236+
f"Enter {APP_NAME} CA certificate's path (if custom CA is used)", default=db_ca_cert)
237+
if db_ca_cert != "":
238+
db_ca_cert = os.path.abspath(db_ca_cert)
239+
assert os.path.exists(
240+
db_ca_cert), f"CA certificate file {db_ca_cert} does not exist"
241+
else:
242+
db_ca_cert = None
243+
db_verify_hostname = typer.confirm(
244+
f"Verify hostname", default=db_verify_hostname)
235245

236246
gen_config = Configuration(
237247
name=name,
@@ -241,7 +251,8 @@ def check_for_overwrite(name):
241251
password=db_password,
242252
use_ssl=db_use_ssl,
243253
use_rest=db_use_rest,
244-
ca_cert=db_ca_cert
254+
ca_cert=db_ca_cert,
255+
verify_hostname=db_verify_hostname
245256
)
246257

247258
assert name is not None, "Configuration name must be specified"

aperturedb/cli/keys.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ def generate_user_key(conn: Connector, user: str):
2323
token = u.generate_token()
2424
u.assign_token(user, token)
2525
key = Configuration.create_aperturedb_key(
26-
conn.config.host, conn.config.port, token, conn.config.use_rest,
27-
conn.config.use_ssl, conn.config.ca_cert)
26+
host=conn.config.host,
27+
port=conn.config.port,
28+
token_string=token,
29+
use_rest=conn.config.use_rest,
30+
use_ssl=conn.config.use_ssl,
31+
ca_cert=conn.config.ca_cert,
32+
verify_hostname=conn.config.verify_hostname)
2833
return key

test/test_Key.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def test_encode_keys(self):
3636
config_type = data[1]
3737
host = data[2]
3838
username = password = token = None
39-
comp, rest, ssl = Configuration.key_type_to_config(config_type)
39+
comp, rest, ssl, verify_hostname = Configuration.key_type_to_config(
40+
config_type)
4041
if host.rfind(':') != -1:
4142
port = int(host.split(':')[1])
4243
host = host.split(':')[0]
@@ -48,7 +49,7 @@ def test_encode_keys(self):
4849
username = data[3]
4950
password = data[4]
5051
c = Configuration(host, port, username, password,
51-
"encoding test", use_rest=rest, use_ssl=ssl, token=token)
52+
"encoding test", use_rest=rest, use_ssl=ssl, token=token, verify_hostname=verify_hostname)
5253
deflated = c.deflate()
5354
assert deflated == key
5455

0 commit comments

Comments
 (0)