clear-skies-aws 2.0.1__py3-none-any.whl → 2.0.3__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.
- {clear_skies_aws-2.0.1.dist-info → clear_skies_aws-2.0.3.dist-info}/METADATA +2 -2
- clear_skies_aws-2.0.3.dist-info/RECORD +63 -0
- {clear_skies_aws-2.0.1.dist-info → clear_skies_aws-2.0.3.dist-info}/WHEEL +1 -1
- clearskies_aws/__init__.py +27 -0
- clearskies_aws/actions/__init__.py +15 -0
- clearskies_aws/actions/action_aws.py +135 -0
- clearskies_aws/actions/assume_role.py +115 -0
- clearskies_aws/actions/ses.py +203 -0
- clearskies_aws/actions/sns.py +61 -0
- clearskies_aws/actions/sqs.py +81 -0
- clearskies_aws/actions/step_function.py +73 -0
- clearskies_aws/backends/__init__.py +19 -0
- clearskies_aws/backends/backend.py +106 -0
- clearskies_aws/backends/dynamo_db_backend.py +609 -0
- clearskies_aws/backends/dynamo_db_condition_parser.py +325 -0
- clearskies_aws/backends/dynamo_db_parti_ql_backend.py +965 -0
- clearskies_aws/backends/sqs_backend.py +61 -0
- clearskies_aws/configs/__init__.py +0 -0
- clearskies_aws/contexts/__init__.py +23 -0
- clearskies_aws/contexts/cli_web_socket_mock.py +20 -0
- clearskies_aws/contexts/lambda_alb.py +81 -0
- clearskies_aws/contexts/lambda_api_gateway.py +81 -0
- clearskies_aws/contexts/lambda_api_gateway_web_socket.py +79 -0
- clearskies_aws/contexts/lambda_invoke.py +138 -0
- clearskies_aws/contexts/lambda_sns.py +124 -0
- clearskies_aws/contexts/lambda_sqs_standard.py +139 -0
- clearskies_aws/di/__init__.py +6 -0
- clearskies_aws/di/aws_additional_config_auto_import.py +37 -0
- clearskies_aws/di/inject/__init__.py +6 -0
- clearskies_aws/di/inject/boto3.py +15 -0
- clearskies_aws/di/inject/boto3_session.py +13 -0
- clearskies_aws/di/inject/parameter_store.py +15 -0
- clearskies_aws/endpoints/__init__.py +1 -0
- clearskies_aws/endpoints/secrets_manager_rotation.py +194 -0
- clearskies_aws/endpoints/simple_body_routing.py +41 -0
- clearskies_aws/input_outputs/__init__.py +21 -0
- clearskies_aws/input_outputs/cli_web_socket_mock.py +20 -0
- clearskies_aws/input_outputs/lambda_alb.py +53 -0
- clearskies_aws/input_outputs/lambda_api_gateway.py +123 -0
- clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +73 -0
- clearskies_aws/input_outputs/lambda_input_output.py +89 -0
- clearskies_aws/input_outputs/lambda_invoke.py +88 -0
- clearskies_aws/input_outputs/lambda_sns.py +88 -0
- clearskies_aws/input_outputs/lambda_sqs_standard.py +86 -0
- clearskies_aws/mocks/__init__.py +1 -0
- clearskies_aws/mocks/actions/__init__.py +6 -0
- clearskies_aws/mocks/actions/ses.py +34 -0
- clearskies_aws/mocks/actions/sns.py +29 -0
- clearskies_aws/mocks/actions/sqs.py +29 -0
- clearskies_aws/mocks/actions/step_function.py +32 -0
- clearskies_aws/models/__init__.py +1 -0
- clearskies_aws/models/web_socket_connection_model.py +182 -0
- clearskies_aws/secrets/__init__.py +13 -0
- clearskies_aws/secrets/additional_configs/__init__.py +62 -0
- clearskies_aws/secrets/additional_configs/iam_db_auth.py +39 -0
- clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +96 -0
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +80 -0
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +162 -0
- clearskies_aws/secrets/akeyless_with_ssm_cache.py +60 -0
- clearskies_aws/secrets/parameter_store.py +52 -0
- clearskies_aws/secrets/secrets.py +16 -0
- clearskies_aws/secrets/secrets_manager.py +96 -0
- clear_skies_aws-2.0.1.dist-info/RECORD +0 -4
- {clear_skies_aws-2.0.1.dist-info → clear_skies_aws-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from clearskies.secrets.akeyless import Akeyless
|
|
7
|
+
from types_boto3_ssm import SSMClient
|
|
8
|
+
|
|
9
|
+
from clearskies_aws.secrets import parameter_store
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AkeylessWithSsmCache(parameter_store.ParameterStore, Akeyless):
|
|
13
|
+
def get(self, path: str, refresh: bool = False) -> str | None: # type: ignore[override]
|
|
14
|
+
# AWS SSM parameter paths only allow a-z, A-Z, 0-9, -, _, ., /, @, and :
|
|
15
|
+
# Replace any disallowed characters with hyphens
|
|
16
|
+
ssm_name = re.sub(r"[^a-zA-Z0-9\-_\./@:]", "-", path)
|
|
17
|
+
# if we're not forcing a refresh, then see if it is in paramater store
|
|
18
|
+
if not refresh:
|
|
19
|
+
missing = False
|
|
20
|
+
try:
|
|
21
|
+
response = self.ssm.get_parameter(Name=ssm_name, WithDecryption=True)
|
|
22
|
+
except self.ssm.exceptions.ParameterNotFound:
|
|
23
|
+
missing = True
|
|
24
|
+
if not missing:
|
|
25
|
+
value = response["Parameter"].get("Value", "")
|
|
26
|
+
if value:
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
# otherwise get it out of Akeyless
|
|
30
|
+
value = str(super().get(path))
|
|
31
|
+
|
|
32
|
+
# and make sure and store the new value in parameter store
|
|
33
|
+
if value:
|
|
34
|
+
self.ssm.put_parameter(
|
|
35
|
+
Name=ssm_name,
|
|
36
|
+
Value=value,
|
|
37
|
+
Type="SecureString",
|
|
38
|
+
Overwrite=True,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
def update(self, path: str, value: Any) -> bool: # type: ignore[override]
|
|
44
|
+
res = self._api.update_secret_val(
|
|
45
|
+
self.akeyless.UpdateSecretVal(name=path, value=str(value), token=self._get_token())
|
|
46
|
+
)
|
|
47
|
+
self.ssm.put_parameter(
|
|
48
|
+
Name=re.sub(r"[^a-zA-Z0-9\-_\./@:]", "-", path),
|
|
49
|
+
Value=value,
|
|
50
|
+
Type="SecureString",
|
|
51
|
+
Overwrite=True,
|
|
52
|
+
)
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
def upsert(self, path: str, value: Any) -> bool: # type: ignore[override]
|
|
56
|
+
try:
|
|
57
|
+
self.update(path, value)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
self.create(path, value)
|
|
60
|
+
return True
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
from clearskies.exceptions.not_found import NotFound
|
|
5
|
+
from types_boto3_ssm import SSMClient
|
|
6
|
+
|
|
7
|
+
from clearskies_aws.secrets import secrets
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ParameterStore(secrets.Secrets):
|
|
11
|
+
ssm: SSMClient
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.ssm = self.boto3.client("ssm", region_name=self.environment.get("AWS_REGION"))
|
|
16
|
+
|
|
17
|
+
def create(self, path: str, value: str) -> bool:
|
|
18
|
+
return self.update(path, value)
|
|
19
|
+
|
|
20
|
+
def get(self, path: str, silent_if_not_found: bool = False) -> str | None: # type: ignore[override]
|
|
21
|
+
try:
|
|
22
|
+
result = self.ssm.get_parameter(Name=path, WithDecryption=True)
|
|
23
|
+
except ClientError as e:
|
|
24
|
+
error = e.response.get("Error", {})
|
|
25
|
+
if error.get("Code") == "ResourceNotFoundException":
|
|
26
|
+
if silent_if_not_found:
|
|
27
|
+
return None
|
|
28
|
+
raise NotFound(f"Could not find secret '{path}' in parameter store")
|
|
29
|
+
raise e
|
|
30
|
+
return result["Parameter"].get("Value", "")
|
|
31
|
+
|
|
32
|
+
def list_secrets(self, path: str) -> list[str]:
|
|
33
|
+
response = self.ssm.get_parameters_by_path(Path=path, Recursive=False)
|
|
34
|
+
return [parameter["Name"] for parameter in response["Parameters"] if "Name" in parameter]
|
|
35
|
+
|
|
36
|
+
def update(self, path: str, value: str) -> bool: # type: ignore[override]
|
|
37
|
+
response = self.ssm.put_parameter(
|
|
38
|
+
Name=path,
|
|
39
|
+
Value=value,
|
|
40
|
+
Type="String",
|
|
41
|
+
Overwrite=True,
|
|
42
|
+
)
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
def upsert(self, path: str, value: str) -> bool: # type: ignore[override]
|
|
46
|
+
return self.update(path, value)
|
|
47
|
+
|
|
48
|
+
def list_sub_folders(
|
|
49
|
+
self,
|
|
50
|
+
path: str,
|
|
51
|
+
) -> list[str]: # type: ignore[override]
|
|
52
|
+
raise NotImplementedError("Parameter store doesn't support list_sub_folders.")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from clearskies.di.inject import Di, Environment
|
|
4
|
+
from clearskies.secrets import Secrets as BaseSecrets
|
|
5
|
+
|
|
6
|
+
from clearskies_aws.di import inject
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Secrets(BaseSecrets):
|
|
10
|
+
boto3 = inject.Boto3()
|
|
11
|
+
environment = Environment()
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
super().__init__()
|
|
15
|
+
if not self.environment.get("AWS_REGION", True):
|
|
16
|
+
raise ValueError("To use secrets manager you must use set the 'AWS_REGION' environment variable")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
from clearskies.exceptions.not_found import NotFound
|
|
7
|
+
from types_boto3_secretsmanager import SecretsManagerClient
|
|
8
|
+
from types_boto3_secretsmanager.type_defs import SecretListEntryTypeDef
|
|
9
|
+
|
|
10
|
+
from clearskies_aws.secrets import secrets
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SecretsManager(secrets.Secrets):
|
|
14
|
+
secrets_manager: SecretsManagerClient
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.secrets_manager = self.boto3.client("secretsmanager", region_name=self.environment.get("AWS_REGION"))
|
|
19
|
+
|
|
20
|
+
def create(self, secret_id: str, value: Any, kms_key_id: str | None = None) -> bool:
|
|
21
|
+
calling_parameters = {
|
|
22
|
+
"SecretId": secret_id,
|
|
23
|
+
"SecretString": value,
|
|
24
|
+
"KmsKeyId": kms_key_id,
|
|
25
|
+
}
|
|
26
|
+
calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
|
|
27
|
+
result = self.secrets_manager.create_secret(**calling_parameters)
|
|
28
|
+
return bool(result.get("ARN"))
|
|
29
|
+
|
|
30
|
+
def get( # type: ignore[override]
|
|
31
|
+
self,
|
|
32
|
+
secret_id: str,
|
|
33
|
+
version_id: str | None = None,
|
|
34
|
+
version_stage: str | None = None,
|
|
35
|
+
silent_if_not_found: bool = False,
|
|
36
|
+
) -> str | bytes | None:
|
|
37
|
+
calling_parameters = {"SecretId": secret_id}
|
|
38
|
+
|
|
39
|
+
# Only add optional parameters if they are not None
|
|
40
|
+
if version_id:
|
|
41
|
+
calling_parameters["VersionId"] = version_id
|
|
42
|
+
if version_stage:
|
|
43
|
+
calling_parameters["VersionStage"] = version_stage
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
result = self.secrets_manager.get_secret_value(**calling_parameters)
|
|
47
|
+
except ClientError as e:
|
|
48
|
+
error = e.response.get("Error", {})
|
|
49
|
+
if error.get("Code") == "ResourceNotFoundException":
|
|
50
|
+
if silent_if_not_found:
|
|
51
|
+
return None
|
|
52
|
+
raise NotFound(
|
|
53
|
+
f"Could not find secret '{secret_id}' with version '{version_id}' and stage '{version_stage}'"
|
|
54
|
+
)
|
|
55
|
+
raise e
|
|
56
|
+
if result.get("SecretString"):
|
|
57
|
+
return result.get("SecretString")
|
|
58
|
+
return result.get("SecretBinary")
|
|
59
|
+
|
|
60
|
+
def list_secrets(self, path: str) -> list[SecretListEntryTypeDef]: # type: ignore[override]
|
|
61
|
+
results = self.secrets_manager.list_secrets(
|
|
62
|
+
Filters=[
|
|
63
|
+
{
|
|
64
|
+
"Key": "name",
|
|
65
|
+
"Values": [path],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
return results["SecretList"]
|
|
70
|
+
|
|
71
|
+
def update(self, secret_id: str, value: str, kms_key_id: str | None = None) -> bool: # type: ignore[override]
|
|
72
|
+
calling_parameters = {
|
|
73
|
+
"SecretId": secret_id,
|
|
74
|
+
"SecretString": value,
|
|
75
|
+
}
|
|
76
|
+
if kms_key_id:
|
|
77
|
+
# If no KMS key is provided, we should not include it in the parameters
|
|
78
|
+
calling_parameters["KmsKeyId"] = kms_key_id
|
|
79
|
+
|
|
80
|
+
result = self.secrets_manager.update_secret(**calling_parameters)
|
|
81
|
+
return bool(result.get("ARN"))
|
|
82
|
+
|
|
83
|
+
def upsert(self, secret_id: str, value: str, kms_key_id: str | None = None) -> bool: # type: ignore[override]
|
|
84
|
+
calling_parameters = {
|
|
85
|
+
"SecretId": secret_id,
|
|
86
|
+
"SecretString": value,
|
|
87
|
+
}
|
|
88
|
+
if kms_key_id:
|
|
89
|
+
# If no KMS key is provided, we should not include it in the parameters
|
|
90
|
+
calling_parameters["KmsKeyId"] = kms_key_id
|
|
91
|
+
|
|
92
|
+
result = self.secrets_manager.put_secret_value(**calling_parameters)
|
|
93
|
+
return bool(result.get("ARN"))
|
|
94
|
+
|
|
95
|
+
def list_sub_folders(self, path: str, value: str) -> list[str]: # type: ignore[override]
|
|
96
|
+
raise NotImplementedError("Secrets Manager doesn't support list_sub_folders.")
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
clear_skies_aws-2.0.1.dist-info/METADATA,sha256=RuKqjsvTbdZc56XVM2UD6IZnSySbVkWA5SaFkxpbps4,8972
|
|
2
|
-
clear_skies_aws-2.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
3
|
-
clear_skies_aws-2.0.1.dist-info/licenses/LICENSE,sha256=MkEX8JF8kZxdyBpTTcB0YTd-xZpWnHvbRlw-pQh8u58,1069
|
|
4
|
-
clear_skies_aws-2.0.1.dist-info/RECORD,,
|
|
File without changes
|