clear-skies-aws 1.10.2__py3-none-any.whl → 2.0.2__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.
Files changed (86) hide show
  1. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/METADATA +36 -35
  2. clear_skies_aws-2.0.2.dist-info/RECORD +63 -0
  3. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/WHEEL +1 -1
  4. clear_skies_aws-2.0.2.dist-info/licenses/LICENSE +21 -0
  5. clearskies_aws/__init__.py +15 -2
  6. clearskies_aws/actions/__init__.py +13 -106
  7. clearskies_aws/actions/action_aws.py +74 -57
  8. clearskies_aws/actions/assume_role.py +43 -30
  9. clearskies_aws/actions/ses.py +82 -73
  10. clearskies_aws/actions/sns.py +27 -30
  11. clearskies_aws/actions/sqs.py +32 -33
  12. clearskies_aws/actions/step_function.py +38 -31
  13. clearskies_aws/backends/__init__.py +11 -4
  14. clearskies_aws/backends/backend.py +106 -0
  15. clearskies_aws/backends/dynamo_db_backend.py +150 -155
  16. clearskies_aws/backends/dynamo_db_condition_parser.py +40 -80
  17. clearskies_aws/backends/dynamo_db_parti_ql_backend.py +179 -337
  18. clearskies_aws/backends/sqs_backend.py +32 -51
  19. clearskies_aws/configs/__init__.py +0 -0
  20. clearskies_aws/contexts/__init__.py +23 -10
  21. clearskies_aws/contexts/cli_web_socket_mock.py +19 -0
  22. clearskies_aws/contexts/lambda_alb.py +76 -0
  23. clearskies_aws/contexts/lambda_api_gateway.py +75 -28
  24. clearskies_aws/contexts/lambda_api_gateway_web_socket.py +56 -29
  25. clearskies_aws/contexts/lambda_invocation.py +15 -44
  26. clearskies_aws/contexts/lambda_sns.py +8 -33
  27. clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +14 -36
  28. clearskies_aws/di/__init__.py +6 -1
  29. clearskies_aws/di/aws_additional_config_auto_import.py +37 -0
  30. clearskies_aws/di/inject/__init__.py +6 -0
  31. clearskies_aws/di/inject/boto3.py +15 -0
  32. clearskies_aws/di/inject/boto3_session.py +13 -0
  33. clearskies_aws/di/inject/parameter_store.py +15 -0
  34. clearskies_aws/{handlers → endpoints}/secrets_manager_rotation.py +76 -55
  35. clearskies_aws/endpoints/simple_body_routing.py +41 -0
  36. clearskies_aws/input_outputs/__init__.py +21 -8
  37. clearskies_aws/input_outputs/{cli_websocket_mock.py → cli_web_socket_mock.py} +9 -3
  38. clearskies_aws/input_outputs/lambda_alb.py +53 -0
  39. clearskies_aws/input_outputs/lambda_api_gateway.py +106 -88
  40. clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +69 -6
  41. clearskies_aws/input_outputs/lambda_input_output.py +87 -0
  42. clearskies_aws/input_outputs/lambda_invocation.py +77 -26
  43. clearskies_aws/input_outputs/lambda_sns.py +66 -39
  44. clearskies_aws/input_outputs/lambda_sqs_standard.py +70 -40
  45. clearskies_aws/mocks/actions/ses.py +25 -19
  46. clearskies_aws/mocks/actions/sns.py +18 -12
  47. clearskies_aws/mocks/actions/sqs.py +18 -12
  48. clearskies_aws/mocks/actions/step_function.py +19 -13
  49. clearskies_aws/models/__init__.py +0 -0
  50. clearskies_aws/models/web_socket_connection_model.py +182 -0
  51. clearskies_aws/secrets/__init__.py +13 -7
  52. clearskies_aws/secrets/additional_configs/__init__.py +10 -2
  53. clearskies_aws/secrets/additional_configs/iam_db_auth.py +26 -16
  54. clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +43 -39
  55. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +30 -31
  56. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +70 -49
  57. clearskies_aws/secrets/akeyless_with_ssm_cache.py +32 -18
  58. clearskies_aws/secrets/parameter_store.py +34 -32
  59. clearskies_aws/secrets/secrets.py +16 -0
  60. clearskies_aws/secrets/secrets_manager.py +78 -57
  61. clear_skies_aws-1.10.2.dist-info/LICENSE +0 -7
  62. clear_skies_aws-1.10.2.dist-info/RECORD +0 -71
  63. clearskies_aws/actions/assume_role_test.py +0 -72
  64. clearskies_aws/actions/ses_test.py +0 -89
  65. clearskies_aws/actions/sns_test.py +0 -77
  66. clearskies_aws/actions/sqs_test.py +0 -127
  67. clearskies_aws/actions/step_function_test.py +0 -103
  68. clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
  69. clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
  70. clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
  71. clearskies_aws/backends/sqs_backend_test.py +0 -31
  72. clearskies_aws/contexts/cli.py +0 -19
  73. clearskies_aws/contexts/cli_websocket_mock.py +0 -33
  74. clearskies_aws/contexts/lambda_elb.py +0 -30
  75. clearskies_aws/contexts/lambda_http_gateway.py +0 -30
  76. clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
  77. clearskies_aws/contexts/wsgi.py +0 -19
  78. clearskies_aws/di/standard_dependencies.py +0 -60
  79. clearskies_aws/handlers/simple_body_routing.py +0 -39
  80. clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
  81. clearskies_aws/input_outputs/lambda_elb.py +0 -21
  82. clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
  83. clearskies_aws/secrets/parameter_store_test.py +0 -18
  84. clearskies_aws/secrets/secrets_manager_test.py +0 -18
  85. clearskies_aws/web_socket_connection_model.py +0 -43
  86. clearskies_aws/{handlers → endpoints}/__init__.py +1 -1
@@ -1,9 +1,16 @@
1
- from .mysql_connection_dynamic_producer_via_ssh_cert_bastion import MySQLConnectionDynamicProducerViaSSHCertBastion as Base
2
- from pathlib import Path
1
+ from __future__ import annotations
2
+
3
+ import os
3
4
  import socket
4
5
  import subprocess
5
- import os
6
6
  import time
7
+ from pathlib import Path
8
+
9
+ from .mysql_connection_dynamic_producer_via_ssh_cert_bastion import (
10
+ MySQLConnectionDynamicProducerViaSSHCertBastion as Base,
11
+ )
12
+
13
+
7
14
  class MySQLConnectionDynamicProducerViaSSMBastion(Base):
8
15
  _config = None
9
16
  _boto3 = None
@@ -18,19 +25,19 @@ class MySQLConnectionDynamicProducerViaSSMBastion(Base):
18
25
  public_key_file_path=None,
19
26
  local_proxy_port=None,
20
27
  database_host=None,
21
- database_name=None
28
+ database_name=None,
22
29
  ):
23
30
  # not using kwargs because I want the argument list to be explicit
24
31
  self.config = {
25
- 'producer_name': producer_name,
26
- 'bastion_instance_id': bastion_instance_id,
27
- 'bastion_region': bastion_region,
28
- 'bastion_name': bastion_name,
29
- 'bastion_username': bastion_username,
30
- 'public_key_file_path': public_key_file_path,
31
- 'local_proxy_port': local_proxy_port,
32
- 'database_host': database_host,
33
- 'database_name': database_name,
32
+ "producer_name": producer_name,
33
+ "bastion_instance_id": bastion_instance_id,
34
+ "bastion_region": bastion_region,
35
+ "bastion_name": bastion_name,
36
+ "bastion_username": bastion_username,
37
+ "public_key_file_path": public_key_file_path,
38
+ "local_proxy_port": local_proxy_port,
39
+ "database_host": database_host,
40
+ "database_name": database_name,
34
41
  }
35
42
 
36
43
  def provide_connection_details(self, environment, secrets, boto3):
@@ -40,91 +47,105 @@ class MySQLConnectionDynamicProducerViaSSMBastion(Base):
40
47
  "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()"
41
48
  )
42
49
 
43
- producer_name = self._fetch_config(environment, 'producer_name', 'akeyless_mysql_dynamic_producer')
44
- bastion_username = self._fetch_config(environment, 'bastion_username', 'mysql_bastion_username', default='ssm')
50
+ producer_name = self._fetch_config(environment, "producer_name", "akeyless_mysql_dynamic_producer")
51
+ bastion_username = self._fetch_config(environment, "bastion_username", "mysql_bastion_username", default="ssm")
45
52
  bastion_instance_id = self._get_bastion_instance_id(environment)
46
53
  public_key_file_path = self._fetch_config(
47
- environment, 'public_key_file_path', 'mysql_bastion_public_key_file_path'
54
+ environment, "public_key_file_path", "mysql_bastion_public_key_file_path"
48
55
  )
49
56
  local_proxy_port = self._fetch_config(
50
- environment, 'local_proxy_port', 'akeyless_mysql_bastion_local_proxy_port', default=8888
57
+ environment, "local_proxy_port", "akeyless_mysql_bastion_local_proxy_port", default=8888
51
58
  )
52
- database_host = self._fetch_config(environment, 'database_host', 'db_host')
53
- database_name = self._fetch_config(environment, 'database_name', 'db_database')
59
+ database_host = self._fetch_config(environment, "database_host", "db_host")
60
+ database_name = self._fetch_config(environment, "database_name", "db_database")
54
61
 
55
62
  # Create the SSH tunnel (yeah, it's obnoxious)
56
63
  self._create_tunnel(
57
- secrets, bastion_instance_id, bastion_username, bastion_region, public_key_file_path, local_proxy_port,
58
- database_host
64
+ secrets,
65
+ bastion_instance_id,
66
+ bastion_username,
67
+ bastion_region,
68
+ public_key_file_path,
69
+ local_proxy_port,
70
+ database_host,
59
71
  )
60
72
 
61
73
  # and now we can fetch credentials
62
74
  credentials = secrets.get_dynamic_secret(producer_name)
63
75
 
64
76
  return {
65
- 'username': credentials['user'],
66
- 'password': credentials['password'],
67
- 'host': '127.0.0.1',
68
- 'database': database_name,
69
- 'port': local_proxy_port,
77
+ "username": credentials["user"],
78
+ "password": credentials["password"],
79
+ "host": "127.0.0.1",
80
+ "database": database_name,
81
+ "port": local_proxy_port,
70
82
  }
71
83
 
72
84
  def _get_bastion_instance_id(self, environment):
73
85
  bastion_instance_id = self._fetch_config(
74
- environment, 'bastion_instance_id', 'mysql_bastion_instance_id', default=''
86
+ environment, "bastion_instance_id", "mysql_bastion_instance_id", default=""
75
87
  )
76
- bastion_name = self._fetch_config(environment, 'bastion_name', 'mysql_bastion_name', default='')
88
+ bastion_name = self._fetch_config(environment, "bastion_name", "mysql_bastion_name", default="")
77
89
  if bastion_instance_id:
78
90
  return bastion_instance_id
79
91
  if bastion_name:
80
- bastion_region = self._fetch_config(environment, 'bastion_region', 'mysql_bastion_region')
92
+ bastion_region = self._fetch_config(environment, "bastion_region", "mysql_bastion_region")
81
93
  return self._instance_id_from_name(bastion_name, bastion_region)
82
94
  raise ValueError(
83
95
  f"I was asked to connect to a database via an AKeyless dynamic producer through an SSH bastion with certificate auth, but I'm missing some configuration. I need either the bastion host or the name of the instance in AWS. These can be set in the call to `clearskies.backends.akeyless_aws.mysql_connection_dynamic_producer_via_ssh_cert_bastion()` by providing the 'bastion_host' or 'bastion_name' argument, or by setting an environment variable named 'akeyless_mysql_bastion_host' or 'akeyless_mysql_bastion_name'."
84
96
  )
85
97
 
86
98
  def _instance_id_from_name(self, bastion_name, bastion_region):
87
- ec2 = self._boto3.client('ec2', region_name=bastion_region)
99
+ ec2 = self._boto3.client("ec2", region_name=bastion_region)
88
100
  response = ec2.describe_instances(
89
101
  Filters=[
90
- {
91
- 'Name': 'tag:Name',
92
- 'Values': [bastion_name]
93
- },
94
- {
95
- 'Name': 'instance-state-name',
96
- 'Values': ['running']
97
- },
102
+ {"Name": "tag:Name", "Values": [bastion_name]},
103
+ {"Name": "instance-state-name", "Values": ["running"]},
98
104
  ],
99
105
  )
100
- if not response.get('Reservations'):
106
+ if not response.get("Reservations"):
101
107
  raise ValueError(
102
108
  f"Could not find a running instance with the designated bastion name, '{bastion_name}' in region '{bastion_region}'"
103
109
  )
104
- if not response.get('Reservations')[0].get('Instances'):
110
+ if not response.get("Reservations")[0].get("Instances"):
105
111
  raise ValueError(
106
112
  f"Could not find a running instance with the designated bastion name, '{bastion_name}' in region '{bastion_region}'"
107
113
  )
108
- return response.get('Reservations')[0].get('Instances')[0]['InstanceId']
114
+ return response.get("Reservations")[0].get("Instances")[0]["InstanceId"]
109
115
 
110
116
  def _create_tunnel(
111
- self, secrets, bastion_instance_id, bastion_username, bastion_region, public_key_file_path, local_proxy_port,
112
- database_host
117
+ self,
118
+ secrets,
119
+ bastion_instance_id,
120
+ bastion_username,
121
+ bastion_region,
122
+ public_key_file_path,
123
+ local_proxy_port,
124
+ database_host,
113
125
  ):
114
126
  # first see if the socket is already open, since we don't close it.
115
127
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
116
- result = sock.connect_ex(('127.0.0.1', local_proxy_port))
128
+ result = sock.connect_ex(("127.0.0.1", local_proxy_port))
117
129
  if result == 0:
118
130
  sock.close()
119
131
  return
120
132
 
121
133
  # and now we can do this thing.
122
134
  tunnel_command = [
123
- 'ssh', '-i', public_key_file_path, '-o', 'ConnectTimeout=2', '-N', '-L',
124
- f'{local_proxy_port}:{database_host}:3306', '-p', '22', f'{bastion_username}@{bastion_instance_id}'
135
+ "ssh",
136
+ "-i",
137
+ public_key_file_path,
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_instance_id}",
125
146
  ]
126
147
  my_env = os.environ.copy()
127
- my_env['AWS_DEFAULT_REGION'] = bastion_region
148
+ my_env["AWS_DEFAULT_REGION"] = bastion_region
128
149
  subprocess.Popen(tunnel_command)
129
150
  connected = False
130
151
  attempts = 0
@@ -132,10 +153,10 @@ class MySQLConnectionDynamicProducerViaSSMBastion(Base):
132
153
  attempts += 1
133
154
  time.sleep(0.5)
134
155
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
135
- result = sock.connect_ex(('127.0.0.1', local_proxy_port))
156
+ result = sock.connect_ex(("127.0.0.1", local_proxy_port))
136
157
  if result == 0:
137
158
  connected = True
138
159
  if not connected:
139
160
  raise ValueError(
140
- 'Failed to open SSH tunnel. The following command was used: \n' + ' '.join(tunnel_command)
161
+ "Failed to open SSH tunnel. The following command was used: \n" + " ".join(tunnel_command)
141
162
  )
@@ -1,21 +1,16 @@
1
- import re
1
+ from __future__ import annotations
2
2
 
3
- from clearskies.secrets import AKeyless
3
+ import re
4
+ from typing import Any
4
5
 
6
+ from clearskies.secrets.akeyless import Akeyless
7
+ from types_boto3_ssm import SSMClient
5
8
 
6
- class AkeylessWithSsmCache(AKeyless):
7
- _boto3 = None
9
+ from clearskies_aws.secrets import parameter_store
8
10
 
9
- def __init__(self, requests, environment, boto3):
10
- super().__init__(requests, environment)
11
- self._boto3 = boto3
12
- if not self._environment.get("AWS_REGION", True):
13
- raise ValueError(
14
- "To use parameter store you must use set the 'AWS_REGION' environment variable"
15
- )
16
11
 
17
- def get(self, path, refresh=False):
18
- ssm = self._boto3.client("ssm", region_name="us-east-1")
12
+ class AkeylessWithSsmCache(parameter_store.ParameterStore, Akeyless):
13
+ def get(self, path: str, refresh: bool = False) -> str | None: # type: ignore[override]
19
14
  # AWS SSM parameter paths only allow a-z, A-Z, 0-9, -, _, ., /, @, and :
20
15
  # Replace any disallowed characters with hyphens
21
16
  ssm_name = re.sub(r"[^a-zA-Z0-9\-_\./@:]", "-", path)
@@ -23,20 +18,20 @@ class AkeylessWithSsmCache(AKeyless):
23
18
  if not refresh:
24
19
  missing = False
25
20
  try:
26
- response = ssm.get_parameter(Name=ssm_name, WithDecryption=True)
27
- except ssm.exceptions.ParameterNotFound:
21
+ response = self.ssm.get_parameter(Name=ssm_name, WithDecryption=True)
22
+ except self.ssm.exceptions.ParameterNotFound:
28
23
  missing = True
29
24
  if not missing:
30
- value = response["Parameter"]["Value"]
25
+ value = response["Parameter"].get("Value", "")
31
26
  if value:
32
27
  return value
33
28
 
34
29
  # otherwise get it out of Akeyless
35
- value = super().get(path)
30
+ value = str(super().get(path))
36
31
 
37
32
  # and make sure and store the new value in parameter store
38
33
  if value:
39
- ssm.put_parameter(
34
+ self.ssm.put_parameter(
40
35
  Name=ssm_name,
41
36
  Value=value,
42
37
  Type="SecureString",
@@ -44,3 +39,22 @@ class AkeylessWithSsmCache(AKeyless):
44
39
  )
45
40
 
46
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
@@ -1,50 +1,52 @@
1
+ from __future__ import annotations
2
+
1
3
  from botocore.exceptions import ClientError
2
- from clearskies.secrets.exceptions import NotFound
3
- class ParameterStore:
4
- _boto3 = None
5
- _environment = None
6
- _ssm = None
7
-
8
- def __init__(self, boto3, environment):
9
- self._boto3 = boto3
10
- self._environment = environment
11
- if not self._environment.get('AWS_REGION', True):
12
- raise ValueError("To use parameter store you must use set the 'AWS_REGION' environment variable")
13
- self._ssm = self._boto3.client('ssm', region_name=self._environment.get('AWS_REGION'))
14
-
15
- def create(self, path, value):
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:
16
18
  return self.update(path, value)
17
19
 
18
- def get(self, path, silent_if_not_found=False):
20
+ def get(self, path: str, silent_if_not_found: bool = False) -> str | None: # type: ignore[override]
19
21
  try:
20
- result = self._ssm.get_parameter(Name=path, WithDecryption=True)
22
+ result = self.ssm.get_parameter(Name=path, WithDecryption=True)
21
23
  except ClientError as e:
22
- if e.response['Error']['Code'] == 'ResourceNotFoundException':
24
+ error = e.response.get("Error", {})
25
+ if error.get("Code") == "ResourceNotFoundException":
23
26
  if silent_if_not_found:
24
27
  return None
25
- raise NotFound(
26
- f"Cound not find secret '{secret_id}' with version '{version}' and stage '{version_stage}'"
27
- )
28
+ raise NotFound(f"Could not find secret '{path}' in parameter store")
28
29
  raise e
29
- return result['Parameter']['Value']
30
+ return result["Parameter"].get("Value", "")
30
31
 
31
- def list_secrets(self, path):
32
- response = self._ssm.get_parameters_by_path(Path=path, Recursive=False)
33
- return [parameter['Name'] for parameter in response['Parameters']]
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]
34
35
 
35
- def update(self, path, value):
36
- response = self._ssm.put_parameter(
36
+ def update(self, path: str, value: str) -> bool: # type: ignore[override]
37
+ response = self.ssm.put_parameter(
37
38
  Name=path,
38
39
  Value=value,
39
- Type='String',
40
+ Type="String",
40
41
  Overwrite=True,
41
42
  )
42
43
  return True
43
44
 
44
- def upsert(self, path, value):
45
+ def upsert(self, path: str, value: str) -> bool: # type: ignore[override]
45
46
  return self.update(path, value)
46
47
 
47
- def list_sub_folders(self, path, value):
48
- raise NotImplementedError(
49
- "Parameter store doesn't support list_sub_folders."
50
- )
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")
@@ -1,75 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
1
5
  from botocore.exceptions import ClientError
2
- from clearskies.secrets.exceptions import NotFound
3
- class SecretsManager:
4
- _boto3 = None
5
- _environment = None
6
- _secrets_manager = None
7
-
8
- def __init__(self, boto3, environment):
9
- self._boto3 = boto3
10
- self._environment = environment
11
- if not self._environment.get('AWS_REGION', True):
12
- raise ValueError("To use secrets manager you must use set the 'AWS_REGION' environment variable")
13
- self._secrets_manager = self._boto3.client('secretsmanager', region_name=self._environment.get('AWS_REGION'))
14
-
15
- def create(self, secret_id, value, kms_key_id=None):
16
- calling_parameters = {
17
- 'SecretId': secret_id,
18
- 'SecretString': value,
19
- 'KmsKeyId': kms_key_id,
20
- }
21
- calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
22
- result = self._secrets_manager.create_secret(**calling_parameters)
6
+ from clearskies.exceptions.not_found import NotFound
7
+ from types_boto3_secretsmanager import SecretsManagerClient
8
+ from types_boto3_secretsmanager.type_defs import SecretListEntryTypeDef
23
9
 
24
- def get(self, secret_id, version_id=None, version_stage=None, silent_if_not_found=False):
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:
25
21
  calling_parameters = {
26
- 'SecretId': secret_id,
27
- 'VersionId': version_id,
28
- 'VersionStage': version_stage,
22
+ "SecretId": secret_id,
23
+ "SecretString": value,
24
+ "KmsKeyId": kms_key_id,
29
25
  }
30
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
+
31
45
  try:
32
- result = self._secrets_manager.get_secret_value(**calling_parameters)
46
+ result = self.secrets_manager.get_secret_value(**calling_parameters)
33
47
  except ClientError as e:
34
- if e.response['Error']['Code'] == 'ResourceNotFoundException':
48
+ error = e.response.get("Error", {})
49
+ if error.get("Code") == "ResourceNotFoundException":
35
50
  if silent_if_not_found:
36
51
  return None
37
52
  raise NotFound(
38
- f"Cound not find secret '{secret_id}' with version '{version_id}' and stage '{version_stage}'"
53
+ f"Could not find secret '{secret_id}' with version '{version_id}' and stage '{version_stage}'"
39
54
  )
40
55
  raise e
41
- if result.get('SecretString'):
42
- return result.get('SecretString')
43
- return result.get('SecretBinary')
44
-
45
- def list_secrets(self, path):
46
- results = self._secrets_manager.list_secrets(Filters=[
47
- {
48
- 'Key': 'name',
49
- 'Values': [path],
50
- },
51
- ], )
52
- return results['SecretList']
53
-
54
- def update(self, secret_id, value, kms_key_id=None):
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]
55
72
  calling_parameters = {
56
- 'SecretId': secret_id,
57
- 'SecretString': value,
58
- 'KmsKeyId': kms_key_id,
73
+ "SecretId": secret_id,
74
+ "SecretString": value,
59
75
  }
60
- calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
61
- result = self._secrets_manager.update_secret(**calling_parameters)
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"))
62
82
 
63
- def upsert(self, secret_id, value, kms_key_id=None):
83
+ def upsert(self, secret_id: str, value: str, kms_key_id: str | None = None) -> bool: # type: ignore[override]
64
84
  calling_parameters = {
65
- 'SecretId': secret_id,
66
- 'SecretString': value,
67
- 'KmsKeyId': kms_key_id,
85
+ "SecretId": secret_id,
86
+ "SecretString": value,
68
87
  }
69
- calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
70
- result = self._secrets_manager.put_secret_value(**calling_parameters)
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
71
91
 
72
- def list_sub_folders(self, path, value):
73
- raise NotImplementedError(
74
- "Secrets Manager doesn't support list_sub_folders."
75
- )
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,7 +0,0 @@
1
- Copyright 2021 Conor Mancone
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
-
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
-
7
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,71 +0,0 @@
1
- clearskies_aws/__init__.py,sha256=BZKL4SIyxx4MXpyP5hmwsMo2oJatyv5c2wjJOPzKBQ4,134
2
- clearskies_aws/actions/__init__.py,sha256=w_IWJg4UHV9Opv69zheH7ZHj1BnCzYLlm2JhFvHQCf8,2998
3
- clearskies_aws/actions/action_aws.py,sha256=zBXsvhBDDm2dAU4MVlub84Ld1YNZ6ey8_zf4OeTe8go,4254
4
- clearskies_aws/actions/assume_role.py,sha256=tZHKMTQaImj_EAWormFZdIPkR_db7yBwOJxYaUyuGdA,4085
5
- clearskies_aws/actions/assume_role_test.py,sha256=AoXWRtApRZz_oPDj6QhT0ERmYjKOHOXLNet-MgbbeB8,2451
6
- clearskies_aws/actions/ses.py,sha256=xJ0NEFPP-8kARPGjDv1yf_bhAiaIIM1mDzMxY22GE5E,7925
7
- clearskies_aws/actions/ses_test.py,sha256=lyNbqI46Ld_ZUKtWLgSwwfSOFksQ39H4hzPS3eTx7nk,3076
8
- clearskies_aws/actions/sns.py,sha256=00E-eCQuq3Ygscyia928K8jX8rhDZrl5kyasM0st_uI,2288
9
- clearskies_aws/actions/sns_test.py,sha256=iqlaZOA-0YHPOu52-XqpCIAgAv1JZodDdlm4YYYhj8U,2306
10
- clearskies_aws/actions/sqs.py,sha256=i-QyyFy-ImivXk-m-Bn9Mh5BF5qODthUyZAQVRsb8vs,3392
11
- clearskies_aws/actions/sqs_test.py,sha256=aazVN-bY6E17Pu3hDwLldj6YPirbqXjM6LfYFomUVmQ,4040
12
- clearskies_aws/actions/step_function.py,sha256=wiDgGdOWdY0zVoClDsX08YFZqTR0bK3ydpyj0Qa7w_s,2599
13
- clearskies_aws/actions/step_function_test.py,sha256=0gRYLDVRt2R-Cv-5Pr2ui4Gbp8rg9nPhQY4ERALg6qU,3837
14
- clearskies_aws/backends/__init__.py,sha256=t98x6QNF2bvKzVI_owS4L3eG5JNVtsLS9c2fBw8uh9s,379
15
- clearskies_aws/backends/dynamo_db_backend.py,sha256=i-Na3McQDbyCYmQewBPKgbWEd3A3pf2wfmLs6FsRJ48,29487
16
- clearskies_aws/backends/dynamo_db_backend_test.py,sha256=wtymsOEVzO9y7_Whu9cCRyioRTmSimbLmRQSt3J4cik,13106
17
- clearskies_aws/backends/dynamo_db_condition_parser.py,sha256=v-DO4ijdjiHwBsSDF3dUgqQw5twCcG-5NA1aq7kUYQ0,13239
18
- clearskies_aws/backends/dynamo_db_condition_parser_test.py,sha256=n7snumB8GxyMBVlj-HL7JrsNj-ZJ91c5gdik0UNM5Ag,12664
19
- clearskies_aws/backends/dynamo_db_parti_ql_backend.py,sha256=pVd_N7XFf9PcTAm6rhMxih_sj9Ix6RkRj3LwmFy_Mr4,48544
20
- clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py,sha256=7gH-RDh7JeaiEGLXsOB512SLmOp0hjdtWQ1cMmexvDM,23649
21
- clearskies_aws/backends/sqs_backend.py,sha256=hT1JCvCMU76Q4Ir7OeX8U8eAMLIu1_tMwGgaN9rpsPk,2726
22
- clearskies_aws/backends/sqs_backend_test.py,sha256=iCuHVVqZIR_PDGUUMZIkpir8yyJy3dcPR00AWR2N25U,1138
23
- clearskies_aws/contexts/__init__.py,sha256=YjwRaSoAqC-nmo3aFB8rzyuiHSP9mi77FVM-8RF0hRE,472
24
- clearskies_aws/contexts/cli.py,sha256=11Giwl10ydackFo0clpTnHZP-HTmJnvtpbhxJf74jDA,506
25
- clearskies_aws/contexts/cli_websocket_mock.py,sha256=W5Ujp9UIz4QG2A33OTpozFLihF5SaN2uUhwf17-qj3g,1085
26
- clearskies_aws/contexts/lambda_api_gateway.py,sha256=zYO_g4K87XYlQeZLYwF7_mzYbZCUzaZYaelpfcfeuTw,1002
27
- clearskies_aws/contexts/lambda_api_gateway_web_socket.py,sha256=5vTe8uWgHzQ11aToBeRHoFymYufUsTXBLC6sE6zUt9Q,1067
28
- clearskies_aws/contexts/lambda_elb.py,sha256=R4tyuRPjuTHNgmyzDqxy-VNePsIdXRH-mUMyWEOMky4,952
29
- clearskies_aws/contexts/lambda_http_gateway.py,sha256=9gmlTTPkxrtLmOQa7uUojCk9tFGg5kJGDB7GiAXx-bQ,1009
30
- clearskies_aws/contexts/lambda_invocation.py,sha256=U_S3cuY3G1_7ktzGiFTiO2LUnqauqvEDfKKEhYdPtpg,1336
31
- clearskies_aws/contexts/lambda_sns.py,sha256=crIo3S78N8xfthZsv0l5ooNlpF21ETjoWmHe1WCGQ4w,1364
32
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py,sha256=iypcI4BpxV4IHcKKUWDxK3HuGyaYiP5YPwpFAm4auCw,1801
33
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py,sha256=SkEIV_cgxCi-1zC3WXsRNyG6bCt7LB_6CWpxvB8QPxo,1855
34
- clearskies_aws/contexts/wsgi.py,sha256=wmHnDIfcrQN0I547uE2mlHHQtOLOpLfpbKpBJkw5puY,510
35
- clearskies_aws/di/__init__.py,sha256=KLq6G-CKR-Vdk9LZe5TijW_U-McJlrp1QuIXqmyAL-A,56
36
- clearskies_aws/di/standard_dependencies.py,sha256=TRST5BxxPQpWJyvXnAwCRJWYRDXC7qM05X3ffYI7EXc,2023
37
- clearskies_aws/handlers/__init__.py,sha256=E2x04QGy5dfaDIB5Xu8IRWwgypIrgGEZibijtKWe8jM,112
38
- clearskies_aws/handlers/secrets_manager_rotation.py,sha256=KV4XWKar4gT0Xu0eM_D75ECzYkjXxjeZtkOopPRG9rc,7471
39
- clearskies_aws/handlers/simple_body_routing.py,sha256=FGdeDY8nZIGfCOn0oOHi6EOP1h4xXcBP3W9fH0NuBlc,1449
40
- clearskies_aws/input_outputs/__init__.py,sha256=Tf8kWN95nwUbvlVaboYT06ICtSuZd0MPPzwWhO5iQGs,385
41
- clearskies_aws/input_outputs/cli_websocket_mock.py,sha256=J3PDYnFxOzqYhmlxWFcobbb4McKpsGduIBq6jdSAx5Y,434
42
- clearskies_aws/input_outputs/lambda_api_gateway.py,sha256=Xi9IdT0u20IMbaIJMBtF2wmsNLCDvmEUVoYB9IRqV00,3479
43
- clearskies_aws/input_outputs/lambda_api_gateway_test.py,sha256=B4tUxTqnAEeV0D0VRMu0HyphOJKNij-LtFd2ERcnKI8,2979
44
- clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py,sha256=kG2fys9X7TailvlOBzIMItuSI8rlvDbBD4AXtI091hM,308
45
- clearskies_aws/input_outputs/lambda_elb.py,sha256=b675h0DxTGo9YscXN4mwzRGzP3hNSie4iPRKOg3kKPI,750
46
- clearskies_aws/input_outputs/lambda_http_gateway.py,sha256=YQk7GlQWtQjDiKWhX5jvXc3un1eq2sm1v_jl6iseNoQ,692
47
- clearskies_aws/input_outputs/lambda_invocation.py,sha256=yF3rYJ-r0YYzlh--33fGE-I-pzyhQxDmDo1TUQxCu00,1122
48
- clearskies_aws/input_outputs/lambda_sns.py,sha256=0m1dfVCSEftcovGAEWhU9ig4txX9duLkTXbdtjIzSAw,2055
49
- clearskies_aws/input_outputs/lambda_sqs_standard.py,sha256=RjsOqzyUovTuO3Nb9CSiWnVaHHxJiPmvOmuQbmpIW38,1974
50
- clearskies_aws/mocks/__init__.py,sha256=mn764gINN667tYoJfnsM6HjAAhCsO_kZ6E-fUwdLY50,22
51
- clearskies_aws/mocks/actions/__init__.py,sha256=to1r8B365Et2PRVfUWWnJGt7Hdr8vwwQuNyZvTSTP6g,152
52
- clearskies_aws/mocks/actions/ses.py,sha256=sCCNk_WdnQbINOzg8E31xyoeoEuUQeX-RwPbJ9ueNik,937
53
- clearskies_aws/mocks/actions/sns.py,sha256=eFnLf-ZIDCL0b62uKDBbPSYXZs1Mc3kRaGv_gPt-8rA,658
54
- clearskies_aws/mocks/actions/sqs.py,sha256=7cwGX7XUhzfGzDJYso2UkUBhMaugvxEqeNKbh9hOark,662
55
- clearskies_aws/mocks/actions/step_function.py,sha256=1JbdsVwPea1wmNnTnlgeWzGaQNBAkJl-2-Y5PG3uORI,888
56
- clearskies_aws/secrets/__init__.py,sha256=MF-e-9FTfnJ64UdZAmYiOxqOl1vSxvWA-FlFFv05CKE,326
57
- clearskies_aws/secrets/additional_configs/__init__.py,sha256=ejLAqwFTP6Xc5aXJAUpBhK1xUtmLzEmexHBuOOmgNBc,1919
58
- clearskies_aws/secrets/additional_configs/iam_db_auth.py,sha256=K6eLjo_D0uSxtCfqTAqGshDi3uz_iF-T0sDSJLyoTew,1082
59
- clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py,sha256=hzvR_WBwSoLcMdGXwhqkvKMKmjXzhZgimPWfm2MWSZQ,3467
60
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py,sha256=L2-E8Tm6BDHV-yJJ_M_Lo72jNY7uBaTp8SPrRuekcUE,3776
61
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py,sha256=Llvg8uQW8J-qndAlDDtg9TY_Tvu2h9tUzchxxUtaRik,6444
62
- clearskies_aws/secrets/akeyless_with_ssm_cache.py,sha256=MFAnajwO_XtZWMZHxsHFZBZa8V9vQGH0NSK4I6uVaow,1559
63
- clearskies_aws/secrets/parameter_store.py,sha256=lxBlp_9d2-vVjGNfl5859XzZCcLVMMrZtlJG8lKGVPA,1810
64
- clearskies_aws/secrets/parameter_store_test.py,sha256=35fTNau4tq_D4elMwyyByIiLesnmn05QhC_X1FVQXsM,763
65
- clearskies_aws/secrets/secrets_manager.py,sha256=jlpfAFC23EeSpm50L8B-yrXg4IROQq-M_90zzXDp_ak,3056
66
- clearskies_aws/secrets/secrets_manager_test.py,sha256=__YSe-YRbbE1S1SBvZZFQd3brIX5DPX2_wE9MI_Ezx0,788
67
- clearskies_aws/web_socket_connection_model.py,sha256=d_Au_Pu7YXBfc7_lbuI7zz4MZ8ZOOwGM0oooppEofcI,1776
68
- clear_skies_aws-1.10.2.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
69
- clear_skies_aws-1.10.2.dist-info/METADATA,sha256=jgygnKXGfTw4rejQAglNblRFraGfcX0VeRcwG7NYGyk,8784
70
- clear_skies_aws-1.10.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
71
- clear_skies_aws-1.10.2.dist-info/RECORD,,