misc: add details about security headers in health info (#41630)

This commit is contained in:
Frédéric Péters 2020-04-12 15:51:55 +02:00
parent 536713ebca
commit 49575cffc6
5 changed files with 90 additions and 5 deletions

View File

@ -232,12 +232,33 @@ class ServiceBase(models.Model):
r = requests.get(self.get_admin_zones()[0].href, verify=False, allow_redirects=False)
return (r.status_code >= 200 and r.status_code < 400)
def security_data(self):
security_data = {
'level': 'NaN',
'label': '',
}
if not self.is_resolvable():
return security_data
security_data['level'] = 0
resp = requests.get(self.base_url, verify=False, allow_redirects=False)
missing_headers = []
for header in ('X-Content-Type-Options', 'X-Frame-Options', 'X-XSS-Protection', 'Strict-Transport-Security'):
if not resp.headers.get(header):
missing_headers.append(header)
if missing_headers:
security_data['level'] = len(missing_headers)
security_data['label'] = _('Missing headers: %s') % ', '.join(missing_headers)
else:
security_data['label'] = _('HTTP security headers are set.')
return security_data
def get_health_dict(self):
properties = [
('is_resolvable', 120),
('has_valid_certificate', 3600),
('is_running', 60),
('is_operational', 60),
('security_data', 60),
]
result = {}
for name, cache_duration in properties:

View File

@ -216,6 +216,23 @@ div#services span.op-nok::before {
color: #b00000;
}
div#services span.sec-NaN {
color: #666;
}
div#services span.security::before {
font-family: FontAwesome;
content: "\f05a"; /* info-circle */
display: inline-block;
width: 1.5rem;
color: #ffc000;
}
div#services span.security.sec-0::before {
color: #00b000;
content: "\f058"; /* check-circle */
}
div#services span {
margin-right: 1rem;
}

View File

@ -36,6 +36,7 @@
<span style="display: none" class="dns">{% trans "DNS" %}</span>
<span style="display: none" class="certificate">{% trans "Certificate" %}</span>
<span style="display: none" class="web">{% trans "Web" %}</span>
<span style="display: none" class="security">{% trans "Security" %}</span>
</p>
</div>
{% endfor %}
@ -77,6 +78,9 @@ $(function() {
$service_block.find('.web').addClass('op-nok');
ok = false;
}
$service_block.find('.security').addClass('sec-' + service.security_data.level);
$service_block.find('.security').attr('title', service.security_data.label);
var $service_p = $service_block.find('span.checking')
$service_block.find('span').show();
$service_p.hide();

View File

@ -74,8 +74,8 @@ def test_is_running(app, admin_user, services, monkeypatch):
jazz = content['data']['jazz']
assert not blues['is_running']
assert jazz['is_running']
assert blues_mock.call['count'] == 2
assert jazz_mock.call['count'] == 2
assert blues_mock.call['count'] == 3
assert jazz_mock.call['count'] == 3
# check it gets results from cache
response = app.get('/api/health/')
@ -84,8 +84,50 @@ def test_is_running(app, admin_user, services, monkeypatch):
jazz = content['data']['jazz']
assert not blues['is_running']
assert jazz['is_running']
assert blues_mock.call['count'] == 2
assert jazz_mock.call['count'] == 2
assert blues_mock.call['count'] == 3
assert jazz_mock.call['count'] == 3
def test_security_data(app, admin_user, services, monkeypatch):
cache.clear()
monkeypatch.setattr(socket, 'gethostbyname', lambda x: '176.31.123.109')
@urlmatch(netloc='jazz.example.publik')
@remember_called
def jazz_mock(url, request):
return {'status_code': 200,
'headers': {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=63072000',
}
}
@urlmatch(netloc='blues.example.publik')
@remember_called
def blues_mock(url, request):
return {'status_code': 200}
with HTTMock(blues_mock, jazz_mock) as mock:
response = app.get('/api/health/')
content = json.loads(response.text)
blues = content['data']['blues']
jazz = content['data']['jazz']
assert blues_mock.call['count'] == 3
assert jazz_mock.call['count'] == 3
assert blues['security_data']['level'] == 4
assert jazz['security_data']['level'] == 0
# check it gets results from cache
response = app.get('/api/health/')
content = json.loads(response.text)
blues = content['data']['blues']
jazz = content['data']['jazz']
assert blues_mock.call['count'] == 3
assert jazz_mock.call['count'] == 3
assert blues['security_data']['level'] == 4
assert jazz['security_data']['level'] == 0
def test_is_operational(app, admin_user, services, monkeypatch):

View File

@ -82,7 +82,8 @@ def test_healt_view(app):
'has_valid_certificate': False,
'is_operational': True,
'is_resolvable': False,
'is_running': False
'is_running': False,
'security_data': {'level': 'NaN', 'label': ''},
}}}
def test_menu_view(app, admin_user):