misc: add details about security headers in health info (#41630)
This commit is contained in:
parent
536713ebca
commit
49575cffc6
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue