import base64 import binascii import functools import re import prometheus_client import requests from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.x509 import Certificate from sns_email import logger _logger = logger.getChild("sns.signature") _valid_sns_url = re.compile(r"^https://sns\.[-a-z0-9]+\.amazonaws\.com/") _certificate_time = prometheus_client.Histogram( 'sns_email_sns_signature_certificate_seconds', 'Time spent loading certificate') _signature_time = prometheus_client.Histogram( 'sns_email_sns_signature_seconds', 'Time spent computing signature') _verify_time = prometheus_client.Histogram('sns_email_sns_verify_seconds', 'Time spent verifying signature') class InvalidSnsSignatureException(Exception): pass
#!/usr/bin/env python import json import threading import time import prometheus_client from sns_email import boto_session, _counter_errors, logger from sns_email.receive import MessageReceiver _logger = logger.getChild('sqs') _counter_sqs_poll = prometheus_client.Counter('sns_email_sqs_poll_total', 'SQS poll total') _counter_sqs = prometheus_client.Counter('sns_email_sqs_received_total', 'SQS received total') class SqsPoller: def __init__(self, receiver: MessageReceiver, queue_url: str, region: str): self.receiver = receiver self.queue_url = queue_url self.region = region self.poll_wait_empty = 10 * 60 self.poll_wait = 10 self._close = threading.Event() def close(self): _logger.info("closing.")
#!/usr/bin/env python import json from http import HTTPStatus from http.server import BaseHTTPRequestHandler, HTTPServer from typing import Tuple import prometheus_client from sns_email import logger, _counter_errors from sns_email.receive import MessageReceiver from sns_email.sns_signature import sns_verify_signature, InvalidSnsSignatureException _logger = logger.getChild('sns') _counter_sns = prometheus_client.Counter('sns_email_sns_received_total', 'SNS received total') _counter_sns_time = prometheus_client.Histogram('sns_email_sns_handle_seconds', 'SNS HTTP handle time') class SnsHandler(BaseHTTPRequestHandler): receiver: MessageReceiver = None @_counter_sns_time.time() def do_POST(self): content_length = int(self.headers['Content-Length']) try: content_bytes = self.rfile.read(content_length) _logger.debug("processing message. headers=%s, content=%s", self.headers, content_bytes) try: body = json.loads(content_bytes) sns_verify_signature(body) except json.decoder.JSONDecodeError:
#!/usr/bin/env python import json import re import prometheus_client import sns_email.deliver from sns_email import logger, _counter_errors from sns_email.counter import count _receive_time = prometheus_client.Histogram('sns_email_receive_seconds', 'Time spent processing receive') _logger = logger.getChild('receive') class MessageReceiver: def __init__(self, rex=re.compile(".*"), deliver=sns_email.deliver.sendmail_deliver, boto_session=sns_email.boto_session): self.deliver = deliver self.boto_session = boto_session self.rex = rex @_receive_time.time() def receive_mail(self, message): mail = message['mail'] message_id = mail['messageId'] with count(message_id) as dup_check: if dup_check: _logger.info("ignoring duplicate message that was fully processed. message_id=%s", message_id) return
#!/usr/bin/env python import subprocess import prometheus_client from sns_email import logger _logger = logger.getChild('deliver') _counter_received = prometheus_client.Counter('sns_email_received_total', 'Received total') class sendmail_deliver: def __init__(self, source, recipients, sendmail_path="/usr/bin/sendmail"): self.p = subprocess.Popen([sendmail_path, "-r", source, "-i"] + recipients, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.recipients = recipients def __enter__(self): return self.p.stdin def __exit__(self, exc_type, exc_value, exc_traceback): if exc_value: _logger.info("exception during delivery, aborting process.") try: self.p.kill() except: _logger.warning("failed aborting delivery process.", exc_info=True) return try: outs, errs = self.p.communicate(timeout=15)