Skip to content

Commit

Permalink
Merge pull request #63 from mwarkentin/36-version-leakage
Browse files Browse the repository at this point in the history
Fixes #36: Disable watchman version header by default
  • Loading branch information
mwarkentin committed Feb 27, 2018
2 parents 5be4abb + 6738fde commit 99793c6
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 13 deletions.
1 change: 1 addition & 0 deletions HISTORY.rst
Expand Up @@ -9,6 +9,7 @@ History

* [`#114 <https://github.com/mwarkentin/django-watchman/pull/114>`_] Add "bare" status view (@jamesmallen)
* [`#115 <https://github.com/mwarkentin/django-watchman/pull/115>`_] Adds ``WATCHMAN_DISABLE_APM`` option (@xfxf)
* [`#63 <https://github.com/mwarkentin/django-watchman/pull/63>`_] Disable watchman version output by default, add ``EXPOSE_WATCHMAN_VERSION`` setting (@mwarkentin)

0.14.0 (2018-01-09)
-------------------
Expand Down
17 changes: 17 additions & 0 deletions README.rst
Expand Up @@ -76,6 +76,12 @@ Pycon Canada Presentation (10 minutes)
Features
--------

Human-friendly dashboard
************************

Visit ``http://127.0.0.1:8000/watchman/dashboard/`` to get a human-friendly HTML
representation of all of your watchman checks.

Token based authentication
**************************

Expand Down Expand Up @@ -221,6 +227,17 @@ Use ``-h`` to see a full list of options::

python manage.py watchman -h

X-Watchman-Version response header
**********************************

Watchman can return the version of watchman which is running to help you keep
track of whether or not your sites are using an up-to-date version. This is
disabled by default to prevent any unintended information leakage for websites
without authentication. To enable, update the ``EXPOSE_WATCHMAN_VERSION``
setting::

EXPOSE_WATCHMAN_VERSION = True

Custom response code
********************

Expand Down
4 changes: 4 additions & 0 deletions sample_project/sample_project/settings.py
Expand Up @@ -27,6 +27,10 @@

ALLOWED_HOSTS = []

# Watchman configuration

EXPOSE_WATCHMAN_VERSION = True


# Application definition

Expand Down
33 changes: 32 additions & 1 deletion tests/test_views.py
Expand Up @@ -176,6 +176,17 @@ def test_login_not_required_with_get_param(self):

self.assertEqual(response.status_code, 200)

@override_settings(WATCHMAN_TOKEN='ABCDE')
def test_version_header_not_included_when_token_auth_fails(self):
# Have to manually reload settings here because override_settings
# happens after self.setUp(), but before self.tearDown()
reload_settings()
request = RequestFactory().get('/')

response = views.status(request)
self.assertEqual(response.status_code, 403)
self.assertFalse(response.has_header('X-Watchman-Version'))

@override_settings(WATCHMAN_TOKEN='ABCDE')
@override_settings(WATCHMAN_AUTH_DECORATOR='watchman.decorators.token_required')
def test_login_not_required_with_authorization_header(self):
Expand Down Expand Up @@ -243,7 +254,16 @@ def test_response_when_login_required(self):
response = views.status(request)
self.assertEqual(response.status_code, 200)

def test_response_version_header_missing_by_default(self):
request = RequestFactory().get('/')
response = views.status(request)
self.assertFalse(response.has_header('X-Watchman-Version'))

@override_settings(EXPOSE_WATCHMAN_VERSION=True)
def test_response_version_header(self):
# Have to manually reload settings here because override_settings
# happens after self.setUp()
reload_settings()
request = RequestFactory().get('/')
response = views.status(request)
self.assertTrue(response.has_header('X-Watchman-Version'))
Expand Down Expand Up @@ -336,10 +356,21 @@ def test_dashboard_response_code(self):
response = views.dashboard(request)
self.assertEqual(response.status_code, 200)

def test_response_version_header(self):
def test_response_version_header_and_html_missing_by_default(self):
request = RequestFactory().get('/')
response = views.dashboard(request)
self.assertFalse(response.has_header('X-Watchman-Version'))
self.assertNotIn('Watchman version:', response.content.decode())

@override_settings(EXPOSE_WATCHMAN_VERSION=True)
def test_response_has_version_header_and_html(self):
# Have to manually reload settings here because override_settings
# happens after self.setUp()
reload_settings()
request = RequestFactory().get('/')
response = views.dashboard(request)
self.assertTrue(response.has_header('X-Watchman-Version'))
self.assertIn('Watchman version:', response.content.decode())


class TestPing(unittest.TestCase):
Expand Down
2 changes: 2 additions & 0 deletions watchman/settings.py
Expand Up @@ -30,3 +30,5 @@
DEFAULT_CHECKS = DEFAULT_CHECKS + PAID_CHECKS

WATCHMAN_CHECKS = getattr(settings, 'WATCHMAN_CHECKS', DEFAULT_CHECKS)

EXPOSE_WATCHMAN_VERSION = getattr(settings, 'EXPOSE_WATCHMAN_VERSION', False)
16 changes: 9 additions & 7 deletions watchman/templates/watchman/dashboard.html
Expand Up @@ -33,12 +33,6 @@ <h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">
</div><!-- .col -->
</div><!-- .row -->

<div class="row">
<div class="col-xs-12 col-sm-offset-1 col-sm-10 col-md-offset-2 col-md-8 col-lg-offset-3 col-lg-6">
<hr />
</div><!-- .col -->
</div><!-- .row -->

<div class="row">
<div class="col-xs-12 col-sm-offset-1 col-sm-10 col-md-offset-2 col-md-8 col-lg-offset-3 col-lg-6">
<table class="table table-bordered table-hover">
Expand Down Expand Up @@ -82,7 +76,6 @@ <h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">
</div><!-- .row -->
{% endblock %}{# status_content #}


{% block error_content %}
{% for type_name, type in checks.items %}
{% for status in type.statuses %}
Expand All @@ -108,4 +101,13 @@ <h4><pre>{{ status.error }}</pre></h4>
{% endfor %}{# for status in type.statuses #}
{% endfor %}{# for type_name, type in checks.items #}
{% endblock %}{# error_content #}

{% if expose_watchman_version %}
<div class="row">
<div class="col-xs-12 col-sm-offset-1 col-sm-10 col-md-offset-2 col-md-8 col-lg-offset-3 col-lg-6">
<p>Watchman version: <a href="https://github.com/mwarkentin/django-watchman/releases/tag/{{ watchman_version }}">{{ watchman_version }}</a></p>
</div><!-- .col -->
</div><!-- .row -->
{% endif %}

{% endblock %}{# content #}
18 changes: 13 additions & 5 deletions watchman/views.py
Expand Up @@ -8,8 +8,7 @@
from django.shortcuts import render
from django.utils.translation import ugettext as _
from jsonview.decorators import json_view
from watchman import settings
from watchman import __version__
from watchman import __version__, settings
from watchman.decorators import auth
from watchman.utils import get_checks

Expand Down Expand Up @@ -83,9 +82,14 @@ def status(request):

if not checks:
raise Http404(_('No checks found'))

http_code = 200 if ok else settings.WATCHMAN_ERROR_CODE
return checks, http_code, {WATCHMAN_VERSION_HEADER: __version__}

response_headers = {}
if settings.EXPOSE_WATCHMAN_VERSION:
response_headers[WATCHMAN_VERSION_HEADER] = __version__

return checks, http_code, response_headers

@non_atomic_requests
def bare_status(request):
Expand Down Expand Up @@ -160,8 +164,12 @@ def dashboard(request):

response = render(request, 'watchman/dashboard.html', {
'checks': expanded_checks,
'overall_status': overall_status
'overall_status': overall_status,
'watchman_version': __version__,
'expose_watchman_version': settings.EXPOSE_WATCHMAN_VERSION,
})

response[WATCHMAN_VERSION_HEADER] = __version__
if settings.EXPOSE_WATCHMAN_VERSION:
response[WATCHMAN_VERSION_HEADER] = __version__

return response

0 comments on commit 99793c6

Please sign in to comment.