Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

from config import Config
from app.extensions import db, migrate, oauth
from app.storage import public_url


class _CloudFrontHTTPSFix:
"""Flip wsgi.url_scheme to https when the request came in via CloudFront.

The stack is CloudFront(HTTPS) → ALB(HTTP) → ECS, and ALB *replaces*
`X-Forwarded-Proto` with its listener's value (http) rather than
appending — so the standard ProxyFix pattern doesn't help here.
CloudFront is configured to inject `X-Forwarded-Scheme: https` as an
immutable origin custom header (see terraform/main/modules/cloudfront/main.tf); ALB
passes that name through verbatim because it has no special handling
for it. Presence = the request entered through prod's CloudFront,
so url_for(_external=True) should generate https URLs (e.g. the OIDC
redirect_uri that Keycloak validates).
"""

def __init__(self, wsgi_app):
self.wsgi_app = wsgi_app

def __call__(self, environ, start_response):
if environ.get("HTTP_X_FORWARDED_SCHEME") == "https":
environ["wsgi.url_scheme"] = "https"
return self.wsgi_app(environ, start_response)


def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

# CloudFront → ALB (HTTP) → ECS. CloudFront stamps `X-Forwarded-Proto:
# https`; ALB appends its own `http`. x_proto=2 picks the CloudFront
# value so `url_for(..., _external=True)` produces https URLs (e.g.
# the OIDC redirect_uri Keycloak validates). x_for=2 mirrors the same
# for client IPs in access logs.
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=2, x_proto=2, x_host=1)
app.wsgi_app = _CloudFrontHTTPSFix(app.wsgi_app)

db.init_app(app)
migrate.init_app(app, db)
Expand Down
13 changes: 13 additions & 0 deletions terraform/main/modules/cloudfront/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ resource "aws_cloudfront_distribution" "this" {
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}

# `viewer_protocol_policy = "redirect-to-https"` below means every
# request reaching origin came from an HTTPS viewer, so this static
# value is always correct. The ALB's HTTP listener replaces
# `X-Forwarded-Proto` with `http` before forwarding to ECS, which
# breaks Flask's url_for(_external=True) for OIDC redirect_uris. A
# custom header name (vs reusing `X-Forwarded-Proto`) sidesteps the
# ALB rewrite entirely; the app reads this header to flip
# wsgi.url_scheme back to https.
custom_header {
name = "X-Forwarded-Scheme"
value = "https"
}
}

# Default behavior: forward everything to the ALB with no caching. Auth
Expand Down