diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 8a817d0b7..3b108f116 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -82,7 +82,7 @@ from authentik.flows.views.executor import QS_KEY_TOKEN from authentik.lib.config import CONFIG from authentik.stages.email.models import EmailStage 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 LOGGER = get_logger() diff --git a/authentik/events/models.py b/authentik/events/models.py index 82d215fbc..d19041f40 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -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.time import timedelta_from_string 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.utils import DEFAULT_TENANT diff --git a/authentik/stages/email/management/commands/test_email.py b/authentik/stages/email/management/commands/test_email.py index 3e1a17fa7..46ff85dad 100644 --- a/authentik/stages/email/management/commands/test_email.py +++ b/authentik/stages/email/management/commands/test_email.py @@ -5,7 +5,7 @@ from django.core.management.base import BaseCommand, no_translations from authentik.stages.email.models import EmailStage 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): diff --git a/authentik/stages/email/models.py b/authentik/stages/email/models.py index fcc63868c..4969b6777 100644 --- a/authentik/stages/email/models.py +++ b/authentik/stages/email/models.py @@ -14,6 +14,7 @@ from structlog.stdlib import get_logger from authentik.flows.models import Stage from authentik.lib.config import CONFIG +from authentik.stages.email.utils.aws import aws_calculate_password LOGGER = get_logger() @@ -106,11 +107,17 @@ class EmailStage(Stage): """Get fully configured Email Backend instance""" if self.use_global_settings: 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( - host=CONFIG.get("email.host"), + host=host, port=CONFIG.get_int("email.port"), username=CONFIG.get("email.username"), - password=CONFIG.get("email.password"), + password=password, use_tls=CONFIG.get_bool("email.use_tls", False), use_ssl=CONFIG.get_bool("email.use_ssl", False), timeout=CONFIG.get_int("email.timeout"), diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py index f116f7de0..6712eeed5 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -18,7 +18,7 @@ from authentik.flows.stage import ChallengeStageView from authentik.flows.views.executor import QS_KEY_TOKEN from authentik.stages.email.models import EmailStage 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_OVERRIDE = "email" diff --git a/authentik/stages/email/utils/__init__.py b/authentik/stages/email/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/stages/email/utils/aws.py b/authentik/stages/email/utils/aws.py new file mode 100644 index 000000000..0a0720d39 --- /dev/null +++ b/authentik/stages/email/utils/aws.py @@ -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") diff --git a/authentik/stages/email/utils.py b/authentik/stages/email/utils/template.py similarity index 100% rename from authentik/stages/email/utils.py rename to authentik/stages/email/utils/template.py