clear-skies-aws 1.10.2__py3-none-any.whl → 2.0.1__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 (73) hide show
  1. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.1.dist-info}/METADATA +36 -35
  2. clear_skies_aws-2.0.1.dist-info/RECORD +4 -0
  3. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.1.dist-info}/WHEEL +1 -1
  4. clear_skies_aws-2.0.1.dist-info/licenses/LICENSE +21 -0
  5. clear_skies_aws-1.10.2.dist-info/LICENSE +0 -7
  6. clear_skies_aws-1.10.2.dist-info/RECORD +0 -71
  7. clearskies_aws/__init__.py +0 -2
  8. clearskies_aws/actions/__init__.py +0 -108
  9. clearskies_aws/actions/action_aws.py +0 -118
  10. clearskies_aws/actions/assume_role.py +0 -102
  11. clearskies_aws/actions/assume_role_test.py +0 -72
  12. clearskies_aws/actions/ses.py +0 -194
  13. clearskies_aws/actions/ses_test.py +0 -89
  14. clearskies_aws/actions/sns.py +0 -64
  15. clearskies_aws/actions/sns_test.py +0 -77
  16. clearskies_aws/actions/sqs.py +0 -82
  17. clearskies_aws/actions/sqs_test.py +0 -127
  18. clearskies_aws/actions/step_function.py +0 -66
  19. clearskies_aws/actions/step_function_test.py +0 -103
  20. clearskies_aws/backends/__init__.py +0 -12
  21. clearskies_aws/backends/dynamo_db_backend.py +0 -614
  22. clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
  23. clearskies_aws/backends/dynamo_db_condition_parser.py +0 -365
  24. clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
  25. clearskies_aws/backends/dynamo_db_parti_ql_backend.py +0 -1123
  26. clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
  27. clearskies_aws/backends/sqs_backend.py +0 -80
  28. clearskies_aws/backends/sqs_backend_test.py +0 -31
  29. clearskies_aws/contexts/__init__.py +0 -10
  30. clearskies_aws/contexts/cli.py +0 -19
  31. clearskies_aws/contexts/cli_websocket_mock.py +0 -33
  32. clearskies_aws/contexts/lambda_api_gateway.py +0 -30
  33. clearskies_aws/contexts/lambda_api_gateway_web_socket.py +0 -30
  34. clearskies_aws/contexts/lambda_elb.py +0 -30
  35. clearskies_aws/contexts/lambda_http_gateway.py +0 -30
  36. clearskies_aws/contexts/lambda_invocation.py +0 -48
  37. clearskies_aws/contexts/lambda_sns.py +0 -43
  38. clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +0 -51
  39. clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
  40. clearskies_aws/contexts/wsgi.py +0 -19
  41. clearskies_aws/di/__init__.py +0 -1
  42. clearskies_aws/di/standard_dependencies.py +0 -60
  43. clearskies_aws/handlers/__init__.py +0 -2
  44. clearskies_aws/handlers/secrets_manager_rotation.py +0 -174
  45. clearskies_aws/handlers/simple_body_routing.py +0 -39
  46. clearskies_aws/input_outputs/__init__.py +0 -8
  47. clearskies_aws/input_outputs/cli_websocket_mock.py +0 -12
  48. clearskies_aws/input_outputs/lambda_api_gateway.py +0 -105
  49. clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
  50. clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +0 -8
  51. clearskies_aws/input_outputs/lambda_elb.py +0 -21
  52. clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
  53. clearskies_aws/input_outputs/lambda_invocation.py +0 -34
  54. clearskies_aws/input_outputs/lambda_sns.py +0 -52
  55. clearskies_aws/input_outputs/lambda_sqs_standard.py +0 -54
  56. clearskies_aws/mocks/__init__.py +0 -1
  57. clearskies_aws/mocks/actions/__init__.py +0 -6
  58. clearskies_aws/mocks/actions/ses.py +0 -28
  59. clearskies_aws/mocks/actions/sns.py +0 -23
  60. clearskies_aws/mocks/actions/sqs.py +0 -23
  61. clearskies_aws/mocks/actions/step_function.py +0 -26
  62. clearskies_aws/secrets/__init__.py +0 -7
  63. clearskies_aws/secrets/additional_configs/__init__.py +0 -54
  64. clearskies_aws/secrets/additional_configs/iam_db_auth.py +0 -29
  65. clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +0 -92
  66. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -81
  67. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +0 -141
  68. clearskies_aws/secrets/akeyless_with_ssm_cache.py +0 -46
  69. clearskies_aws/secrets/parameter_store.py +0 -50
  70. clearskies_aws/secrets/parameter_store_test.py +0 -18
  71. clearskies_aws/secrets/secrets_manager.py +0 -75
  72. clearskies_aws/secrets/secrets_manager_test.py +0 -18
  73. clearskies_aws/web_socket_connection_model.py +0 -43
@@ -1,92 +0,0 @@
1
- import time
2
- import clearskies
3
- class IAMDBAuthWithSSM(clearskies.di.AdditionalConfig):
4
- def provide_subprocess(self):
5
- import subprocess
6
- return subprocess
7
-
8
- def provide_socket(self):
9
- import socket
10
- return socket
11
-
12
- def provide_connection_details(self, environment, subprocess, socket, boto3):
13
- local_port = self.open_tunnel(environment, subprocess, socket, boto3)
14
-
15
- return {
16
- 'host': '127.0.0.1',
17
- 'database': environment.get('db_database'),
18
- 'username': environment.get('db_username'),
19
- 'password': self.get_password(environment, boto3),
20
- 'ssl_ca': 'rds-cert-bundle.pem',
21
- 'port': local_port,
22
- }
23
-
24
- def get_password(self, environment, boto3):
25
- endpoint = environment.get('db_endpoint')
26
- username = environment.get('db_username')
27
- region = environment.get('db_region')
28
-
29
- rds_api = boto3.Session().client('rds', region_name=region)
30
- return rds_api.generate_db_auth_token(DBHostname=endpoint, Port='3306', DBUsername=username, Region=region)
31
-
32
- def open_tunnel(self, environment, subprocess, socket, boto3):
33
- endpoint = environment.get('db_endpoint')
34
- region = environment.get('db_region')
35
- instance_name = environment.get('instance_name')
36
- local_proxy_port = int(environment.get('local_proxy_port', '9000'))
37
-
38
- ec2_api = boto3.client('ec2', region_name=region)
39
- running_instances = ec2_api.describe_instances(
40
- Filters=[{
41
- 'Name': 'tag:Name',
42
- 'Values': [instance_name]
43
- }, {
44
- 'Name': 'instance-state-name',
45
- 'Values': ['running']
46
- }],
47
- )
48
- instance_ids = []
49
- for reservation in running_instances['Reservations']:
50
- for instance in reservation['Instances']:
51
- instance_ids.append(instance['InstanceId'])
52
-
53
- if len(instance_ids) == 0:
54
- raise ValueError('Failed to launch SSM tunnel! Cannot find bastion!')
55
-
56
- instance_id = instance_ids.pop()
57
- self._connect_to_bastion(local_proxy_port, instance_id, endpoint, subprocess, socket)
58
- return local_proxy_port
59
-
60
- def _connect_to_bastion(self, local_proxy_port, instance_id, endpoint, subprocess, socket):
61
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
62
- result = sock.connect_ex(('127.0.0.1', local_proxy_port))
63
- if result == 0:
64
- sock.close()
65
- return
66
-
67
- tunnel_command = [
68
- 'aws',
69
- '--region',
70
- 'us-east-1',
71
- 'ssm',
72
- 'start-session',
73
- '--target',
74
- '{}'.format(instance_id),
75
- '--document-name',
76
- 'AWS-StartPortForwardingSessionToRemoteHost',
77
- '--parameters={{"host":["{}"], "portNumber":["3306"],"localPortNumber":["{}"]}}'.format(
78
- endpoint, local_proxy_port
79
- ),
80
- ]
81
-
82
- subprocess.Popen(tunnel_command)
83
- connected = False
84
- attempts = 0
85
- while not connected and attempts < 6:
86
- attempts += 1
87
- time.sleep(0.5)
88
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
89
- result = sock.connect_ex(('127.0.0.1', local_proxy_port))
90
- if result == 0:
91
- return
92
- raise ValueError('Failed to launch SSM tunnel with command: ' + ' '.join(tunnel_command))
@@ -1,81 +0,0 @@
1
- from clearskies.secrets.additional_configs import MySQLConnectionDynamicProducerViaSSHCertBastion as Base
2
- from pathlib import Path
3
- import socket
4
- import subprocess
5
- import os
6
- import time
7
- class MySQLConnectionDynamicProducerViaSSHCertBastion(Base):
8
- _config = None
9
- _boto3 = None
10
-
11
- def __init__(
12
- self,
13
- producer_name=None,
14
- bastion_region=None,
15
- bastion_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_region': bastion_region,
29
- 'bastion_name': bastion_name,
30
- 'bastion_username': bastion_username,
31
- 'public_key_file_path': public_key_file_path,
32
- 'local_proxy_port': local_proxy_port,
33
- 'cert_issuer_name': cert_issuer_name,
34
- 'database_host': database_host,
35
- 'database_name': database_name,
36
- }
37
-
38
- def provide_connection_details(self, environment, secrets, boto3):
39
- self._boto3 = boto3
40
- return super().provide_connection_details(environment, secrets)
41
-
42
- def _get_bastion_host(self, environment):
43
- bastion_host = self._fetch_config(environment, 'bastion_host', 'akeyless_mysql_bastion_host', default='')
44
- bastion_name = self._fetch_config(environment, 'bastion_name', 'akeyless_mysql_bastion_name', default='')
45
- if bastion_host:
46
- return bastion_host
47
- if bastion_name:
48
- bastion_region = self._fetch_config(environment, 'bastion_region', 'akeyless_mysql_bastion_region')
49
- return self._public_ip_from_name(bastion_name, bastion_region)
50
- raise ValueError(
51
- 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'."
52
- )
53
-
54
- def _public_ip_from_name(self, bastion_name, bastion_region):
55
- ec2 = self._boto3.client('ec2', region_name=bastion_region)
56
- response = ec2.describe_instances(
57
- Filters=[
58
- {
59
- 'Name': 'tag:Name',
60
- 'Values': [bastion_name]
61
- },
62
- {
63
- 'Name': 'instance-state-name',
64
- 'Values': ['running']
65
- },
66
- ],
67
- )
68
- if not response.get('Reservations'):
69
- raise ValueError(
70
- f"Could not find a running instance with the designated bastion name, '{bastion_name}' in region '{bastion_region}'"
71
- )
72
- if not response.get('Reservations')[0].get('Instances'):
73
- raise ValueError(
74
- f"Could not find a running instance with the designated bastion name, '{bastion_name}' in region '{bastion_region}'"
75
- )
76
- instance = response.get('Reservations')[0].get('Instances')[0]
77
- if not instance.get('PublicIpAddress'):
78
- raise ValueError(
79
- f"I found the bastion instance with a name of '{bastion_name}' in region '{bastion_region}', but it doesn't have a public IP address"
80
- )
81
- return instance.get('PublicIpAddress')
@@ -1,141 +0,0 @@
1
- from .mysql_connection_dynamic_producer_via_ssh_cert_bastion import MySQLConnectionDynamicProducerViaSSHCertBastion as Base
2
- from pathlib import Path
3
- import socket
4
- import subprocess
5
- import os
6
- import time
7
- class MySQLConnectionDynamicProducerViaSSMBastion(Base):
8
- _config = None
9
- _boto3 = None
10
-
11
- def __init__(
12
- self,
13
- producer_name=None,
14
- bastion_region=None,
15
- bastion_name=None,
16
- bastion_username=None,
17
- bastion_instance_id=None,
18
- public_key_file_path=None,
19
- local_proxy_port=None,
20
- database_host=None,
21
- database_name=None
22
- ):
23
- # not using kwargs because I want the argument list to be explicit
24
- 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,
34
- }
35
-
36
- def provide_connection_details(self, environment, secrets, boto3):
37
- self._boto3 = boto3
38
- if not secrets:
39
- raise ValueError(
40
- "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
- )
42
-
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')
45
- bastion_instance_id = self._get_bastion_instance_id(environment)
46
- public_key_file_path = self._fetch_config(
47
- environment, 'public_key_file_path', 'mysql_bastion_public_key_file_path'
48
- )
49
- local_proxy_port = self._fetch_config(
50
- environment, 'local_proxy_port', 'akeyless_mysql_bastion_local_proxy_port', default=8888
51
- )
52
- database_host = self._fetch_config(environment, 'database_host', 'db_host')
53
- database_name = self._fetch_config(environment, 'database_name', 'db_database')
54
-
55
- # Create the SSH tunnel (yeah, it's obnoxious)
56
- self._create_tunnel(
57
- secrets, bastion_instance_id, bastion_username, bastion_region, public_key_file_path, local_proxy_port,
58
- database_host
59
- )
60
-
61
- # and now we can fetch credentials
62
- credentials = secrets.get_dynamic_secret(producer_name)
63
-
64
- return {
65
- 'username': credentials['user'],
66
- 'password': credentials['password'],
67
- 'host': '127.0.0.1',
68
- 'database': database_name,
69
- 'port': local_proxy_port,
70
- }
71
-
72
- def _get_bastion_instance_id(self, environment):
73
- bastion_instance_id = self._fetch_config(
74
- environment, 'bastion_instance_id', 'mysql_bastion_instance_id', default=''
75
- )
76
- bastion_name = self._fetch_config(environment, 'bastion_name', 'mysql_bastion_name', default='')
77
- if bastion_instance_id:
78
- return bastion_instance_id
79
- if bastion_name:
80
- bastion_region = self._fetch_config(environment, 'bastion_region', 'mysql_bastion_region')
81
- return self._instance_id_from_name(bastion_name, bastion_region)
82
- raise ValueError(
83
- 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
- )
85
-
86
- def _instance_id_from_name(self, bastion_name, bastion_region):
87
- ec2 = self._boto3.client('ec2', region_name=bastion_region)
88
- response = ec2.describe_instances(
89
- Filters=[
90
- {
91
- 'Name': 'tag:Name',
92
- 'Values': [bastion_name]
93
- },
94
- {
95
- 'Name': 'instance-state-name',
96
- 'Values': ['running']
97
- },
98
- ],
99
- )
100
- if not response.get('Reservations'):
101
- raise ValueError(
102
- f"Could not find a running instance with the designated bastion name, '{bastion_name}' in region '{bastion_region}'"
103
- )
104
- if not response.get('Reservations')[0].get('Instances'):
105
- raise ValueError(
106
- f"Could not find a running instance with the designated bastion name, '{bastion_name}' in region '{bastion_region}'"
107
- )
108
- return response.get('Reservations')[0].get('Instances')[0]['InstanceId']
109
-
110
- def _create_tunnel(
111
- self, secrets, bastion_instance_id, bastion_username, bastion_region, public_key_file_path, local_proxy_port,
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
- # and now we can do this thing.
122
- 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}'
125
- ]
126
- my_env = os.environ.copy()
127
- my_env['AWS_DEFAULT_REGION'] = bastion_region
128
- subprocess.Popen(tunnel_command)
129
- connected = False
130
- attempts = 0
131
- while not connected and attempts < 6:
132
- attempts += 1
133
- time.sleep(0.5)
134
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
135
- result = sock.connect_ex(('127.0.0.1', local_proxy_port))
136
- if result == 0:
137
- connected = True
138
- if not connected:
139
- raise ValueError(
140
- 'Failed to open SSH tunnel. The following command was used: \n' + ' '.join(tunnel_command)
141
- )
@@ -1,46 +0,0 @@
1
- import re
2
-
3
- from clearskies.secrets import AKeyless
4
-
5
-
6
- class AkeylessWithSsmCache(AKeyless):
7
- _boto3 = None
8
-
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
-
17
- def get(self, path, refresh=False):
18
- ssm = self._boto3.client("ssm", region_name="us-east-1")
19
- # AWS SSM parameter paths only allow a-z, A-Z, 0-9, -, _, ., /, @, and :
20
- # Replace any disallowed characters with hyphens
21
- ssm_name = re.sub(r"[^a-zA-Z0-9\-_\./@:]", "-", path)
22
- # if we're not forcing a refresh, then see if it is in paramater store
23
- if not refresh:
24
- missing = False
25
- try:
26
- response = ssm.get_parameter(Name=ssm_name, WithDecryption=True)
27
- except ssm.exceptions.ParameterNotFound:
28
- missing = True
29
- if not missing:
30
- value = response["Parameter"]["Value"]
31
- if value:
32
- return value
33
-
34
- # otherwise get it out of Akeyless
35
- value = super().get(path)
36
-
37
- # and make sure and store the new value in parameter store
38
- if value:
39
- ssm.put_parameter(
40
- Name=ssm_name,
41
- Value=value,
42
- Type="SecureString",
43
- Overwrite=True,
44
- )
45
-
46
- return value
@@ -1,50 +0,0 @@
1
- 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):
16
- return self.update(path, value)
17
-
18
- def get(self, path, silent_if_not_found=False):
19
- try:
20
- result = self._ssm.get_parameter(Name=path, WithDecryption=True)
21
- except ClientError as e:
22
- if e.response['Error']['Code'] == 'ResourceNotFoundException':
23
- if silent_if_not_found:
24
- return None
25
- raise NotFound(
26
- f"Cound not find secret '{secret_id}' with version '{version}' and stage '{version_stage}'"
27
- )
28
- raise e
29
- return result['Parameter']['Value']
30
-
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']]
34
-
35
- def update(self, path, value):
36
- response = self._ssm.put_parameter(
37
- Name=path,
38
- Value=value,
39
- Type='String',
40
- Overwrite=True,
41
- )
42
- return True
43
-
44
- def upsert(self, path, value):
45
- return self.update(path, value)
46
-
47
- def list_sub_folders(self, path, value):
48
- raise NotImplementedError(
49
- "Parameter store doesn't support list_sub_folders."
50
- )
@@ -1,18 +0,0 @@
1
- import unittest
2
- from types import SimpleNamespace
3
- from unittest.mock import MagicMock
4
-
5
- from .parameter_store import ParameterStore
6
-
7
-
8
- class ParameterStoreTest(unittest.TestCase):
9
- def setUp(self):
10
- self.environment = SimpleNamespace(get=MagicMock(return_value='us-east-1'))
11
-
12
- def test_get(self):
13
- ssm = SimpleNamespace(get_parameter=MagicMock(return_value={'Parameter': {'Value': 'sup'}}))
14
- boto3 = SimpleNamespace(client=MagicMock(return_value=ssm))
15
- parameter_store = ParameterStore(boto3, self.environment)
16
- self.assertEqual("sup", parameter_store.get("/my/item"))
17
- ssm.get_parameter.assert_called_with(Name='/my/item', WithDecryption=True)
18
- boto3.client.assert_called_with('ssm', region_name='us-east-1')
@@ -1,75 +0,0 @@
1
- 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)
23
-
24
- def get(self, secret_id, version_id=None, version_stage=None, silent_if_not_found=False):
25
- calling_parameters = {
26
- 'SecretId': secret_id,
27
- 'VersionId': version_id,
28
- 'VersionStage': version_stage,
29
- }
30
- calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
31
- try:
32
- result = self._secrets_manager.get_secret_value(**calling_parameters)
33
- except ClientError as e:
34
- if e.response['Error']['Code'] == 'ResourceNotFoundException':
35
- if silent_if_not_found:
36
- return None
37
- raise NotFound(
38
- f"Cound not find secret '{secret_id}' with version '{version_id}' and stage '{version_stage}'"
39
- )
40
- 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):
55
- calling_parameters = {
56
- 'SecretId': secret_id,
57
- 'SecretString': value,
58
- 'KmsKeyId': kms_key_id,
59
- }
60
- calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
61
- result = self._secrets_manager.update_secret(**calling_parameters)
62
-
63
- def upsert(self, secret_id, value, kms_key_id=None):
64
- calling_parameters = {
65
- 'SecretId': secret_id,
66
- 'SecretString': value,
67
- 'KmsKeyId': kms_key_id,
68
- }
69
- calling_parameters = {key: value for (key, value) in calling_parameters.items() if value}
70
- result = self._secrets_manager.put_secret_value(**calling_parameters)
71
-
72
- def list_sub_folders(self, path, value):
73
- raise NotImplementedError(
74
- "Secrets Manager doesn't support list_sub_folders."
75
- )
@@ -1,18 +0,0 @@
1
- import unittest
2
- from types import SimpleNamespace
3
- from unittest.mock import MagicMock
4
-
5
- from .secrets_manager import SecretsManager
6
-
7
-
8
- class SecretsManagerTest(unittest.TestCase):
9
- def setUp(self):
10
- self.environment = SimpleNamespace(get=MagicMock(return_value='us-east-1'))
11
-
12
- def test_get(self):
13
- secretsmanager = SimpleNamespace(get_secret_value=MagicMock(return_value={'SecretString': 'sup'}))
14
- boto3 = SimpleNamespace(client=MagicMock(return_value=secretsmanager))
15
- secrets_manager = SecretsManager(boto3, self.environment)
16
- self.assertEqual("sup", secrets_manager.get("/my/item"))
17
- secretsmanager.get_secret_value.assert_called_with(SecretId='/my/item')
18
- boto3.client.assert_called_with('secretsmanager', region_name='us-east-1')
@@ -1,43 +0,0 @@
1
- from abc import abstractmethod
2
- import json
3
-
4
- import clearskies
5
-
6
-
7
- class WebSocketConnectionModel(clearskies.Model):
8
-
9
- id_column_name = "connection_id"
10
-
11
- def __init__(self, backend, columns, boto3, input_output):
12
- super().__init__(backend, columns)
13
- self._boto3 = boto3
14
- self._input_output = input_output
15
-
16
- def _build_model(self):
17
- model_class = self.model_class()
18
- return model_class(self._backend, self._columns, self._boto3, self._input_output)
19
-
20
- def send(self, message):
21
- if not self.exists:
22
- raise ValueError("Cannot send message to non-existent connection.")
23
- if not self.data.get("connection_id"):
24
- raise ValueError(
25
- f"Hmmm... I couldn't find the connection id for the {self.__class__.__name__}. I'm picky about id column names. Can you please make sure I have a column called connection_id and that it contains the connection id?"
26
- )
27
-
28
- event = self._input_output.context_specifics()["event"]
29
- domain = event.get("requestContext", {}).get("domainName")
30
- stage = event.get("requestContext", {}).get("stage")
31
- # only include the stage if we're using the default AWS domain - not with a custom domain
32
- if ".amazonaws.com" in domain:
33
- endpoint_url = f"https://{domain}/{stage}"
34
- else:
35
- endpoint_url = f"https://{domain}"
36
- api_gateway = self._boto3.client("apigatewaymanagementapi", endpoint_url=endpoint_url)
37
-
38
- bytes_message = json.dumps(message).encode("utf-8")
39
- try:
40
- response = api_gateway.post_to_connection(Data=bytes_message, ConnectionId=self.connection_id)
41
- except api_gateway.exceptions.GoneException:
42
- self.delete()
43
- return response