Merge "Use common ApacheSSLContext"

This commit is contained in:
Zuul 2019-02-22 11:00:14 +00:00 committed by Gerrit Code Review
commit c59894df27
7 changed files with 71 additions and 179 deletions

View File

@ -29,6 +29,7 @@ from charmhelpers.fetch import (
filter_installed_packages,
)
from charmhelpers.core.hookenv import (
NoNetworkBinding,
config,
is_relation_made,
local_unit,
@ -868,7 +869,7 @@ class ApacheSSLContext(OSContextGenerator):
addr = network_get_primary_address(
ADDRESS_MAP[net_type]['binding']
)
except NotImplementedError:
except (NotImplementedError, NoNetworkBinding):
addr = fallback
endpoint = resolve_address(net_type)

View File

@ -13,6 +13,7 @@
# limitations under the License.
from charmhelpers.core.hookenv import (
NoNetworkBinding,
config,
unit_get,
service_name,
@ -175,7 +176,7 @@ def resolve_address(endpoint_type=PUBLIC, override=True):
# configuration is not in use
try:
resolved_address = network_get_primary_address(binding)
except NotImplementedError:
except (NotImplementedError, NoNetworkBinding):
resolved_address = fallback_addr
if resolved_address is None:

View File

@ -14,8 +14,6 @@
# vim: set ts=4:et
from base64 import b64decode
import os
import json
from charmhelpers.core.hookenv import (
@ -29,14 +27,13 @@ from charmhelpers.core.hookenv import (
ERROR,
)
from charmhelpers.core.strutils import bool_from_string
from charmhelpers.contrib.openstack import context
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
context_complete
)
from charmhelpers.contrib.hahelpers.apache import (
get_ca_cert,
get_cert,
install_ca_cert,
)
from charmhelpers.contrib.network.ip import (
get_ipv6_addr,
@ -234,41 +231,14 @@ class ApacheContext(OSContextGenerator):
return ctxt
class ApacheSSLContext(OSContextGenerator):
class ApacheSSLContext(context.ApacheSSLContext):
interfaces = ['https']
external_ports = [443]
service_namespace = 'horizon'
def __call__(self):
''' Grab cert and key from configuration for SSL config '''
ctxt = {'ssl_configured': False}
use_local_ca = True
for rid in relation_ids('certificates'):
if related_units(rid):
use_local_ca = False
if use_local_ca:
ca_cert = get_ca_cert()
if not ca_cert:
return ctxt
install_ca_cert(b64decode(ca_cert))
ssl_cert, ssl_key = get_cert()
if all([ssl_cert, ssl_key]):
with open('/etc/ssl/certs/dashboard.cert', 'wb') as cert_out:
cert_out.write(b64decode(ssl_cert))
with open('/etc/ssl/private/dashboard.key', 'wb') as key_out:
key_out.write(b64decode(ssl_key))
os.chmod('/etc/ssl/private/dashboard.key', 0o600)
ctxt = {
'ssl_configured': True,
'ssl_cert': '/etc/ssl/certs/dashboard.cert',
'ssl_key': '/etc/ssl/private/dashboard.key',
}
else:
if os.path.exists(SSL_CERT_FILE) and os.path.exists(SSL_KEY_FILE):
ctxt = {
'ssl_configured': True,
'ssl_cert': SSL_CERT_FILE,
'ssl_key': SSL_KEY_FILE,
}
return ctxt
return super(ApacheSSLContext, self).__call__()
class RouterSettingContext(OSContextGenerator):

View File

@ -376,28 +376,7 @@ def certs_joined(relation_id=None):
@hooks.hook('certificates-relation-changed')
def certs_changed(relation_id=None, unit=None):
if config('os-public-hostname'):
# NOTE(fnordahl): Kludge to fix LP: #1816621
# Long term fix is to use the common ApacheSSLContext from
# charm-helpers and adapt the Apache config along the lines of
# ``charmhelpers/contrib/openstack/templates/openstack_https_frontend``
process_certificates('horizon', relation_id, unit)
ssl_dir = '/etc/apache2/ssl/horizon'
cert = os.path.join(
ssl_dir,
'{}_{}'.format('cert', config('os-public-hostname')))
key = os.path.join(
ssl_dir,
'{}_{}'.format('key', config('os-public-hostname')))
cert_link = os.path.join(ssl_dir, 'cert_dashboard')
key_link = os.path.join(ssl_dir, 'key_dashboard')
for source, dest in [(cert, cert_link), (key, key_link)]:
if os.path.exists(dest):
os.remove(dest)
os.symlink(source, dest)
else:
process_certificates('horizon', relation_id, unit,
custom_hostname_link='dashboard')
process_certificates('horizon', relation_id, unit)
CONFIGS.write_all()
service_reload('apache2')
enable_ssl()

View File

@ -1,57 +1,63 @@
<IfModule mod_ssl.c>
<VirtualHost _default_:{{ https_port }}>
ServerAdmin webmaster@localhost
{% if endpoints -%}
# Accept connections from non-SNI clients
SSLStrictSNIVHostCheck off
{% for ext_port in ext_ports -%}
NameVirtualHost *:{{ 443 }}
{% endfor -%}
{% for address, endpoint, ext, int in endpoints -%}
<VirtualHost {{ address }}:{{ ext }}>
ServerName {{ endpoint }}
DocumentRoot /var/www
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
ServerAdmin webmaster@localhost
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all
</Directory>
DocumentRoot /var/www
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all
</Directory>
CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
SSLEngine on
SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM
{% if ssl_configured %}
SSLCertificateFile {{ ssl_cert }}
SSLCertificateKeyFile {{ ssl_key }}
CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
SSLEngine on
SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM
SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
# See LP 1484489 - this is to support <= 2.4.7 and >= 2.4.8
SSLCertificateChainFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
{% if enforce_ssl %}
Header set Strict-Transport-Security "max-age={{ hsts_max_age_seconds }}"
Header set Strict-Transport-Security "max-age={{ hsts_max_age_seconds }}"
{% endif %}
{% else %}
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
{% endif %}
Header set X-XSS-Protection "1; mode=block"
Header set X-Content-Type-Options "nosniff"
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
Header set X-XSS-Protection "1; mode=block"
Header set X-Content-Type-Options "nosniff"
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost>
</IfModule>
</VirtualHost>
{% endfor -%}
{% endif -%}

View File

@ -14,7 +14,7 @@
from contextlib import contextmanager
import io
from mock import MagicMock, patch, call
from mock import MagicMock, patch
import hooks.horizon_contexts as horizon_contexts
@ -27,7 +27,6 @@ TO_PATCH = [
'related_units',
'log',
'get_cert',
'b64decode',
'context_complete',
'local_unit',
'get_relation_ip',
@ -95,49 +94,6 @@ class TestHorizonContexts(CharmTestCase):
'hsts_max_age_seconds': 15768000,
'custom_theme': False})
@patch.object(horizon_contexts, 'get_ca_cert', lambda: 'ca_cert')
@patch.object(horizon_contexts, 'install_ca_cert')
@patch('os.chmod')
def test_ApacheSSLContext_enabled(self, _chmod, _install_ca_cert):
self.relation_ids.return_value = []
self.get_cert.return_value = ('cert', 'key')
self.b64decode.side_effect = ['ca', 'cert', 'key']
with patch_open() as (_open, _file):
self.assertEqual(horizon_contexts.ApacheSSLContext()(),
{'ssl_configured': True,
'ssl_cert': '/etc/ssl/certs/dashboard.cert',
'ssl_key': '/etc/ssl/private/dashboard.key'})
_open.assert_has_calls([
call('/etc/ssl/certs/dashboard.cert', 'wb'),
call('/etc/ssl/private/dashboard.key', 'wb')
])
_file.write.assert_has_calls([
call('cert'),
call('key')
])
# Security check on key permissions
_chmod.assert_called_with('/etc/ssl/private/dashboard.key', 0o600)
_install_ca_cert.assert_called_once()
@patch.object(horizon_contexts, 'get_ca_cert', lambda: None)
def test_ApacheSSLContext_disabled(self):
self.relation_ids.return_value = []
self.get_cert.return_value = (None, None)
self.assertEqual(horizon_contexts.ApacheSSLContext()(),
{'ssl_configured': False})
@patch.object(horizon_contexts.os.path, 'exists')
def test_ApacheSSLContext_vault(self, _exists):
_exists.return_value = True
self.relation_ids.return_value = ['certificates:60']
self.related_units.return_value = ['vault/0']
self.assertEqual(
horizon_contexts.ApacheSSLContext()(),
{
'ssl_configured': True,
'ssl_cert': '/etc/apache2/ssl/horizon/cert_dashboard',
'ssl_key': '/etc/apache2/ssl/horizon/key_dashboard'})
def test_HorizonContext_defaults(self):
self.assertEqual(horizon_contexts.HorizonContext()(),
{'compress_offline': True,

View File

@ -331,33 +331,12 @@ class TestHorizonHooks(CharmTestCase):
}),
])
@patch.object(hooks.os, 'symlink')
@patch.object(hooks.os, 'remove')
@patch.object(hooks.os.path, 'exists')
@patch.object(hooks, 'service_reload')
@patch.object(hooks, 'process_certificates')
def test_certs_changed(self, _process_certificates, _service_reload,
_exists, _remove, _symlink):
def test_certs_changed(self, _process_certificates, _service_reload):
self._call_hook('certificates-relation-changed')
_process_certificates.assert_called_with(
'horizon', None, None, custom_hostname_link='dashboard')
self.assertFalse(_symlink.called)
'horizon', None, None)
self.CONFIGS.write_all.assert_called_with()
_service_reload.assert_called_with('apache2')
self.enable_ssl.assert_called_with()
_process_certificates.reset_mock()
self.config.side_effect = None
self.config.return_value = 'somehostname'
_exists.return_value = True
self._call_hook('certificates-relation-changed')
_process_certificates.assert_called_with('horizon', None, None)
_remove.assert_has_calls([
call('/etc/apache2/ssl/horizon/cert_dashboard'),
call('/etc/apache2/ssl/horizon/key_dashboard'),
])
_symlink.assert_has_calls([
call('/etc/apache2/ssl/horizon/cert_somehostname',
'/etc/apache2/ssl/horizon/cert_dashboard'),
call('/etc/apache2/ssl/horizon/key_somehostname',
'/etc/apache2/ssl/horizon/key_dashboard'),
])