clear-skies 2.0.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clear-skies might be problematic. Click here for more details.
- clear_skies-2.0.27.dist-info/METADATA +78 -0
- clear_skies-2.0.27.dist-info/RECORD +270 -0
- clear_skies-2.0.27.dist-info/WHEEL +4 -0
- clear_skies-2.0.27.dist-info/licenses/LICENSE +7 -0
- clearskies/__init__.py +69 -0
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +15 -0
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +23 -0
- clearskies/authentication/authorization_pass_through.py +22 -0
- clearskies/authentication/jwks.py +165 -0
- clearskies/authentication/public.py +5 -0
- clearskies/authentication/secret_bearer.py +551 -0
- clearskies/autodoc/__init__.py +8 -0
- clearskies/autodoc/formats/__init__.py +5 -0
- clearskies/autodoc/formats/oai3_json/__init__.py +7 -0
- clearskies/autodoc/formats/oai3_json/oai3_json.py +87 -0
- clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +15 -0
- clearskies/autodoc/formats/oai3_json/parameter.py +35 -0
- clearskies/autodoc/formats/oai3_json/request.py +68 -0
- clearskies/autodoc/formats/oai3_json/response.py +28 -0
- clearskies/autodoc/formats/oai3_json/schema/__init__.py +11 -0
- clearskies/autodoc/formats/oai3_json/schema/array.py +9 -0
- clearskies/autodoc/formats/oai3_json/schema/default.py +13 -0
- clearskies/autodoc/formats/oai3_json/schema/enum.py +7 -0
- clearskies/autodoc/formats/oai3_json/schema/object.py +35 -0
- clearskies/autodoc/formats/oai3_json/test.json +1985 -0
- clearskies/autodoc/py.typed +0 -0
- clearskies/autodoc/request/__init__.py +15 -0
- clearskies/autodoc/request/header.py +6 -0
- clearskies/autodoc/request/json_body.py +6 -0
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +47 -0
- clearskies/autodoc/request/url_parameter.py +6 -0
- clearskies/autodoc/request/url_path.py +6 -0
- clearskies/autodoc/response/__init__.py +5 -0
- clearskies/autodoc/response/response.py +9 -0
- clearskies/autodoc/schema/__init__.py +31 -0
- clearskies/autodoc/schema/array.py +10 -0
- clearskies/autodoc/schema/base64.py +8 -0
- clearskies/autodoc/schema/boolean.py +5 -0
- clearskies/autodoc/schema/date.py +5 -0
- clearskies/autodoc/schema/datetime.py +5 -0
- clearskies/autodoc/schema/double.py +5 -0
- clearskies/autodoc/schema/enum.py +17 -0
- clearskies/autodoc/schema/integer.py +6 -0
- clearskies/autodoc/schema/long.py +5 -0
- clearskies/autodoc/schema/number.py +6 -0
- clearskies/autodoc/schema/object.py +13 -0
- clearskies/autodoc/schema/password.py +5 -0
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +5 -0
- clearskies/backends/__init__.py +67 -0
- clearskies/backends/api_backend.py +1194 -0
- clearskies/backends/backend.py +137 -0
- clearskies/backends/cursor_backend.py +339 -0
- clearskies/backends/graphql_backend.py +977 -0
- clearskies/backends/memory_backend.py +794 -0
- clearskies/backends/secrets_backend.py +100 -0
- clearskies/clients/__init__.py +5 -0
- clearskies/clients/graphql_client.py +182 -0
- clearskies/column.py +1221 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +306 -0
- clearskies/columns/belongs_to_id.py +478 -0
- clearskies/columns/belongs_to_model.py +145 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +274 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +125 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +92 -0
- clearskies/columns/created_by_authorization_data.py +114 -0
- clearskies/columns/created_by_header.py +103 -0
- clearskies/columns/created_by_ip.py +90 -0
- clearskies/columns/created_by_routing_data.py +102 -0
- clearskies/columns/created_by_user_agent.py +89 -0
- clearskies/columns/date.py +232 -0
- clearskies/columns/datetime.py +284 -0
- clearskies/columns/email.py +78 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +552 -0
- clearskies/columns/has_many_self.py +62 -0
- clearskies/columns/has_one.py +21 -0
- clearskies/columns/integer.py +158 -0
- clearskies/columns/json.py +126 -0
- clearskies/columns/many_to_many_ids.py +335 -0
- clearskies/columns/many_to_many_ids_with_data.py +281 -0
- clearskies/columns/many_to_many_models.py +163 -0
- clearskies/columns/many_to_many_pivots.py +132 -0
- clearskies/columns/phone.py +162 -0
- clearskies/columns/select.py +95 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +107 -0
- clearskies/columns/uuid.py +83 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +170 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +15 -0
- clearskies/configs/any_dict.py +24 -0
- clearskies/configs/any_dict_or_callable.py +25 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +18 -0
- clearskies/configs/boolean_or_callable.py +20 -0
- clearskies/configs/callable_config.py +20 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +26 -0
- clearskies/configs/datetime.py +20 -0
- clearskies/configs/datetime_or_callable.py +21 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +17 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +18 -0
- clearskies/configs/float_or_callable.py +20 -0
- clearskies/configs/headers.py +28 -0
- clearskies/configs/integer.py +18 -0
- clearskies/configs/integer_or_callable.py +20 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +32 -0
- clearskies/configs/list_any_dict_or_callable.py +33 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +67 -0
- clearskies/configs/model_columns.py +58 -0
- clearskies/configs/model_destination_name.py +26 -0
- clearskies/configs/model_to_id_column.py +45 -0
- clearskies/configs/readable_model_column.py +11 -0
- clearskies/configs/readable_model_columns.py +11 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +11 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +28 -0
- clearskies/configs/select_list.py +49 -0
- clearskies/configs/string.py +31 -0
- clearskies/configs/string_dict.py +34 -0
- clearskies/configs/string_list.py +47 -0
- clearskies/configs/string_list_or_callable.py +48 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +20 -0
- clearskies/configs/timezone.py +20 -0
- clearskies/configs/url.py +25 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +11 -0
- clearskies/configs/writeable_model_columns.py +11 -0
- clearskies/configurable.py +78 -0
- clearskies/contexts/__init__.py +11 -0
- clearskies/contexts/cli.py +130 -0
- clearskies/contexts/context.py +99 -0
- clearskies/contexts/wsgi.py +79 -0
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +10 -0
- clearskies/cursors/cursor.py +161 -0
- clearskies/cursors/from_environment/__init__.py +5 -0
- clearskies/cursors/from_environment/mysql.py +51 -0
- clearskies/cursors/from_environment/postgresql.py +49 -0
- clearskies/cursors/from_environment/sqlite.py +35 -0
- clearskies/cursors/mysql.py +61 -0
- clearskies/cursors/postgresql.py +61 -0
- clearskies/cursors/sqlite.py +62 -0
- clearskies/decorators.py +33 -0
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +15 -0
- clearskies/di/additional_config.py +130 -0
- clearskies/di/additional_config_auto_import.py +17 -0
- clearskies/di/di.py +948 -0
- clearskies/di/inject/__init__.py +25 -0
- clearskies/di/inject/akeyless_sdk.py +16 -0
- clearskies/di/inject/by_class.py +24 -0
- clearskies/di/inject/by_name.py +22 -0
- clearskies/di/inject/di.py +16 -0
- clearskies/di/inject/environment.py +15 -0
- clearskies/di/inject/input_output.py +19 -0
- clearskies/di/inject/logger.py +16 -0
- clearskies/di/inject/now.py +16 -0
- clearskies/di/inject/requests.py +16 -0
- clearskies/di/inject/secrets.py +15 -0
- clearskies/di/inject/utcnow.py +16 -0
- clearskies/di/inject/uuid.py +16 -0
- clearskies/di/injectable.py +32 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +219 -0
- clearskies/endpoint.py +1303 -0
- clearskies/endpoint_group.py +333 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +519 -0
- clearskies/endpoints/callable.py +382 -0
- clearskies/endpoints/create.py +201 -0
- clearskies/endpoints/delete.py +133 -0
- clearskies/endpoints/get.py +267 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +567 -0
- clearskies/endpoints/restful_api.py +417 -0
- clearskies/endpoints/schema.py +185 -0
- clearskies/endpoints/simple_search.py +279 -0
- clearskies/endpoints/update.py +188 -0
- clearskies/environment.py +106 -0
- clearskies/exceptions/__init__.py +19 -0
- clearskies/exceptions/authentication.py +2 -0
- clearskies/exceptions/authorization.py +2 -0
- clearskies/exceptions/client_error.py +2 -0
- clearskies/exceptions/input_errors.py +4 -0
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/exceptions/not_found.py +2 -0
- clearskies/functional/__init__.py +7 -0
- clearskies/functional/json.py +47 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +112 -0
- clearskies/functional/validations.py +76 -0
- clearskies/input_outputs/__init__.py +13 -0
- clearskies/input_outputs/cli.py +157 -0
- clearskies/input_outputs/exceptions/__init__.py +7 -0
- clearskies/input_outputs/exceptions/cli_input_error.py +2 -0
- clearskies/input_outputs/exceptions/cli_not_found.py +2 -0
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +116 -0
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/py.typed +0 -0
- clearskies/input_outputs/wsgi.py +80 -0
- clearskies/loggable.py +19 -0
- clearskies/model.py +2039 -0
- clearskies/py.typed +0 -0
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +195 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +7 -0
- clearskies/secrets/additional_configs/__init__.py +32 -0
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +61 -0
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +160 -0
- clearskies/secrets/akeyless.py +507 -0
- clearskies/secrets/exceptions/__init__.py +7 -0
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +39 -0
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +11 -0
- clearskies/security_headers/cache_control.py +68 -0
- clearskies/security_headers/cors.py +51 -0
- clearskies/security_headers/csp.py +95 -0
- clearskies/security_headers/hsts.py +23 -0
- clearskies/security_headers/x_content_type_options.py +0 -0
- clearskies/security_headers/x_frame_options.py +0 -0
- clearskies/typing.py +11 -0
- clearskies/validator.py +36 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +61 -0
- clearskies/validators/before_column.py +15 -0
- clearskies/validators/in_the_future.py +29 -0
- clearskies/validators/in_the_future_at_least.py +13 -0
- clearskies/validators/in_the_future_at_most.py +12 -0
- clearskies/validators/in_the_past.py +29 -0
- clearskies/validators/in_the_past_at_least.py +12 -0
- clearskies/validators/in_the_past_at_most.py +12 -0
- clearskies/validators/maximum_length.py +25 -0
- clearskies/validators/maximum_value.py +28 -0
- clearskies/validators/minimum_length.py +25 -0
- clearskies/validators/minimum_value.py +28 -0
- clearskies/validators/required.py +32 -0
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from .mysql_connection_dynamic_producer import MySQLConnectionDynamicProducer
|
|
2
|
+
from .mysql_connection_dynamic_producer_via_ssh_cert_bastion import MySQLConnectionDynamicProducerViaSSHCertBastion
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def mysql_connection_dynamic_producer(producer_name=None, database_host=None, database_name=None):
|
|
6
|
+
return MySQLConnectionDynamicProducer(
|
|
7
|
+
producer_name=producer_name,
|
|
8
|
+
database_host=database_host,
|
|
9
|
+
database_name=database_name,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def mysql_connection_dynamic_producer_via_ssh_cert_bastion(
|
|
14
|
+
producer_name=None,
|
|
15
|
+
bastion_host=None,
|
|
16
|
+
bastion_username=None,
|
|
17
|
+
public_key_file_path=None,
|
|
18
|
+
cert_issuer_name=None,
|
|
19
|
+
database_host=None,
|
|
20
|
+
database_name=None,
|
|
21
|
+
local_proxy_port=None,
|
|
22
|
+
):
|
|
23
|
+
return MySQLConnectionDynamicProducerViaSSHCertBastion(
|
|
24
|
+
producer_name=producer_name,
|
|
25
|
+
bastion_host=bastion_host,
|
|
26
|
+
bastion_username=bastion_username,
|
|
27
|
+
cert_issuer_name=cert_issuer_name,
|
|
28
|
+
public_key_file_path=public_key_file_path,
|
|
29
|
+
database_host=database_host,
|
|
30
|
+
database_name=database_name,
|
|
31
|
+
local_proxy_port=local_proxy_port,
|
|
32
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import clearskies.di
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MySQLConnectionDynamicProducer(clearskies.di.additional_config.AdditionalConfig):
|
|
5
|
+
_producer_name = None
|
|
6
|
+
_database_host = None
|
|
7
|
+
_database_name = None
|
|
8
|
+
|
|
9
|
+
def __init__(self, producer_name=None, database_host=None, database_name=None):
|
|
10
|
+
self._producer_name = producer_name
|
|
11
|
+
self._database_host = database_host
|
|
12
|
+
self._database_name = database_name
|
|
13
|
+
|
|
14
|
+
def provide_connection_details(self, environment, secrets):
|
|
15
|
+
if not secrets:
|
|
16
|
+
raise ValueError(
|
|
17
|
+
"I was asked to connect to a database via an AKeyless dynamic producer, \
|
|
18
|
+
but AKeyless itself wasn't configured. \
|
|
19
|
+
Try setting the AKeyless auth method via clearskies.secrets.akeyless_[jwt|saml|aws_iam]_auth()"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
producer_name = (
|
|
23
|
+
self._producer_name
|
|
24
|
+
if self._producer_name is not None
|
|
25
|
+
else environment.get("akeyless_mysql_dynamic_producer", silent=True)
|
|
26
|
+
)
|
|
27
|
+
if not producer_name:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"I was asked to connect to a database via an AKeyless dynamic producer, \
|
|
30
|
+
but I wasn't told the path to the dynamic producer. \
|
|
31
|
+
This can be set in an environment variable named 'akeyless_mysql_dynamic_producer'\
|
|
32
|
+
or it can be set in the configuration via the producer_name kwarg."
|
|
33
|
+
)
|
|
34
|
+
database_name = (
|
|
35
|
+
self._database_name if self._database_name is not None else environment.get("db_database", silent=True)
|
|
36
|
+
)
|
|
37
|
+
if not database_name:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"I was asked to connect to a database via an AKeyless dynamic producer, \
|
|
40
|
+
but I wasn't told the name of the database. \
|
|
41
|
+
This can be set in an environment variable named 'db_database' \
|
|
42
|
+
or it can be set in the configuration via the database_name kwarg."
|
|
43
|
+
)
|
|
44
|
+
database_host = (
|
|
45
|
+
self._database_host if self._database_host is not None else environment.get("db_host", silent=True)
|
|
46
|
+
)
|
|
47
|
+
if not database_host:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"I was asked to connect to a database via an AKeyless dynamic producer, \
|
|
50
|
+
but I wasn't told the host name of the database. \
|
|
51
|
+
This can be set in an environment variable named 'db_host' \
|
|
52
|
+
or it can be set in the configuration via the database_host kwarg."
|
|
53
|
+
)
|
|
54
|
+
credentials = secrets.get_dynamic_secret(producer_name)
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"username": credentials["user"],
|
|
58
|
+
"password": credentials["password"],
|
|
59
|
+
"host": database_host,
|
|
60
|
+
"database": database_name,
|
|
61
|
+
}
|
clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import clearskies.di
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MySQLConnectionDynamicProducerViaSSHCertBastion(clearskies.di.additional_config.AdditionalConfig):
|
|
11
|
+
_config = None
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
producer_name=None,
|
|
16
|
+
bastion_host=None,
|
|
17
|
+
bastion_username=None,
|
|
18
|
+
public_key_file_path=None,
|
|
19
|
+
local_proxy_port=None,
|
|
20
|
+
cert_issuer_name=None,
|
|
21
|
+
database_host=None,
|
|
22
|
+
database_name=None,
|
|
23
|
+
):
|
|
24
|
+
# not using kwargs because I want the argument list to be explicit
|
|
25
|
+
self.config = {
|
|
26
|
+
"producer_name": producer_name,
|
|
27
|
+
"bastion_host": bastion_host,
|
|
28
|
+
"bastion_username": bastion_username,
|
|
29
|
+
"public_key_file_path": public_key_file_path,
|
|
30
|
+
"local_proxy_port": local_proxy_port,
|
|
31
|
+
"cert_issuer_name": cert_issuer_name,
|
|
32
|
+
"database_host": database_host,
|
|
33
|
+
"database_name": database_name,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def provide_connection_details(self, environment, secrets):
|
|
37
|
+
if not secrets:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"I was asked to connect to a database via an AKeyless dynamic producer but AKeyless itself wasn't configured. Try setting the AKeyless auth method via clearskies.secrets.akeyless_[jwt|saml|aws_iam]_auth()"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
home = str(Path.home())
|
|
43
|
+
default_public_key_file_path = f"{home}/.ssh/id_rsa.pub"
|
|
44
|
+
|
|
45
|
+
producer_name = self._fetch_config(environment, "producer_name", "akeyless_mysql_dynamic_producer")
|
|
46
|
+
bastion_host = self._get_bastion_host(environment)
|
|
47
|
+
bastion_username = self._fetch_config(environment, "bastion_username", "akeyless_mysql_bastion_username")
|
|
48
|
+
public_key_file_path = self._fetch_config(
|
|
49
|
+
environment,
|
|
50
|
+
"public_key_file_path",
|
|
51
|
+
"akeyless_mysql_bastion_public_key_file_path",
|
|
52
|
+
default=default_public_key_file_path,
|
|
53
|
+
)
|
|
54
|
+
cert_issuer_name = self._fetch_config(
|
|
55
|
+
environment, "cert_issuer_name", "akeyless_mysql_bastion_cert_issuer_name"
|
|
56
|
+
)
|
|
57
|
+
local_proxy_port = self._fetch_config(
|
|
58
|
+
environment, "local_proxy_port", "akeyless_mysql_bastion_local_proxy_port", default=8888
|
|
59
|
+
)
|
|
60
|
+
database_host = self._fetch_config(environment, "database_host", "db_host")
|
|
61
|
+
database_name = self._fetch_config(environment, "database_name", "db_database")
|
|
62
|
+
|
|
63
|
+
# Create the SSH tunnel (yeah, it's obnoxious)
|
|
64
|
+
self._create_tunnel(
|
|
65
|
+
secrets,
|
|
66
|
+
bastion_host,
|
|
67
|
+
bastion_username,
|
|
68
|
+
local_proxy_port,
|
|
69
|
+
cert_issuer_name,
|
|
70
|
+
public_key_file_path,
|
|
71
|
+
database_host,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# and now we can fetch credentials
|
|
75
|
+
credentials = secrets.get_dynamic_secret(producer_name)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"username": credentials["user"],
|
|
79
|
+
"password": credentials["password"],
|
|
80
|
+
"host": "127.0.0.1",
|
|
81
|
+
"database": database_name,
|
|
82
|
+
"port": local_proxy_port,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def _get_bastion_host(self, environment):
|
|
86
|
+
return self._fetch_config(environment, "bastion_host", "akeyless_mysql_bastion_host")
|
|
87
|
+
|
|
88
|
+
def _fetch_config(self, environment, config_key_name, environment_key_name, default=None):
|
|
89
|
+
if self.config[config_key_name]:
|
|
90
|
+
return self.config[config_key_name]
|
|
91
|
+
from_environment = environment.get(environment_key_name, silent=True)
|
|
92
|
+
if from_environment:
|
|
93
|
+
return from_environment
|
|
94
|
+
if default is not None:
|
|
95
|
+
return default
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"I was asked to connect to a database via an AKeyless dynamic producer through an SSH bastion"
|
|
98
|
+
"with certificate auth, but I wasn't given a required configuration value: '{config_key_name}'."
|
|
99
|
+
"This can be set in the call to "
|
|
100
|
+
"`clearskies.backends.akeyless.mysql_connection_dynamic_producer_via_ssh_cert_bastion()` by providing the "
|
|
101
|
+
"'{config_key_name}' argument, or by setting an environment variable named '{environment_key_name}'."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def _create_tunnel(
|
|
105
|
+
self,
|
|
106
|
+
secrets,
|
|
107
|
+
bastion_host,
|
|
108
|
+
bastion_username,
|
|
109
|
+
local_proxy_port,
|
|
110
|
+
cert_issuer_name,
|
|
111
|
+
public_key_file_path,
|
|
112
|
+
database_host,
|
|
113
|
+
):
|
|
114
|
+
# first see if the socket is already open, since we don't close it.
|
|
115
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
116
|
+
result = sock.connect_ex(("127.0.0.1", local_proxy_port))
|
|
117
|
+
if result == 0:
|
|
118
|
+
sock.close()
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
if not os.path.isfile(public_key_file_path):
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"I was asked to connect to AKeyless SSH with the public key file in '{public_key_file_path}',"
|
|
124
|
+
"but this file does not exist"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
ssh_certificate = secrets.get_ssh_certificate(cert_issuer_name, bastion_username, public_key_file_path)
|
|
128
|
+
|
|
129
|
+
# We need to write the certificate out to the standard location that SSH expects it so that SSH can find it.
|
|
130
|
+
# I haven't found a good library for doing this in Python, so I'm relying on the ssh command
|
|
131
|
+
home = str(Path.home())
|
|
132
|
+
with open(f"{home}/.ssh/id_rsa-cert.pub", "w") as fp:
|
|
133
|
+
fp.write(ssh_certificate)
|
|
134
|
+
|
|
135
|
+
# and now we can do this thing.
|
|
136
|
+
tunnel_command = [
|
|
137
|
+
"ssh",
|
|
138
|
+
"-o",
|
|
139
|
+
"ConnectTimeout=2",
|
|
140
|
+
"-N",
|
|
141
|
+
"-L",
|
|
142
|
+
f"{local_proxy_port}:{database_host}:3306",
|
|
143
|
+
"-p",
|
|
144
|
+
"22",
|
|
145
|
+
f"{bastion_username}@{bastion_host}",
|
|
146
|
+
]
|
|
147
|
+
subprocess.Popen(tunnel_command)
|
|
148
|
+
connected = False
|
|
149
|
+
attempts = 0
|
|
150
|
+
while not connected and attempts < 6:
|
|
151
|
+
attempts += 1
|
|
152
|
+
time.sleep(0.5)
|
|
153
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
154
|
+
result = sock.connect_ex(("127.0.0.1", local_proxy_port))
|
|
155
|
+
if result == 0:
|
|
156
|
+
connected = True
|
|
157
|
+
if not connected:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
"Failed to open SSH tunnel. The following command was used: \n" + " ".join(tunnel_command)
|
|
160
|
+
)
|