Skip to content

Commit c8bcefd

Browse files
committed
Merge dev into main
2 parents 837142a + ebad4b1 commit c8bcefd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+949
-392
lines changed

.github/workflows/ci-pipeline__dev.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424

2525
- name: Start Local Stack
2626
uses: owasp-sbot/OSBot-GitHub-Actions/.github/actions/docker__local-stack@dev
27+
with:
28+
LOCAL_STACK_SERVICES: 's3,lambda,iam,cloudwatch,dynamodb,logs,sts,ec2'
2729

2830
- name: Poetry - Install Dependencies
2931
uses: owasp-sbot/OSBot-GitHub-Actions/.github/actions/poetry__install@dev

.github/workflows/ci-pipeline__main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ jobs:
8080

8181

8282
publish-to-docker-hub:
83+
if: False
8384
runs-on: ubuntu-latest
8485

8586
steps:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OSBot-AWS
22

3-
![Current Release](https://img.shields.io/badge/release-v2.37.0-blue)
3+
![Current Release](https://img.shields.io/badge/release-v2.37.23-blue)
44

55
Large number of AWS apis and utils from the OWASP Security Bot (OSBot) which make
66
the use of AWS's boto3 library easier and more intuitive.

osbot_aws/AWS_Config.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import os
2-
from osbot_utils.base_classes.Type_Safe import Type_Safe
3-
from osbot_utils.utils.Env import load_dotenv
1+
from osbot_utils.type_safe.Type_Safe import Type_Safe
42

53
DEFAULT__AWS_DEFAULT_REGION = 'eu-west-1'
64

@@ -12,6 +10,8 @@
1210
class AWS_Config(Type_Safe):
1311

1412
def __init__(self):
13+
from osbot_utils.utils.Env import load_dotenv
14+
1515
super().__init__()
1616
load_dotenv()
1717

@@ -21,29 +21,29 @@ def __enter__(self):
2121
def __exit__(self, type, value, traceback):
2222
pass
2323

24-
def aws_access_key_id (self): return os.getenv('AWS_ACCESS_KEY_ID' )
25-
def aws_secret_access_key (self): return os.getenv('AWS_SECRET_ACCESS_KEY' )
26-
def aws_endpoint_url (self): return os.getenv(ENV_NAME__AWS_ENDPOINT_URL )
27-
def aws_session_profile_name (self): return os.getenv('AWS_PROFILE_NAME' )
28-
def aws_session_region_name (self): return os.getenv(ENV_NAME__AWS_DEFAULT_REGION ) or DEFAULT__AWS_DEFAULT_REGION
29-
def aws_session_account_id (self): return os.getenv(ENV_NAME__AWS_ACCOUNT_ID ) or self.sts__session_account_id()
30-
31-
def dev_skip_aws_key_check (self): return os.getenv('DEV_SKIP_AWS_KEY_CHECK' , False ) # use to not have the 500ms check that happens during this check
32-
def bot_name (self): return os.getenv('OSBOT_NAME' ) # todo: refactor variable to osbot_name (need to check for side effects)
33-
def lambda_s3_folder_layers (self): return os.getenv('OSBOT_LAMBDA_S3_FOLDER_LAYERS' , 'layers' ) # todo: change these static values to DEFAULT_.... ones
34-
def lambda_s3_folder_lambdas (self): return os.getenv('OSBOT_LAMBDA_S3_FOLDER_LAMBDAS', 'lambdas' )
35-
def lambda_role_name (self): return os.getenv('OSBOT_LAMBDA_ROLE_NAME' 'role-osbot-lambda')
36-
37-
def set_aws_access_key_id (self, value): os.environ['AWS_ACCESS_KEY_ID' ] = value ; return value
38-
def set_aws_secret_access_key (self, value): os.environ['AWS_SECRET_ACCESS_KEY' ] = value ; return value
39-
def set_aws_session_profile_name(self, value): os.environ['AWS_PROFILE_NAME' ] = value ; return value
40-
def set_aws_session_region_name (self, value): os.environ[ENV_NAME__AWS_DEFAULT_REGION ] = value ; return value
41-
def set_aws_session_account_id (self, value): os.environ[ENV_NAME__AWS_ACCOUNT_ID ] = value ; return value
42-
def set_lambda_s3_bucket (self, value): os.environ['OSBOT_LAMBDA_S3_BUCKET' ] = value ; return value
43-
def set_lambda_s3_folder_layers (self, value): os.environ['OSBOT_LAMBDA_S3_FOLDER_LAYERS' ] = value ; return value
44-
def set_lambda_s3_folder_lambdas(self, value): os.environ['OSBOT_LAMBDA_S3_FOLDER_LAMBDAS' ] = value ; return value
45-
def set_lambda_role_name (self, value): os.environ['OSBOT_LAMBDA_ROLE_NAME' ] = value ; return value
46-
def set_bot_name (self, value): os.environ['OSBOT_NAME' ] = value ; return value
24+
def aws_access_key_id (self): import os; return os.getenv('AWS_ACCESS_KEY_ID' )
25+
def aws_secret_access_key (self): import os; return os.getenv('AWS_SECRET_ACCESS_KEY' )
26+
def aws_endpoint_url (self): import os; return os.getenv(ENV_NAME__AWS_ENDPOINT_URL )
27+
def aws_session_profile_name (self): import os; return os.getenv('AWS_PROFILE_NAME' )
28+
def aws_session_region_name (self): import os; return os.getenv(ENV_NAME__AWS_DEFAULT_REGION ) or DEFAULT__AWS_DEFAULT_REGION
29+
def aws_session_account_id (self): import os; return os.getenv(ENV_NAME__AWS_ACCOUNT_ID ) or self.sts__session_account_id()
30+
31+
def dev_skip_aws_key_check (self): import os; return os.getenv('DEV_SKIP_AWS_KEY_CHECK' , False ) # use to not have the 500ms check that happens during this check
32+
def bot_name (self): import os; return os.getenv('OSBOT_NAME' ) # todo: refactor variable to osbot_name (need to check for side effects)
33+
def lambda_s3_folder_layers (self): import os; return os.getenv('OSBOT_LAMBDA_S3_FOLDER_LAYERS' , 'layers' ) # todo: change these static values to DEFAULT_.... ones
34+
def lambda_s3_folder_lambdas (self): import os; return os.getenv('OSBOT_LAMBDA_S3_FOLDER_LAMBDAS', 'lambdas' )
35+
def lambda_role_name (self): import os; return os.getenv('OSBOT_LAMBDA_ROLE_NAME' 'role-osbot-lambda')
36+
37+
def set_aws_access_key_id (self, value): import os; os.environ['AWS_ACCESS_KEY_ID' ] = value ; return value
38+
def set_aws_secret_access_key (self, value): import os; os.environ['AWS_SECRET_ACCESS_KEY' ] = value ; return value
39+
def set_aws_session_profile_name(self, value): import os; os.environ['AWS_PROFILE_NAME' ] = value ; return value
40+
def set_aws_session_region_name (self, value): import os; os.environ[ENV_NAME__AWS_DEFAULT_REGION ] = value ; return value
41+
def set_aws_session_account_id (self, value): import os; os.environ[ENV_NAME__AWS_ACCOUNT_ID ] = value ; return value
42+
def set_lambda_s3_bucket (self, value): import os; os.environ['OSBOT_LAMBDA_S3_BUCKET' ] = value ; return value
43+
def set_lambda_s3_folder_layers (self, value): import os; os.environ['OSBOT_LAMBDA_S3_FOLDER_LAYERS' ] = value ; return value
44+
def set_lambda_s3_folder_lambdas(self, value): import os; os.environ['OSBOT_LAMBDA_S3_FOLDER_LAMBDAS' ] = value ; return value
45+
def set_lambda_role_name (self, value): import os; os.environ['OSBOT_LAMBDA_ROLE_NAME' ] = value ; return value
46+
def set_bot_name (self, value): import os; os.environ['OSBOT_NAME' ] = value ; return value
4747

4848
def sts__session_account_id(self): # to handle when the AWS_ACCOUNT_ID is not set
4949
#if self.aws_configured(): # todo: find an efficient way to handle this since this doesn't work when a role is attached to a lambda function or EC2 instance
@@ -73,12 +73,15 @@ def region_name(self):
7373
return self.aws_session_region_name()
7474

7575
def resolve_lambda_bucket_name(self):
76+
import os
7677
bucket_name = os.getenv('OSBOT_LAMBDA_S3_BUCKET')
7778
if bucket_name is None:
7879
bucket_name = f'{self.aws_session_account_id()}--osbot-lambdas--{self.region_name()}' # this is a needed breaking change
7980
return bucket_name
8081

8182
def resolve_temp_data_bucket_name(self):
83+
import os
84+
8285
bucket_name = os.getenv('OSBOT_TEMP_DATA_S3_BUCKET')
8386
if bucket_name is None:
8487
bucket_name = f'{self.aws_session_account_id()}--temp-data--{self.region_name()}'

osbot_aws/apis/Session.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from osbot_aws.exceptions.Session_Client_Creation_Fail import Session_Client_Creation_Fail
1212
from osbot_aws.exceptions.Session_No_Credentials import Session_No_Credentials
1313

14-
#ENV_NAME__OSBOT_AWS__SESSION__SET_SIGNATURE_VERSION = 'OSBOT_AWS__SESSION__SET_SIGNATURE_VERSION' # todo: see if this needed (see comments in default_config method)
1514

1615
class Session(Kwargs_To_Self): # todo: refactor to AWS_Session so it doesn't clash with boto3.Session object
1716
account_id = None
@@ -26,6 +25,7 @@ def boto_session(self) -> boto3.Session:
2625

2726
@cache
2827
def botocore_session(self, aws_access_key_id=None , aws_secret_access_key=None , aws_session_token= None, region_name= None, botocore_session=None, profile_name=None ):
28+
import boto3
2929
kwargs = { "aws_access_key_id" : aws_access_key_id ,
3030
"aws_secret_access_key" : aws_secret_access_key ,
3131
"aws_session_token" : aws_session_token ,
@@ -67,6 +67,7 @@ def caller_identity(self, session=None):
6767

6868
@cache_on_self
6969
def session(self) -> boto3.Session:
70+
import boto3
7071
return boto3.Session()
7172

7273
def session_default(self):
@@ -81,12 +82,12 @@ def client(self, service_name, **kwargs):
8182
raise Session_Client_Creation_Fail(status=status)
8283

8384
def client_boto3(self,service_name, aws_access_key_id=None, aws_secret_access_key=None, region_name=None, config=None, endpoint_url=None, profile_name=None): # todo: refactor add this to resource_boto3
85+
import boto3
8486
try:
8587
self.config = config or self.config or self.default_config ()
8688
self.region_name = region_name or self.region_name or aws_config.aws_session_region_name ()
8789
self.profile_name = profile_name or self.profile_name or aws_config.aws_session_profile_name()
88-
self.endpoint_url = endpoint_url or self.endpoint_url or aws_config.aws_endpoint_url()
89-
90+
self.endpoint_url = endpoint_url or self.endpoint_url or self.resolve_endpoint_url_for_service(service_name)
9091

9192
client_kwargs = dict( service_name = service_name , config = self.config)
9293

@@ -128,10 +129,25 @@ def profiles(self):
128129

129130
def profiles_map(self):
130131
return get_session()._build_profile_map()
131-
132132
#return self.boto_session()._build_profile_map()
133133

134+
def resolve_endpoint_url_for_service(self, service): # todo: find a better way to do this mapping to the local_stack services that are supported
135+
supported_local_stack_services = ['s3', 'lambda','logs'] # for now only these 3 services have been tested with local stack , all others will ahve the default
136+
endpoint_url = aws_config.aws_endpoint_url()
137+
if endpoint_url == 'http://localhost:4566':
138+
if service in supported_local_stack_services:
139+
return endpoint_url
140+
else:
141+
region = aws_config.aws_session_region_name()
142+
if region:
143+
return f'https://{service}.{region}.amazonaws.com'
144+
else:
145+
return None
146+
else:
147+
return endpoint_url
148+
134149
def resource_boto3(self,service_name, profile_name=None, region_name=None): # todo: refactor with client_boto3
150+
import boto3
135151
try:
136152
profile_name = profile_name or aws_config.aws_session_profile_name()
137153
region_name = region_name or aws_config.aws_session_region_name()
@@ -147,6 +163,7 @@ def resource_boto3(self,service_name, profile_name=None, region_name=None):
147163
def resource(self, service_name,profile_name=None, region_name=None):
148164
return self.resource_boto3(service_name,profile_name,region_name).get('resource')
149165

166+
150167
# client helpers
151168

152169
# putting all these here in global location so that we limit the calls to authenticate
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import requests
2+
from osbot_utils.type_safe.Type_Safe import Type_Safe
3+
from osbot_utils.utils.Dev import pprint
4+
from osbot_aws.apis.shell.Shell_Client import Shell_Client
5+
6+
class Http__Remote_Shell(Type_Safe, Shell_Client):
7+
target_url: str = None
8+
9+
def _invoke(self, method_name, method_kwargs=None, return_logs=False):
10+
auth_key = self._lambda_auth_key()
11+
12+
payload = {'lambda_shell': {'method_name' : method_name ,
13+
'method_kwargs': method_kwargs ,
14+
'auth_key' : auth_key }}
15+
response = requests.post(self.target_url, json=payload)
16+
if response.headers.get('Content-Type') == 'application/json':
17+
return response.json()
18+
return response.text.strip()
19+
20+
def exec_print(self, executable, *params):
21+
result = self.exec(executable, params)
22+
pprint(result)
23+
return result
24+
25+
def function(self, function):
26+
return self.python_exec_function(function)
27+
28+
def function__print(self,function):
29+
result = self.function(function)
30+
pprint(result)
31+
return result
32+
33+
# helper execution methods
34+
35+
def env_vars(self):
36+
def return_dict_environ():
37+
from os import environ
38+
return dict(environ)
39+
return self.function(return_dict_environ)

osbot_aws/aws/boto3/View_Boto3_Rest_Calls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from functools import wraps
22
from botocore.client import BaseClient
33
from osbot_utils.helpers.Print_Table import Print_Table
4+
from osbot_utils.helpers.duration.Duration import Duration
45
from osbot_utils.utils.Dev import pformat
56
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
6-
from osbot_utils.testing.Duration import Duration
77
from osbot_utils.testing.Hook_Method import Hook_Method
88
from osbot_utils.utils.Objects import obj_data
99

osbot_aws/aws/cloud_front/Cloud_Front.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
from osbot_aws.apis.Session import Session
1+
import time
2+
from typing import List
3+
4+
from osbot_utils.type_safe.decorators.type_safe import type_safe
5+
6+
from osbot_aws.apis.Session import Session
7+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
28

3-
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
49

510

611
class Cloud_Front(Kwargs_To_Self):
@@ -14,3 +19,14 @@ def distributions(self):
1419
items = response.get('DistributionList', {}).get('Items', [])
1520
return items
1621

22+
def invalidate_path(self, distribution_id: str, paths: str):
23+
return self.invalidate_paths(distribution_id, [paths])
24+
25+
@type_safe
26+
def invalidate_paths(self, distribution_id:str, paths: List[str]):
27+
kwargs = dict(DistributionId = distribution_id,
28+
InvalidationBatch = { 'Paths' : { 'Quantity': len(paths) ,
29+
'Items' : paths },
30+
'CallerReference': f'invalidation-{int(time.time()) }' }) # Unique reference
31+
response = self.client().create_invalidation(**kwargs)
32+
return response

osbot_aws/aws/dynamo_db/Dynamo_DB.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import uuid
2-
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
3-
from osbot_aws.apis.Session import Session
4-
from osbot_utils.base_classes.Type_Safe import Type_Safe
1+
from osbot_utils.type_safe.Type_Safe import Type_Safe
52
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
63
from osbot_utils.decorators.methods.remove_return_value import remove_return_value
74

@@ -17,10 +14,14 @@ class Dynamo_DB(Type_Safe):
1714
# helpers
1815
@cache_on_self
1916
def client(self):
17+
from osbot_aws.apis.Session import Session
18+
2019
return Session().client(service_name='dynamodb', region_name=self.region_name, endpoint_url=self.endpoint_url)
2120

2221
@cache_on_self
2322
def client__dynamo_streams(self):
23+
from osbot_aws.apis.Session import Session
24+
2425
return Session().client('dynamodbstreams')
2526

2627
# main methods
@@ -43,13 +44,16 @@ def document_delete(self, table_name, key_name, key_value):
4344
return True # so unless there was an exception thrown, assume it did
4445

4546
def document_deserialize(self, item):
47+
from boto3.dynamodb.types import TypeDeserializer
4648
if item:
4749
deserializer = TypeDeserializer()
4850
return {k: deserializer.deserialize(v) for k, v in item.items()}
4951
return {}
5052

5153
@remove_return_value('ResponseMetadata')
5254
def document_update(self, table_name, key_name, key_value, update_data):
55+
from boto3.dynamodb.types import TypeSerializer
56+
5357
# Initialize TypeSerializer to convert Python types to DynamoDB types
5458
serializer = TypeSerializer()
5559

@@ -89,6 +93,8 @@ def document_update(self, table_name, key_name, key_value, update_data):
8993
return response
9094

9195
def document_serialize(self, document):
96+
from boto3.dynamodb.types import TypeSerializer
97+
9298
serializer = TypeSerializer()
9399
return {k: serializer.serialize(v) for k, v in document.items()}
94100

@@ -290,4 +296,5 @@ def streams(self):
290296
return self.client__dynamo_streams().list_streams().get('Streams')
291297

292298
def random_id(self):
299+
import uuid
293300
return str(uuid.uuid4())

osbot_aws/aws/dynamo_db/Dynamo_DB__Table.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import uuid
2-
1+
from osbot_utils.type_safe.Type_Safe import Type_Safe
32
from osbot_aws.aws.dynamo_db.Dynamo_DB import Dynamo_DB
4-
from osbot_aws.aws.dynamo_db.Dynamo_DB__Record import Dynamo_DB__Record
5-
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
63
from osbot_utils.decorators.methods.capture_status import apply_capture_status
7-
from osbot_utils.utils.Misc import timestamp_utc_now, wait_for
4+
85

96
PRIMARY_KEY_NAME = 'id'
107
PRIMARY_KEY_TYPE = 'S'
118

9+
from typing import TYPE_CHECKING
10+
if TYPE_CHECKING:
11+
from osbot_aws.aws.dynamo_db.Dynamo_DB__Record import Dynamo_DB__Record
12+
1213
@apply_capture_status
13-
class Dynamo_DB__Table(Kwargs_To_Self):
14+
class Dynamo_DB__Table(Type_Safe):
1415
dynamo_db : Dynamo_DB
1516
key_name : str
1617
key_type : str
@@ -32,12 +33,13 @@ def add_documents(self, documents):
3233
documents_to_add = []
3334
for document in documents:
3435
if self.key_name not in document: # If key is present,
35-
document = {self.key_name: self.dynamo_db.random_id(), # add it as a generate a random UUID as the key
36+
document = {self.key_name: self.dynamo_db.random_id(), # add random UUID as the key
3637
**document} # to the current document object
3738
documents_to_add.append(document)
3839
return self.dynamo_db.documents_add(table_name=self.table_name, documents=documents_to_add)
3940

40-
def add_record(self, record : Dynamo_DB__Record):
41+
def add_record(self, record : 'Dynamo_DB__Record'):
42+
from osbot_utils.utils.Misc import timestamp_utc_now
4143
metadata = record.metadata
4244
metadata.timestamp_created = timestamp_utc_now()
4345
document = record.serialize_to_dict()
@@ -132,6 +134,8 @@ def gsi_delete(self, index_name):
132134
return self.update_table(gsi_updates=gsi_updates).get('data')
133135

134136
def gsi_wait_for_status(self, status='ACTIVE', max_attempts=20, delay=0.05): # todo: see if these values need to be higher when dealing with AWS DynamoDB (vs the dynamodb-local)
137+
from osbot_utils.utils.Misc import wait_for
138+
135139
for i in range(max_attempts):
136140
all_match = False
137141
for gsi in self.gsis().get('data'):

0 commit comments

Comments
 (0)