Skip to content
7 changes: 7 additions & 0 deletions airflow/config_templates/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,13 @@
type: integer
example: ~
default: "3"
- name: warn_deployment_exposure
description: |
Boolean for displaying warning for publicly viewable deployment
version_added: 2.3.0
type: boolean
example: ~
default: "True"

- name: email
description: |
Expand Down
3 changes: 3 additions & 0 deletions airflow/config_templates/default_airflow.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,9 @@ session_lifetime_minutes = 43200
# when auto-refresh is turned on
auto_refresh_interval = 3

# Boolean for displaying warning for publicly viewable deployment
warn_deployment_exposure = True

[email]

# Configuration email backend and whether to
Expand Down
25 changes: 24 additions & 1 deletion airflow/www/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
from airflow.utils.session import create_session, provide_session
from airflow.utils.state import State
from airflow.utils.strings import to_boolean
from airflow.utils.timezone import td_format
from airflow.utils.timezone import td_format, utcnow
from airflow.version import version
from airflow.www import auth, utils as wwwutils
from airflow.www.decorators import action_logging, gzipped
Expand Down Expand Up @@ -788,6 +788,29 @@ def _iter_parsed_moved_data_table_names():
# Second segment is a version marker that we don't need to show.
yield segments[2], table_name

if (
permissions.ACTION_CAN_ACCESS_MENU,
permissions.RESOURCE_ADMIN_MENU,
) in user_permissions and conf.getboolean("webserver", "warn_deployment_exposure"):
robots_file_access_count = (
session.query(Log)
.filter(Log.event == "robots")
.filter(Log.dttm > (utcnow() - timedelta(days=7)))
.count()
)
if robots_file_access_count > 0:
flash(
Markup(
'Recent requests have been made to /robots.txt. '
'This indicates that this deployment may be accessible to the public internet. '
'This warning can be disabled by setting webserver.warn_deployment_exposure=False in '
'airflow.cfg. Read more about web deployment security <a href='
f'"{get_docs_url("security/webserver.html")}">'
'here</a>'
),
"warning",
)

return self.render_template(
'airflow/dags.html',
dags=dags,
Expand Down
11 changes: 11 additions & 0 deletions docs/apache-airflow/security/webserver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ set the below:
[webserver]
x_frame_enabled = False

Disable Deployment Exposure Warning
---------------------------------------

Airflow warns when recent requests are made to ``/robot.txt``. To disable this warning set ``warn_deployment_exposure`` to
``False`` as below:

.. code-block:: ini

[webserver]
warn_deployment_exposure = False

Sensitive Variable fields
-------------------------

Expand Down
2 changes: 1 addition & 1 deletion tests/www/views/test_views_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


def test_index(admin_client):
with assert_queries_count(49):
with assert_queries_count(50):
resp = admin_client.get('/', follow_redirects=True)
check_content_in_response('DAGs', resp)

Expand Down
13 changes: 13 additions & 0 deletions tests/www/views/test_views_robots.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,21 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from tests.test_utils.config import conf_vars


def test_robots(viewer_client):
resp = viewer_client.get('/robots.txt', follow_redirects=True)
assert resp.data.decode('utf-8') == "User-agent: *\nDisallow: /\n"


def test_deployment_warning_config(admin_client):
warn_text = "webserver.warn_deployment_exposure"
admin_client.get('/robots.txt', follow_redirects=True)
resp = admin_client.get('', follow_redirects=True)
assert warn_text in resp.data.decode('utf-8')

with conf_vars({('webserver', 'warn_deployment_exposure'): 'False'}):
admin_client.get('/robots.txt', follow_redirects=True)
resp = admin_client.get('/robots.txt', follow_redirects=True)
assert warn_text not in resp.data.decode('utf-8')