boto3-assist 0.32.0__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 (67) hide show
  1. boto3_assist/__init__.py +0 -0
  2. boto3_assist/aws_config.py +199 -0
  3. boto3_assist/aws_lambda/event_info.py +414 -0
  4. boto3_assist/aws_lambda/mock_context.py +5 -0
  5. boto3_assist/boto3session.py +87 -0
  6. boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
  7. boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
  8. boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
  9. boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
  10. boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
  11. boto3_assist/cognito/cognito_authorizer.py +169 -0
  12. boto3_assist/cognito/cognito_connection.py +59 -0
  13. boto3_assist/cognito/cognito_utility.py +514 -0
  14. boto3_assist/cognito/jwks_cache.py +21 -0
  15. boto3_assist/cognito/user.py +27 -0
  16. boto3_assist/connection.py +146 -0
  17. boto3_assist/connection_tracker.py +120 -0
  18. boto3_assist/dynamodb/dynamodb.py +1206 -0
  19. boto3_assist/dynamodb/dynamodb_connection.py +113 -0
  20. boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
  21. boto3_assist/dynamodb/dynamodb_importer.py +102 -0
  22. boto3_assist/dynamodb/dynamodb_index.py +507 -0
  23. boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
  24. boto3_assist/dynamodb/dynamodb_key.py +130 -0
  25. boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
  26. boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
  27. boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
  28. boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
  29. boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
  30. boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
  31. boto3_assist/dynamodb/readme.md +68 -0
  32. boto3_assist/dynamodb/troubleshooting.md +7 -0
  33. boto3_assist/ec2/ec2_connection.py +57 -0
  34. boto3_assist/environment_services/__init__.py +0 -0
  35. boto3_assist/environment_services/environment_loader.py +128 -0
  36. boto3_assist/environment_services/environment_variables.py +219 -0
  37. boto3_assist/erc/__init__.py +64 -0
  38. boto3_assist/erc/ecr_connection.py +57 -0
  39. boto3_assist/errors/custom_exceptions.py +46 -0
  40. boto3_assist/http_status_codes.py +80 -0
  41. boto3_assist/models/serializable_model.py +9 -0
  42. boto3_assist/role_assumption_mixin.py +38 -0
  43. boto3_assist/s3/s3.py +64 -0
  44. boto3_assist/s3/s3_bucket.py +67 -0
  45. boto3_assist/s3/s3_connection.py +76 -0
  46. boto3_assist/s3/s3_event_data.py +168 -0
  47. boto3_assist/s3/s3_object.py +695 -0
  48. boto3_assist/securityhub/securityhub.py +150 -0
  49. boto3_assist/securityhub/securityhub_connection.py +57 -0
  50. boto3_assist/session_setup_mixin.py +70 -0
  51. boto3_assist/ssm/connection.py +57 -0
  52. boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
  53. boto3_assist/utilities/datetime_utility.py +349 -0
  54. boto3_assist/utilities/decimal_conversion_utility.py +140 -0
  55. boto3_assist/utilities/dictionary_utility.py +32 -0
  56. boto3_assist/utilities/file_operations.py +135 -0
  57. boto3_assist/utilities/http_utility.py +48 -0
  58. boto3_assist/utilities/logging_utility.py +0 -0
  59. boto3_assist/utilities/numbers_utility.py +329 -0
  60. boto3_assist/utilities/serialization_utility.py +664 -0
  61. boto3_assist/utilities/string_utility.py +337 -0
  62. boto3_assist/version.py +1 -0
  63. boto3_assist-0.32.0.dist-info/METADATA +76 -0
  64. boto3_assist-0.32.0.dist-info/RECORD +67 -0
  65. boto3_assist-0.32.0.dist-info/WHEEL +4 -0
  66. boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
  67. boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,150 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import os
8
+ from typing import Optional, Literal
9
+
10
+ from aws_lambda_powertools import Logger
11
+
12
+ from boto3_assist.securityhub.securityhub_connection import SecurityHubConnection
13
+
14
+ logger = Logger("security-hub-service")
15
+
16
+
17
+ class SecurityHub(SecurityHubConnection):
18
+ """Security Hub Service"""
19
+
20
+ def update_findings_status(
21
+ self,
22
+ region_name: str,
23
+ workflow_status: Literal["NEW", "NOTIFIED", "RESOLVED", "SUPPRESSED"],
24
+ note_text: Optional[str] = None,
25
+ updated_by: Optional[str] = None,
26
+ ):
27
+ """
28
+ Updates the workflow status for all findings in the specified region.
29
+
30
+ :param region_name: AWS region where Security Hub findings are located.
31
+ :param workflow_status: The new workflow status to apply (e.g., NEW, NOTIFIED, SUPPRESSED, RESOLVED).
32
+ """
33
+ # Initialize Security Hub client
34
+ client = self.client
35
+
36
+ findings_to_update = []
37
+ next_token = None
38
+
39
+ logger.info(f"Fetching findings in region: {region_name}...")
40
+
41
+ try:
42
+ # Paginate through findings
43
+ while True:
44
+ response = client.get_findings(
45
+ MaxResults=100,
46
+ Filters={
47
+ "Region": [
48
+ {"Value": region_name, "Comparison": "EQUALS"},
49
+ ]
50
+ },
51
+ NextToken=next_token if next_token else "",
52
+ )
53
+
54
+ for finding in response.get("Findings", []):
55
+ current_status = finding.get("Workflow", {}).get("Status")
56
+ if (
57
+ current_status
58
+ and str(current_status).lower() != str(workflow_status).lower()
59
+ ):
60
+ findings_to_update.append(
61
+ {
62
+ "Id": finding["Id"],
63
+ "ProductArn": finding["ProductArn"],
64
+ }
65
+ )
66
+ else:
67
+ logger.debug(
68
+ f"Skipping: {finding['Id']} with a status of {current_status}"
69
+ )
70
+
71
+ next_token = response.get("NextToken")
72
+ if not next_token:
73
+ break
74
+
75
+ print(f"Found {len(findings_to_update)} findings to update.")
76
+
77
+ note_text = note_text or "Automated Update"
78
+ updated_by = updated_by or "System"
79
+ # Update workflow status in batches of 100
80
+ for i in range(0, len(findings_to_update), 100):
81
+ batch = findings_to_update[i : i + 100]
82
+ response = client.batch_update_findings(
83
+ FindingIdentifiers=batch,
84
+ Workflow={"Status": str(workflow_status).upper()},
85
+ Note={"Text": note_text, "UpdatedBy": updated_by},
86
+ )
87
+ logger.debug(
88
+ f"Updated findings {i + 1} to {i + len(batch)} to status: {workflow_status}"
89
+ )
90
+ print(response)
91
+
92
+ logger.info("All findings updated successfully!")
93
+
94
+ except Exception as e:
95
+ logger.exception(f"An error occurred: {str(e)}")
96
+ raise
97
+
98
+
99
+ def main():
100
+ status = "RESOLVED" # Change to NEW, NOTIFIED, SUPPRESSED, or RESOLVED
101
+ note_text = "This region is now disabled."
102
+ # these are my linked regions and the only ones I care about
103
+ # if have SCP's in place for the other regions
104
+ regions_to_skip = ["us-east-1", "us-west-2", "eu-west-2"]
105
+
106
+ aws_profile = os.getenv("SECURITY_HUB_PROFILE")
107
+
108
+ aws_regions = {
109
+ "af-south-1": "Africa (Cape Town)",
110
+ "ap-east-1": "Asia Pacific (Hong Kong)",
111
+ "ap-northeast-1": "Asia Pacific (Tokyo)",
112
+ "ap-northeast-2": "Asia Pacific (Seoul)",
113
+ "ap-northeast-3": "Asia Pacific (Osaka)",
114
+ "ap-south-1": "Asia Pacific (Mumbai)",
115
+ "ap-south-2": "Asia Pacific (Hyderabad)",
116
+ "ap-southeast-1": "Asia Pacific (Singapore)",
117
+ "ap-southeast-2": "Asia Pacific (Sydney)",
118
+ "ap-southeast-3": "Asia Pacific (Jakarta)",
119
+ "ap-southeast-4": "Asia Pacific (Melbourne)",
120
+ "ap-southeast-5": "Asia Pacific (Auckland)",
121
+ "ca-central-1": "Canada (Central)",
122
+ "ca-west-1": "Canada (West)",
123
+ "eu-central-1": "Europe (Frankfurt)",
124
+ "eu-central-2": "Europe (Zurich)",
125
+ "eu-north-1": "Europe (Stockholm)",
126
+ "eu-south-1": "Europe (Milan)",
127
+ "eu-south-2": "Europe (Spain)",
128
+ "eu-west-1": "Europe (Ireland)",
129
+ "eu-west-2": "Europe (London)",
130
+ "eu-west-3": "Europe (Paris)",
131
+ "il-central-1": "Israel (Tel Aviv)",
132
+ "me-central-1": "Middle East (UAE)",
133
+ "sa-east-1": "South America (São Paulo)",
134
+ "us-east-1": "US East (N. Virginia)",
135
+ "us-east-2": "US East (Ohio)",
136
+ "us-west-1": "US West (N. California)",
137
+ "us-west-2": "US West (Oregon)",
138
+ }
139
+
140
+ sh: SecurityHub = SecurityHub(aws_profile=aws_profile)
141
+ for region in aws_regions:
142
+ print(region)
143
+ if region not in regions_to_skip:
144
+ sh.update_findings_status(region, status, note_text=note_text)
145
+ else:
146
+ print(f"Skipping region: {region}")
147
+
148
+
149
+ if __name__ == "__main__":
150
+ main()
@@ -0,0 +1,57 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import Optional
8
+ from typing import TYPE_CHECKING
9
+
10
+ from aws_lambda_powertools import Logger
11
+
12
+ from boto3_assist.connection import Connection
13
+
14
+ if TYPE_CHECKING:
15
+ from mypy_boto3_securityhub import SecurityHubClient
16
+ else:
17
+ SecurityHubClient = object
18
+
19
+
20
+ logger = Logger()
21
+
22
+
23
+ class SecurityHubConnection(Connection):
24
+ """Connection"""
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ aws_profile: Optional[str] = None,
30
+ aws_region: Optional[str] = None,
31
+ aws_end_point_url: Optional[str] = None,
32
+ aws_access_key_id: Optional[str] = None,
33
+ aws_secret_access_key: Optional[str] = None,
34
+ ) -> None:
35
+ super().__init__(
36
+ service_name="securityhub",
37
+ aws_profile=aws_profile,
38
+ aws_region=aws_region,
39
+ aws_access_key_id=aws_access_key_id,
40
+ aws_secret_access_key=aws_secret_access_key,
41
+ aws_end_point_url=aws_end_point_url,
42
+ )
43
+
44
+ self.__client: SecurityHubClient | None = None
45
+
46
+ @property
47
+ def client(self) -> SecurityHubClient:
48
+ """Client Connection"""
49
+ if self.__client is None:
50
+ self.__client = self.session.client
51
+
52
+ return self.__client
53
+
54
+ @client.setter
55
+ def client(self, value: SecurityHubClient):
56
+ logger.info("Setting Client")
57
+ self.__client = value
@@ -0,0 +1,70 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import os
8
+ import boto3
9
+ from typing import Optional
10
+ from boto3_assist.aws_config import AWSConfig
11
+
12
+
13
+ class SessionSetupMixin:
14
+ def _create_base_session(
15
+ self,
16
+ aws_profile: Optional[str],
17
+ aws_region: Optional[str],
18
+ aws_access_key_id: Optional[str],
19
+ aws_secret_access_key: Optional[str],
20
+ aws_session_token: Optional[str],
21
+ ) -> boto3.Session:
22
+ try:
23
+ return boto3.Session(
24
+ profile_name=aws_profile,
25
+ region_name=aws_region,
26
+ aws_access_key_id=aws_access_key_id,
27
+ aws_secret_access_key=aws_secret_access_key,
28
+ aws_session_token=aws_session_token,
29
+ )
30
+ except Exception as e:
31
+
32
+ error_message = f"Failed to create boto3 session "
33
+ if "profile" in str(e).lower():
34
+ error_message += " due to a profile error "
35
+
36
+ error_message += f" with profile '{aws_profile}'."
37
+ config: AWSConfig = AWSConfig()
38
+ if not config.path_exists():
39
+ error_message += (
40
+ f" The AWS config file '{config.get_path()}' was not found. "
41
+ "Please ensure that the AWS CLI is installed and configured correctly. "
42
+ "You can install the AWS CLI by running 'pip install awscli' or 'pip install awscli --upgrade'. "
43
+ )
44
+
45
+ if (
46
+ os.getenv("HOME") == "/tmp"
47
+ or os.getenv("AWS_CONFIG_FILE") == "/tmp"
48
+ ):
49
+ error_message += (
50
+ f'The environment HOME path is set to {os.getenv("HOME")}. '
51
+ )
52
+ if os.getenv("AWS_CONFIG_FILE"):
53
+ error_message += (
54
+ f"The environment AWS_CONFIG_FILE is set to {os.getenv('AWS_CONFIG_FILE')}. "
55
+ "The AWS_CONFIG_FILE overrides the HOME directory and path. "
56
+ )
57
+
58
+ error_message += (
59
+ "If you are running this locally and expecting it to be in your users home "
60
+ "directory, you may need to set the HOME or AWS_CONFIG_FILE environment variable manually. "
61
+ "There could be other actions such as a Lambda environment resetting the path to /tmp. "
62
+ "If you are running in a GitHub Actions environment, "
63
+ "you may need to set the HOME or AWS_CONFIG_FILE environment variable to '/home/runner'. "
64
+ )
65
+ elif not config.has_profile(aws_profile):
66
+ error_message += f" The profile '{aws_profile}' was not found in the AWS config file in {config.get_path()}."
67
+
68
+ # check for the existence of the profile and the path to the profile
69
+
70
+ raise RuntimeError(error_message) from e
@@ -0,0 +1,57 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import Optional
8
+ from typing import TYPE_CHECKING
9
+
10
+ from aws_lambda_powertools import Logger
11
+
12
+ from boto3_assist.connection import Connection
13
+
14
+ if TYPE_CHECKING:
15
+ from mypy_boto3_ssm import Client
16
+ else:
17
+ Client = object
18
+
19
+
20
+ logger = Logger()
21
+
22
+
23
+ class SSMConnection(Connection):
24
+ """Connection"""
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ aws_profile: Optional[str] = None,
30
+ aws_region: Optional[str] = None,
31
+ aws_end_point_url: Optional[str] = None,
32
+ aws_access_key_id: Optional[str] = None,
33
+ aws_secret_access_key: Optional[str] = None,
34
+ ) -> None:
35
+ super().__init__(
36
+ service_name="ssm",
37
+ aws_profile=aws_profile,
38
+ aws_region=aws_region,
39
+ aws_access_key_id=aws_access_key_id,
40
+ aws_secret_access_key=aws_secret_access_key,
41
+ aws_end_point_url=aws_end_point_url,
42
+ )
43
+
44
+ self.__client: Client | None = None
45
+
46
+ @property
47
+ def client(self) -> Client:
48
+ """Client Connection"""
49
+ if self.__client is None:
50
+ self.__client = self.session.client
51
+
52
+ return self.__client
53
+
54
+ @client.setter
55
+ def client(self, value: Client):
56
+ logger.info("Setting Client")
57
+ self.__client = value
@@ -0,0 +1,116 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import Optional, Literal
8
+
9
+ from botocore.exceptions import ClientError
10
+ from boto3_assist.ssm.connection import SSMConnection
11
+
12
+
13
+ class ParameterStore(SSMConnection):
14
+ """Parameter Store"""
15
+
16
+ def get_parameter(self, name: str, with_decryption=True):
17
+ """
18
+ Retrieve a parameter from Parameter Store.
19
+
20
+ :param name: The full name of the parameter.
21
+ :param with_decryption: If True, decrypt secure strings.
22
+ :return: The parameter value or None if an error occurs.
23
+ """
24
+ try:
25
+ response = self.client.get_parameter(
26
+ Name=name, WithDecryption=with_decryption
27
+ )
28
+ return response["Parameter"]["Value"]
29
+ except ClientError as e:
30
+ print(f"Error getting parameter {name}: {e}")
31
+ raise
32
+
33
+ def put_parameter(
34
+ self,
35
+ name: str,
36
+ value: str,
37
+ type: Literal["String", "StringList", "SecureString"] = "String", # pylint: disable=redefined-builtin
38
+ overwrite=True,
39
+ ):
40
+ """
41
+ Create or update a parameter in Parameter Store.
42
+
43
+ :param name: The full name of the parameter.
44
+ :param value: The value to store.
45
+ :param type: Parameter type ('String', 'StringList', or 'SecureString').
46
+ :param overwrite: If True, overwrite an existing parameter.
47
+ :return: The response from the put_parameter call or None on error.
48
+ """
49
+ try:
50
+ response = self.client.put_parameter(
51
+ Name=name, Value=value, Type=type, Overwrite=overwrite
52
+ )
53
+ return response
54
+ except ClientError as e:
55
+ print(f"Error putting parameter {name}: {e}")
56
+ raise
57
+
58
+ def delete_parameter(self, name: str):
59
+ """
60
+ Delete a parameter from Parameter Store.
61
+
62
+ :param name: The full name of the parameter.
63
+ :return: The response from the delete_parameter call or None on error.
64
+ """
65
+ try:
66
+ response = self.client.delete_parameter(Name=name)
67
+ return response
68
+ except ClientError as e:
69
+ print(f"Error deleting parameter {name}: {e}")
70
+ raise
71
+
72
+ def list_parameters(self, path: str = "/", recursive=True):
73
+ """
74
+ List parameters in a given path.
75
+
76
+ :param path: The hierarchical path for the parameters.
77
+ :param recursive: If True, retrieve parameters recursively.
78
+ :return: A list of parameter metadata dictionaries.
79
+ """
80
+ try:
81
+ paginator = self.client.get_paginator("describe_parameters")
82
+ parameters = []
83
+ filters = [
84
+ {
85
+ "Key": "Path",
86
+ "Option": "Recursive" if recursive else "OneLevel",
87
+ "Values": [path],
88
+ }
89
+ ]
90
+ for page in paginator.paginate(ParameterFilters=filters):
91
+ parameters.extend(page.get("Parameters", []))
92
+ return parameters
93
+ except ClientError as e:
94
+ print(f"Error listing parameters for path {path}: {e}")
95
+ raise
96
+
97
+
98
+ # Example usage:
99
+ if __name__ == "__main__":
100
+ # Initialize the ParameterStore class.
101
+ ssm = ParameterStore()
102
+ # Example: Put a parameter.
103
+ put_response = ssm.put_parameter("/myapp/AccountNumber", "123456789012")
104
+ print("Put parameter response:", put_response)
105
+
106
+ # Example: Get a parameter.
107
+ account_number = ssm.get_parameter("/myapp/AccountNumber")
108
+ print("Account Number:", account_number)
109
+
110
+ # Example: List parameters under /myapp.
111
+ params = ssm.list_parameters(path="/myapp")
112
+ print("Parameters under /myapp:", params)
113
+
114
+ # Example: Delete a parameter.
115
+ delete_response = ssm.delete_parameter("/myapp/AccountNumber")
116
+ print("Delete parameter response:", delete_response)