Skip to content

Commit 7e31e4e

Browse files
committed
/deploy sit
1 parent 1e034eb commit 7e31e4e

2 files changed

Lines changed: 796 additions & 380 deletions

File tree

podaac/lambda_handler/lambda_handler.py

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from shutil import rmtree
88
import multiprocessing as mp
99
import traceback
10+
import fnmatch
1011
import requests
1112

13+
import boto3
1214
import botocore
1315
from cumulus_logger import CumulusLogger
1416
from cumulus_process import Process, s3
@@ -116,8 +118,36 @@ def download_file_from_s3(self, s3file, working_dir):
116118
str
117119
full path of the downloaded file
118120
"""
121+
# Extract bucket name from S3 URI
122+
bucket_name = s3file.split('/')[2] # s3://bucket-name/path/to/file
123+
124+
# Check if we need to assume a role for this bucket
125+
role_arn = self._get_role_for_bucket(bucket_name)
126+
119127
try:
128+
if role_arn:
129+
self.logger.info(f"Assuming role {role_arn} for download from bucket {bucket_name}")
130+
credentials = self._assume_role(role_arn)
131+
132+
# Create a new S3 client with assumed role credentials
133+
s3_client = boto3.client(
134+
's3',
135+
aws_access_key_id=credentials['aws_access_key_id'],
136+
aws_secret_access_key=credentials['aws_secret_access_key'],
137+
aws_session_token=credentials['aws_session_token']
138+
)
139+
140+
# Extract key from S3 URI
141+
key = '/'.join(s3file.split('/')[3:]) # path/to/file
142+
filename = os.path.basename(key)
143+
local_path = os.path.join(working_dir, filename)
144+
145+
# Download using the custom client
146+
s3_client.download_file(bucket_name, key, local_path)
147+
return local_path
148+
120149
return s3.download(s3file, working_dir)
150+
121151
except botocore.exceptions.ClientError as ex:
122152
self.logger.error("Error downloading file %s: %s" % (s3file, working_dir), exc_info=True)
123153
raise ex
@@ -132,10 +162,41 @@ def upload_file_to_s3(self, filename, uri):
132162
uri: str
133163
s3 string of file location
134164
"""
165+
# Extract bucket name from S3 URI
166+
bucket_name = uri.split('/')[2] # s3://bucket-name/path/to/file
167+
168+
# Check if we need to assume a role for this bucket
169+
role_arn = self._get_role_for_bucket(bucket_name)
170+
135171
try:
172+
if role_arn:
173+
self.logger.info(f"Assuming role {role_arn} for upload to bucket {bucket_name}")
174+
credentials = self._assume_role(role_arn)
175+
176+
# Create a new S3 client with assumed role credentials
177+
s3_client = boto3.client(
178+
's3',
179+
aws_access_key_id=credentials['aws_access_key_id'],
180+
aws_secret_access_key=credentials['aws_secret_access_key'],
181+
aws_session_token=credentials['aws_session_token']
182+
)
183+
184+
# Extract key from S3 URI
185+
key = '/'.join(uri.split('/')[3:]) # path/to/file
186+
187+
# Upload using the custom client
188+
s3_client.upload_file(
189+
filename,
190+
bucket_name,
191+
key,
192+
ExtraArgs={"ACL": "bucket-owner-full-control"}
193+
)
194+
return uri
195+
136196
return s3.upload(filename, uri, extra={"ACL": "bucket-owner-full-control"})
197+
137198
except botocore.exceptions.ClientError as ex:
138-
self.logger.error("Error uploading file %s: %s" % (os.path.basename(os.path.basename(filename)), str(ex)), exc_info=True)
199+
self.logger.error("Error uploading file %s: %s" % (os.path.basename(filename), str(ex)), exc_info=True)
139200
raise ex
140201

141202
@staticmethod
@@ -366,11 +427,147 @@ def _is_valid_input(self, file_):
366427
data_type = file_['type']
367428
return re.match(self.processing_regex, input_file) or data_type == "data"
368429

430+
def _get_role_for_bucket(self, bucket_name):
431+
"""Get the appropriate role to assume for a given bucket.
432+
433+
The role mappings should be configured in self.config['role_mappings'] as:
434+
{
435+
"exact-bucket-name": "arn:aws:iam::123456789012:role/MyRole",
436+
"bucket-prefix-*": "arn:aws:iam::123456789012:role/PrefixRole",
437+
"*bucket-suffix": "arn:aws:iam::123456789012:role/SuffixRole",
438+
"bucket-*pattern*": "arn:aws:iam::123456789012:role/PatternRole",
439+
"regex-pattern": "arn:aws:iam::123456789012:role/RegexRole"
440+
}
441+
442+
Supports exact matches, wildcard patterns, and regular expression matching.
443+
444+
Parameters
445+
----------
446+
bucket_name: str
447+
Name of the S3 bucket
448+
449+
Returns
450+
-------
451+
str or None
452+
Role ARN to assume, or None if no role is needed
453+
"""
454+
role_mappings = self.config.get('role_mappings', {})
455+
456+
# Check for exact bucket match first (fastest)
457+
if bucket_name in role_mappings:
458+
return role_mappings[bucket_name]
459+
460+
# Check pattern matches
461+
for pattern, role in role_mappings.items():
462+
# Skip exact matches (already checked above)
463+
if pattern == bucket_name:
464+
continue
465+
466+
# Handle regex patterns
467+
if self._is_regex_pattern(pattern):
468+
try:
469+
if re.match(pattern, bucket_name):
470+
return role
471+
except re.error as ex:
472+
self.logger.warning(f"Invalid regex pattern '{pattern}': {ex}")
473+
continue
474+
# Handle simple wildcard patterns
475+
elif pattern.endswith('*'):
476+
prefix = pattern[:-1]
477+
if bucket_name.startswith(prefix):
478+
return role
479+
elif pattern.startswith('*'):
480+
suffix = pattern[1:]
481+
if bucket_name.endswith(suffix):
482+
return role
483+
elif '*' in pattern:
484+
# Handle complex wildcard patterns
485+
if fnmatch.fnmatch(bucket_name, pattern):
486+
return role
487+
488+
return None
489+
490+
def _is_regex_pattern(self, pattern):
491+
"""Check if a pattern is a regex pattern for optimization.
492+
493+
Parameters
494+
----------
495+
pattern: str
496+
Pattern to check
497+
498+
Returns
499+
-------
500+
bool
501+
True if pattern is a regex pattern
502+
"""
503+
# Quick checks for common regex indicators
504+
return (pattern.startswith('^') or
505+
pattern.endswith('$') or
506+
'(' in pattern or
507+
'|' in pattern or
508+
'[' in pattern or
509+
'\\' in pattern)
510+
511+
def _assume_role(self, role_arn):
512+
"""Assume an IAM role and return credentials.
513+
514+
Parameters
515+
----------
516+
role_arn: str
517+
ARN of the role to assume
518+
519+
Returns
520+
-------
521+
dict
522+
Credentials dictionary with access_key, secret_key, and token
523+
"""
524+
try:
525+
sts_client = boto3.client('sts')
526+
response = sts_client.assume_role(
527+
RoleArn=role_arn,
528+
RoleSessionName='ImageGeneratorSession'
529+
)
530+
531+
credentials = response['Credentials']
532+
return {
533+
'aws_access_key_id': credentials['AccessKeyId'],
534+
'aws_secret_access_key': credentials['SecretAccessKey'],
535+
'aws_session_token': credentials['SessionToken']
536+
}
537+
except Exception as ex:
538+
self.logger.error(f"Error assuming role {role_arn}: {ex}", exc_info=True)
539+
raise
540+
369541
def _download_file(self, file_):
370542
"""Download the input file from S3."""
371543
input_file = f's3://{file_["bucket"]}/{file_["key"]}'
544+
545+
# Check if we need to assume a role for this bucket
546+
role_arn = self._get_role_for_bucket(file_["bucket"])
547+
372548
try:
549+
if role_arn:
550+
self.logger.info(f"Assuming role {role_arn} for bucket {file_['bucket']}")
551+
credentials = self._assume_role(role_arn)
552+
553+
# Create a new S3 client with assumed role credentials
554+
s3_client = boto3.client(
555+
's3',
556+
aws_access_key_id=credentials['aws_access_key_id'],
557+
aws_secret_access_key=credentials['aws_secret_access_key'],
558+
aws_session_token=credentials['aws_session_token']
559+
)
560+
561+
# Download using the custom client
562+
bucket = file_["bucket"]
563+
key = file_["key"]
564+
local_path = os.path.join(self.path, os.path.basename(key))
565+
566+
s3_client.download_file(bucket, key, local_path)
567+
return local_path
568+
373569
return s3.download(input_file, path=self.path)
570+
374571
except botocore.exceptions.ClientError as ex:
375572
self.logger.error("Error downloading file from S3: {}".format(ex), exc_info=True)
376573
raise

0 commit comments

Comments
 (0)