Switch to intermediate Mozilla cert profile (#15957)

* Allow choosing intermediate SSL profile

* Fix tests
This commit is contained in:
Paulus Schoutsen 2018-08-14 08:20:17 +02:00 committed by GitHub
parent 69b694ff26
commit 6540d2e073
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 6 deletions

View file

@ -49,6 +49,10 @@ CONF_TRUSTED_PROXIES = 'trusted_proxies'
CONF_TRUSTED_NETWORKS = 'trusted_networks'
CONF_LOGIN_ATTEMPTS_THRESHOLD = 'login_attempts_threshold'
CONF_IP_BAN_ENABLED = 'ip_ban_enabled'
CONF_SSL_PROFILE = 'ssl_profile'
SSL_MODERN = 'modern'
SSL_INTERMEDIATE = 'intermediate'
_LOGGER = logging.getLogger(__name__)
@ -74,7 +78,9 @@ HTTP_SCHEMA = vol.Schema({
vol.Optional(CONF_LOGIN_ATTEMPTS_THRESHOLD,
default=NO_LOGIN_ATTEMPT_THRESHOLD):
vol.Any(cv.positive_int, NO_LOGIN_ATTEMPT_THRESHOLD),
vol.Optional(CONF_IP_BAN_ENABLED, default=True): cv.boolean
vol.Optional(CONF_IP_BAN_ENABLED, default=True): cv.boolean,
vol.Optional(CONF_SSL_PROFILE, default=SSL_MODERN):
vol.In([SSL_INTERMEDIATE, SSL_MODERN]),
})
CONFIG_SCHEMA = vol.Schema({
@ -123,6 +129,7 @@ async def async_setup(hass, config):
trusted_networks = conf[CONF_TRUSTED_NETWORKS]
is_ban_enabled = conf[CONF_IP_BAN_ENABLED]
login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD]
ssl_profile = conf[CONF_SSL_PROFILE]
if api_password is not None:
logging.getLogger('aiohttp.access').addFilter(
@ -141,7 +148,8 @@ async def async_setup(hass, config):
trusted_proxies=trusted_proxies,
trusted_networks=trusted_networks,
login_threshold=login_threshold,
is_ban_enabled=is_ban_enabled
is_ban_enabled=is_ban_enabled,
ssl_profile=ssl_profile,
)
async def stop_server(event):
@ -181,7 +189,7 @@ class HomeAssistantHTTP:
ssl_certificate, ssl_peer_certificate,
ssl_key, server_host, server_port, cors_origins,
use_x_forwarded_for, trusted_proxies, trusted_networks,
login_threshold, is_ban_enabled):
login_threshold, is_ban_enabled, ssl_profile):
"""Initialize the HTTP Home Assistant server."""
app = self.app = web.Application(
middlewares=[staticresource_middleware])
@ -222,6 +230,7 @@ class HomeAssistantHTTP:
self.server_port = server_port
self.trusted_networks = trusted_networks
self.is_ban_enabled = is_ban_enabled
self.ssl_profile = ssl_profile
self._handler = None
self.server = None
@ -308,7 +317,10 @@ class HomeAssistantHTTP:
if self.ssl_certificate:
try:
context = ssl_util.server_context()
if self.ssl_profile == SSL_INTERMEDIATE:
context = ssl_util.server_context_intermediate()
else:
context = ssl_util.server_context_modern()
context.load_cert_chain(self.ssl_certificate, self.ssl_key)
except OSError as error:
_LOGGER.error("Could not read SSL certificate from %s: %s",

View file

@ -13,7 +13,7 @@ def client_context() -> ssl.SSLContext:
return context
def server_context() -> ssl.SSLContext:
def server_context_modern() -> ssl.SSLContext:
"""Return an SSL context following the Mozilla recommendations.
TLS configuration follows the best-practice guidelines specified here:
@ -37,4 +37,58 @@ def server_context() -> ssl.SSLContext:
"ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:"
"ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
)
return context
def server_context_intermediate() -> ssl.SSLContext:
"""Return an SSL context following the Mozilla recommendations.
TLS configuration follows the best-practice guidelines specified here:
https://wiki.mozilla.org/Security/Server_Side_TLS
Intermediate guidelines are followed.
"""
context = ssl.SSLContext(ssl.PROTOCOL_TLS) # pylint: disable=no-member
context.options |= (
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_CIPHER_SERVER_PREFERENCE
)
if hasattr(ssl, 'OP_NO_COMPRESSION'):
context.options |= ssl.OP_NO_COMPRESSION
context.set_ciphers(
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"ECDHE-ECDSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"DHE-RSA-AES128-GCM-SHA256:"
"DHE-RSA-AES256-GCM-SHA384:"
"ECDHE-ECDSA-AES128-SHA256:"
"ECDHE-RSA-AES128-SHA256:"
"ECDHE-ECDSA-AES128-SHA:"
"ECDHE-RSA-AES256-SHA384:"
"ECDHE-RSA-AES128-SHA:"
"ECDHE-ECDSA-AES256-SHA384:"
"ECDHE-ECDSA-AES256-SHA:"
"ECDHE-RSA-AES256-SHA:"
"DHE-RSA-AES128-SHA256:"
"DHE-RSA-AES128-SHA:"
"DHE-RSA-AES256-SHA256:"
"DHE-RSA-AES256-SHA:"
"ECDHE-ECDSA-DES-CBC3-SHA:"
"ECDHE-RSA-DES-CBC3-SHA:"
"EDH-RSA-DES-CBC3-SHA:"
"AES128-GCM-SHA256:"
"AES256-GCM-SHA384:"
"AES128-SHA256:"
"AES256-SHA256:"
"AES128-SHA:"
"AES256-SHA:"
"DES-CBC3-SHA:"
"!DSS"
)
return context

View file

@ -1,10 +1,13 @@
"""The tests for the Home Assistant HTTP component."""
import logging
import unittest
from unittest.mock import patch
from homeassistant.setup import async_setup_component
import homeassistant.components.http as http
from homeassistant.util.ssl import (
server_context_modern, server_context_intermediate)
class TestView(http.HomeAssistantView):
@ -169,3 +172,56 @@ async def test_proxy_config_only_trust_proxies(hass):
http.CONF_TRUSTED_PROXIES: ['127.0.0.1']
}
}) is not True
async def test_ssl_profile_defaults_modern(hass):
"""Test default ssl profile."""
assert await async_setup_component(hass, 'http', {}) is True
hass.http.ssl_certificate = 'bla'
with patch('ssl.SSLContext.load_cert_chain'), \
patch('homeassistant.util.ssl.server_context_modern',
side_effect=server_context_modern) as mock_context:
await hass.async_start()
await hass.async_block_till_done()
assert len(mock_context.mock_calls) == 1
async def test_ssl_profile_change_intermediate(hass):
"""Test setting ssl profile to intermediate."""
assert await async_setup_component(hass, 'http', {
'http': {
'ssl_profile': 'intermediate'
}
}) is True
hass.http.ssl_certificate = 'bla'
with patch('ssl.SSLContext.load_cert_chain'), \
patch('homeassistant.util.ssl.server_context_intermediate',
side_effect=server_context_intermediate) as mock_context:
await hass.async_start()
await hass.async_block_till_done()
assert len(mock_context.mock_calls) == 1
async def test_ssl_profile_change_modern(hass):
"""Test setting ssl profile to modern."""
assert await async_setup_component(hass, 'http', {
'http': {
'ssl_profile': 'modern'
}
}) is True
hass.http.ssl_certificate = 'bla'
with patch('ssl.SSLContext.load_cert_chain'), \
patch('homeassistant.util.ssl.server_context_modern',
side_effect=server_context_modern) as mock_context:
await hass.async_start()
await hass.async_block_till_done()
assert len(mock_context.mock_calls) == 1

View file

@ -159,7 +159,9 @@ class TestCheckConfig(unittest.TestCase):
'login_attempts_threshold': -1,
'server_host': '0.0.0.0',
'server_port': 8123,
'trusted_networks': []}
'trusted_networks': [],
'ssl_profile': 'modern',
}
assert res['secret_cache'] == {secrets_path: {'http_pw': 'abc123'}}
assert res['secrets'] == {'http_pw': 'abc123'}
assert normalize_yaml_files(res) == [