def get_object_retry_errors(bucket: str, key: str, error_codes: list = ['NoSuchKey', 'AccessDenied'], retries=5, sleep=3):
    exception = None
    for i in range(retries):
        try:
            return boto_client('s3').get_object(Bucket=bucket, Key=key)
        except ClientError as e:
            exception = e
            if e.response["Error"]["Code"] in error_codes:
                logger.warning(e.response['Error']['Message'])
                time_sleep(sleep + i)
    raise exception
Exemple #2
0
def get_s3_client():
    return boto_client('s3')
Exemple #3
0
from os import urandom
from random import randint
from hashlib import sha256
from json import loads as parse_json
from boto3 import client as boto_client
from botocore.exceptions import ClientError

from .validator import validate_payload
from .constants import KINESIS_REGION, KINESIS_ENABLED, KINESIS_STREAM_NAME, RETRY_SECONDS_INTERVAL_FROM, \
    RETRY_SECONDS_INTERVAL_TO, TIMEOUT_SECONDS_THRESHOLD

RETRY_EXCEPTIONS = [
    'ThrottlingException', 'ProvisionedThroughputExceededException'
]

kinesis_client = boto_client('kinesis', region_name=KINESIS_REGION)


class Processor:
    def __init__(self, event, context, logger):
        self.event = event
        self.context = context
        self.raw_payload = event['body'] if 'body' in event else event
        self.payload = parse_json(self.raw_payload)
        self.logger = logger

    def validate(self):
        validate_payload(self.payload)

    @staticmethod
    def generate_partition_key(event):
def main(event, context):

    from email import message_from_bytes as email_message_from_bytes
    from json import loads as json_loads
    from re import sub as re_sub

    logger.info(f'Event: \n{event}')

    email_bucket = commons.get_env_str('email_bucket')
    email_bucket_prefix = commons.get_env_str('email_bucket_prefix')
    email_forward_rules = json_loads(
        commons.get_env_str('email_forward_rules'))

    logger.debug(f'Rules: \n{email_forward_rules}')
    for record in event.get('Records', []):
        if 'aws:ses' == record['eventSource']:
            source = record['ses']['mail']['source']
            target = record['ses']['receipt']['recipients']
            subject = record['ses']['mail']['commonHeaders']['subject']

            logger.debug(f'Found email from {source} to {target}')
            rule, destination = get_matching_destination(
                email_forward_rules, target)

            if not destination:
                logger.info(f'No rules matching for: {target}')
                continue

            ses = boto_client('ses')

            message_id = record['ses']['mail']['messageId']
            key = get_prefixed_key(email_bucket_prefix, message_id)
            s3_object = get_object_retry_errors(email_bucket, key)

            logger.debug(f'S3Object: \n{s3_object}')
            s3_object_body = s3_object['Body']
            s3_object_meta = s3_object['Metadata']
            content_length = int(s3_object_meta.get(
                'x-amz-unencrypted-content-length', 0)
                or s3_object.get('ContentLength', 0)
            )

            if content_length and content_length > 10485760:
                logger.info(f'The size {content_length} is too large. \
                            Notify to source: {source}')
                ses.send_email(
                    Source=rule,
                    Destination={
                        'ToAddresses': [source]
                    },
                    Message={
                        'Subject': {
                            'Data': subject,
                            'Charset': 'UTF-8'
                        },
                        'Body': {
                            'Text': {
                                'Data': 'Your email was rejected due to the maximum 10mb size constraint',
                                'Charset': 'UTF-8'
                            }
                        }
                    },
                    ReplyToAddresses=[rule],
                    ReturnPath=rule
                )
                continue

            if 'kms' == s3_object_meta.get('x-amz-wrap-alg'):
                from Cryptodome.Cipher import AES
                from base64 import b64decode as base64_b64decode

                logger.debug(f'Decrypt Body')
                kms = boto_client('kms')
                envelope_iv = base64_b64decode(s3_object_meta['x-amz-iv'])

                decrypted_envelope_key = kms.decrypt(
                    CiphertextBlob=base64_b64decode(
                        s3_object_meta['x-amz-key-v2']
                    ),
                    EncryptionContext=json_loads(
                        s3_object_meta['x-amz-matdesc']
                    )
                )

                decryptor = AES.new(
                    decrypted_envelope_key['Plaintext'],
                    AES.MODE_GCM,
                    envelope_iv
                )

                decrypted_body = b''
                while True:
                    chunk = s3_object_body.read(16*1024)
                    if len(chunk) == 0:
                        break
                    decrypted_body += decryptor.decrypt(chunk)

                decrypted_body_array = bytearray(decrypted_body)
                raw_mail = decrypted_body_array[:content_length or None]
            else:
                logger.debug(f'Read Raw Body')
                raw_mail = s3_object['Body'].read()

            logger.info(f'Shovel email to <{destination}>')
            email_message = email_message_from_bytes(raw_mail)
            original_from = email_message['From']

            del email_message['DKIM-Signature']
            del email_message['Sender']
            del email_message['Reply-To']
            email_message['Reply-To'] = original_from
            del email_message['Return-Path']
            email_message['Return-Path'] = rule
            del email_message['From']
            email_message['From'] = re_sub(
                r'\<.*?\>', f'<{rule}>', original_from)

            ses.send_raw_email(
                Destinations=[destination],
                RawMessage=dict(
                    Data=email_message.as_bytes()
                )
            )