stages/email: add helper for AWS-specific smtp password handling

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2023-08-24 13:05:44 +02:00
parent bc6706016b
commit c1b4c785c2
No known key found for this signature in database
8 changed files with 44 additions and 6 deletions

View File

@ -82,7 +82,7 @@ from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils.template import TemplateEmailMessage
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
LOGGER = get_logger() LOGGER = get_logger()

View File

@ -39,7 +39,7 @@ from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import get_client_ip, get_http_session from authentik.lib.utils.http import get_client_ip, get_http_session
from authentik.lib.utils.time import timedelta_from_string from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils.template import TemplateEmailMessage
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
from authentik.tenants.utils import DEFAULT_TENANT from authentik.tenants.utils import DEFAULT_TENANT

View File

@ -5,7 +5,7 @@ from django.core.management.base import BaseCommand, no_translations
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mail from authentik.stages.email.tasks import send_mail
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils.template import TemplateEmailMessage
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.flows.models import Stage from authentik.flows.models import Stage
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.stages.email.utils.aws import aws_calculate_password
LOGGER = get_logger() LOGGER = get_logger()
@ -106,11 +107,17 @@ class EmailStage(Stage):
"""Get fully configured Email Backend instance""" """Get fully configured Email Backend instance"""
if self.use_global_settings: if self.use_global_settings:
CONFIG.refresh("email.password") CONFIG.refresh("email.password")
host = CONFIG.get("email.host")
password = CONFIG.get("email.password")
# Special case for AWS Email passwords
if host.endswith("amazonaws.com"):
region = host.replace(".amazonaws.com", "").split(".")[-1]
password = aws_calculate_password(password, region)
return self.backend_class( return self.backend_class(
host=CONFIG.get("email.host"), host=host,
port=CONFIG.get_int("email.port"), port=CONFIG.get_int("email.port"),
username=CONFIG.get("email.username"), username=CONFIG.get("email.username"),
password=CONFIG.get("email.password"), password=password,
use_tls=CONFIG.get_bool("email.use_tls", False), use_tls=CONFIG.get_bool("email.use_tls", False),
use_ssl=CONFIG.get_bool("email.use_ssl", False), use_ssl=CONFIG.get_bool("email.use_ssl", False),
timeout=CONFIG.get_int("email.timeout"), timeout=CONFIG.get_int("email.timeout"),

View File

@ -18,7 +18,7 @@ from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils.template import TemplateEmailMessage
PLAN_CONTEXT_EMAIL_SENT = "email_sent" PLAN_CONTEXT_EMAIL_SENT = "email_sent"
PLAN_CONTEXT_EMAIL_OVERRIDE = "email" PLAN_CONTEXT_EMAIL_OVERRIDE = "email"

View File

View File

@ -0,0 +1,31 @@
"""AWS Helpers"""
import base64
import hashlib
import hmac
# These values are required to calculate the signature. Do not change them.
AWS_DATE = "11111111"
AWS_SERVICE = "ses"
AWS_MESSAGE = "SendRawEmail"
AWS_TERMINAL = "aws4_request"
AWS_VERSION = 0x04
# https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html#smtp-credentials-convert
def aws_sign(key: bytes, msg: bytes) -> bytes:
"""Hmac sign"""
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
def aws_calculate_password(secret_access_key: str, region: str) -> str:
"""Calculate AWS SMTP password from secret key"""
signature = aws_sign(("AWS4" + secret_access_key).encode("utf-8"), AWS_DATE)
signature = aws_sign(signature, region)
signature = aws_sign(signature, AWS_SERVICE)
signature = aws_sign(signature, AWS_TERMINAL)
signature = aws_sign(signature, AWS_MESSAGE)
signature_and_version = bytes([AWS_VERSION]) + signature
smtp_password = base64.b64encode(signature_and_version)
return smtp_password.decode("utf-8")