diff --git a/airflow-core/tests/unit/always/test_project_structure.py b/airflow-core/tests/unit/always/test_project_structure.py index af628f4b32c3a..a1cfba80cf693 100644 --- a/airflow-core/tests/unit/always/test_project_structure.py +++ b/airflow-core/tests/unit/always/test_project_structure.py @@ -138,6 +138,7 @@ def test_providers_modules_should_have_tests(self): "providers/fab/tests/unit/fab/www/extensions/test_init_security.py", "providers/fab/tests/unit/fab/www/extensions/test_init_session.py", "providers/fab/tests/unit/fab/www/extensions/test_init_views.py", + "providers/fab/tests/unit/fab/www/extensions/test_init_wsgi_middlewares.py", "providers/fab/tests/unit/fab/www/security/test_permissions.py", "providers/fab/tests/unit/fab/www/test_airflow_flask_app.py", "providers/fab/tests/unit/fab/www/test_app.py", diff --git a/providers/fab/provider.yaml b/providers/fab/provider.yaml index c069b3b12bda4..7805ca3a0d772 100644 --- a/providers/fab/provider.yaml +++ b/providers/fab/provider.yaml @@ -119,6 +119,58 @@ config: type: integer example: ~ default: "43200" + enable_proxy_fix: + description: | + Enable werkzeug ``ProxyFix`` middleware for reverse proxy + version_added: 2.1.0 + type: boolean + example: ~ + default: "False" + proxy_fix_x_for: + description: | + Number of values to trust for ``X-Forwarded-For``. + See `Werkzeug: X-Forwarded-For Proxy Fix + `__ for more details. + version_added: 2.1.0 + type: integer + example: ~ + default: "1" + proxy_fix_x_proto: + description: | + Number of values to trust for ``X-Forwarded-Proto``. + See `Werkzeug: X-Forwarded-For Proxy Fix + `__ for more details. + version_added: 2.1.0 + type: integer + example: ~ + default: "1" + proxy_fix_x_host: + description: | + Number of values to trust for ``X-Forwarded-Host``. + See `Werkzeug: X-Forwarded-For Proxy Fix + `__ for more details. + version_added: 2.1.0 + type: integer + example: ~ + default: "1" + proxy_fix_x_port: + description: | + Number of values to trust for ``X-Forwarded-Port``. + See `Werkzeug: X-Forwarded-For Proxy Fix + `__ for more details. + version_added: 2.1.0 + type: integer + example: ~ + default: "1" + proxy_fix_x_prefix: + description: | + Number of values to trust for ``X-Forwarded-Prefix``. + See `Werkzeug: X-Forwarded-For Proxy Fix + `__ for more details. + version_added: 2.1.0 + type: integer + example: ~ + default: "1" auth-managers: - airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager diff --git a/providers/fab/src/airflow/providers/fab/get_provider_info.py b/providers/fab/src/airflow/providers/fab/get_provider_info.py index 924dfca466a39..6558807420aa7 100644 --- a/providers/fab/src/airflow/providers/fab/get_provider_info.py +++ b/providers/fab/src/airflow/providers/fab/get_provider_info.py @@ -79,6 +79,48 @@ def get_provider_info(): "example": None, "default": "43200", }, + "enable_proxy_fix": { + "description": "Enable werkzeug ``ProxyFix`` middleware for reverse proxy\n", + "version_added": "2.1.0", + "type": "boolean", + "example": None, + "default": "False", + }, + "proxy_fix_x_for": { + "description": "Number of values to trust for ``X-Forwarded-For``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n`__ for more details.\n", + "version_added": "2.1.0", + "type": "integer", + "example": None, + "default": "1", + }, + "proxy_fix_x_proto": { + "description": "Number of values to trust for ``X-Forwarded-Proto``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n`__ for more details.\n", + "version_added": "2.1.0", + "type": "integer", + "example": None, + "default": "1", + }, + "proxy_fix_x_host": { + "description": "Number of values to trust for ``X-Forwarded-Host``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n`__ for more details.\n", + "version_added": "2.1.0", + "type": "integer", + "example": None, + "default": "1", + }, + "proxy_fix_x_port": { + "description": "Number of values to trust for ``X-Forwarded-Port``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n`__ for more details.\n", + "version_added": "2.1.0", + "type": "integer", + "example": None, + "default": "1", + }, + "proxy_fix_x_prefix": { + "description": "Number of values to trust for ``X-Forwarded-Prefix``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n`__ for more details.\n", + "version_added": "2.1.0", + "type": "integer", + "example": None, + "default": "1", + }, }, } }, diff --git a/providers/fab/src/airflow/providers/fab/www/app.py b/providers/fab/src/airflow/providers/fab/www/app.py index be29f28bd674c..873e7adb8f048 100644 --- a/providers/fab/src/airflow/providers/fab/www/app.py +++ b/providers/fab/src/airflow/providers/fab/www/app.py @@ -41,6 +41,7 @@ init_error_handlers, init_plugins, ) +from airflow.providers.fab.www.extensions.init_wsgi_middlewares import init_wsgi_middleware from airflow.providers.fab.www.utils import get_session_lifetime_config app: Flask | None = None @@ -103,6 +104,7 @@ def create_app(enable_plugins: bool): init_jinja_globals(flask_app, enable_plugins=enable_plugins) init_xframe_protection(flask_app) init_airflow_session_interface(flask_app) + init_wsgi_middleware(flask_app) return flask_app diff --git a/providers/fab/src/airflow/providers/fab/www/extensions/init_wsgi_middlewares.py b/providers/fab/src/airflow/providers/fab/www/extensions/init_wsgi_middlewares.py new file mode 100644 index 0000000000000..bcb9d3919f6c3 --- /dev/null +++ b/providers/fab/src/airflow/providers/fab/www/extensions/init_wsgi_middlewares.py @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from werkzeug.middleware.proxy_fix import ProxyFix + +from airflow.configuration import conf + +if TYPE_CHECKING: + from flask import Flask + + +def init_wsgi_middleware(flask_app: Flask) -> None: + """Handle X-Forwarded-* headers and base_url support.""" + # Apply ProxyFix middleware + if conf.getboolean("fab", "ENABLE_PROXY_FIX"): + flask_app.wsgi_app = ProxyFix( # type: ignore + flask_app.wsgi_app, + x_for=conf.getint("fab", "PROXY_FIX_X_FOR", fallback=1), + x_proto=conf.getint("fab", "PROXY_FIX_X_PROTO", fallback=1), + x_host=conf.getint("fab", "PROXY_FIX_X_HOST", fallback=1), + x_port=conf.getint("fab", "PROXY_FIX_X_PORT", fallback=1), + x_prefix=conf.getint("fab", "PROXY_FIX_X_PREFIX", fallback=1), + )