Commit 944542ad authored by Jérome Perrin's avatar Jérome Perrin

stack/erp5: stop using caucase managed certificate

In nexedi/slapos@74d18b9d I switched ERP5 to use caucase certificate, but caucase is not yet usable for this and this caused issues with upgrading existing instances.

We'll maybe try caucase again here, in that case 620c9332 can be reverted if we want to reuse some parts of what was made.

See merge request nexedi/slapos!857
parents fb49ccdb 620c9332
Pipeline #12234 failed with stage
...@@ -5,15 +5,13 @@ import logging ...@@ -5,15 +5,13 @@ import logging
import os import os
import re import re
import shutil import shutil
import socket
import subprocess import subprocess
import tempfile import tempfile
import time import time
import urlparse import urlparse
from BaseHTTPServer import BaseHTTPRequestHandler from BaseHTTPServer import BaseHTTPRequestHandler
from typing import Any, Dict, Optional from typing import Dict
import idna
import mock import mock
import OpenSSL.SSL import OpenSSL.SSL
import pexpect import pexpect
...@@ -106,20 +104,6 @@ class CaucaseService(ManagedResource): ...@@ -106,20 +104,6 @@ class CaucaseService(ManagedResource):
self._caucased_process.wait() self._caucased_process.wait()
shutil.rmtree(self.directory) shutil.rmtree(self.directory)
@property
def ca_crt_path(self):
# type: () -> str
"""Path of the CA certificate from this caucase.
"""
ca_crt_path = os.path.join(self.directory, 'ca.crt.pem')
if not os.path.exists(ca_crt_path):
with open(ca_crt_path, 'w') as f:
f.write(
requests.get(urlparse.urljoin(
self.url,
'/cas/crt/ca.crt.pem',
)).text)
return ca_crt_path
class BalancerTestCase(ERP5InstanceTestCase): class BalancerTestCase(ERP5InstanceTestCase):
...@@ -387,85 +371,6 @@ class TestHTTP(BalancerTestCase): ...@@ -387,85 +371,6 @@ class TestHTTP(BalancerTestCase):
]) ])
class TestTLS(BalancerTestCase):
"""Check TLS
"""
__partition_reference__ = 's'
def _getServerCertificate(self, hostname, port):
# type: (Optional[str], Optional[int]) -> Any
hostname_idna = idna.encode(hostname)
sock = socket.socket()
sock.connect((hostname, port))
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
ctx.check_hostname = False
ctx.verify_mode = OpenSSL.SSL.VERIFY_NONE
sock_ssl = OpenSSL.SSL.Connection(ctx, sock)
sock_ssl.set_connect_state()
sock_ssl.set_tlsext_host_name(hostname_idna)
sock_ssl.do_handshake()
cert = sock_ssl.get_peer_certificate()
crypto_cert = cert.to_cryptography()
sock_ssl.close()
sock.close()
return crypto_cert
def test_certificate_validates_with_caucase_ca(self):
# type: () -> None
caucase = self.getManagedResource("caucase", CaucaseService)
requests.get(self.default_balancer_url, verify=caucase.ca_crt_path)
def test_certificate_renewal(self):
# type: () -> None
caucase = self.getManagedResource("caucase", CaucaseService)
balancer_parsed_url = urlparse.urlparse(self.default_balancer_url)
certificate_before_renewal = self._getServerCertificate(
balancer_parsed_url.hostname,
balancer_parsed_url.port)
# run caucase updater 90 days in the future, so that certificate is
# renewed.
caucase_updater = os.path.join(
self.computer_partition_root_path,
'etc',
'service',
'caucase-updater',
)
process = pexpect.spawnu(
"faketime +90days %s" % caucase_updater,
env=dict(os.environ, PYTHONPATH=''),
)
logger = self.logger
class DebugLogFile:
def write(self, msg):
logger.info("output from caucase_updater: %s", msg)
def flush(self):
pass
process.logfile = DebugLogFile()
process.expect(u"Renewing .*\nNext wake-up.*")
process.terminate()
process.wait()
# wait for server to use new certificate
for _ in range(30):
certificate_after_renewal = self._getServerCertificate(
balancer_parsed_url.hostname,
balancer_parsed_url.port)
if certificate_after_renewal.not_valid_before > certificate_before_renewal.not_valid_before:
break
time.sleep(.5)
self.assertGreater(
certificate_after_renewal.not_valid_before,
certificate_before_renewal.not_valid_before,
)
# requests are served properly after cert renewal
requests.get(self.default_balancer_url, verify=caucase.ca_crt_path).raise_for_status()
class ContentTypeHTTPServer(ManagedHTTPServer): class ContentTypeHTTPServer(ManagedHTTPServer):
"""An HTTP Server which reply with content type from path. """An HTTP Server which reply with content type from path.
......
...@@ -31,7 +31,6 @@ import glob ...@@ -31,7 +31,6 @@ import glob
import urlparse import urlparse
import socket import socket
import time import time
import tempfile
import psutil import psutil
import requests import requests
...@@ -44,7 +43,7 @@ setUpModule # pyflakes ...@@ -44,7 +43,7 @@ setUpModule # pyflakes
class TestPublishedURLIsReachableMixin(object): class TestPublishedURLIsReachableMixin(object):
"""Mixin that checks that default page of ERP5 is reachable. """Mixin that checks that default page of ERP5 is reachable.
""" """
def _checkERP5IsReachable(self, url, verify): def _checkERP5IsReachable(self, url):
# What happens is that instanciation just create the services, but does not # What happens is that instanciation just create the services, but does not
# wait for ERP5 to be initialized. When this test run ERP5 instance is # wait for ERP5 to be initialized. When this test run ERP5 instance is
# instanciated, but zope is still busy creating the site and haproxy replies # instanciated, but zope is still busy creating the site and haproxy replies
...@@ -52,7 +51,7 @@ class TestPublishedURLIsReachableMixin(object): ...@@ -52,7 +51,7 @@ class TestPublishedURLIsReachableMixin(object):
# erp5 site is not created, with 500 when mysql is not yet reachable, so we # erp5 site is not created, with 500 when mysql is not yet reachable, so we
# retry in a loop until we get a succesful response. # retry in a loop until we get a succesful response.
for i in range(1, 60): for i in range(1, 60):
r = requests.get(url, verify=verify) r = requests.get(url, verify=False) # XXX can we get CA from caucase already ?
if r.status_code != requests.codes.ok: if r.status_code != requests.codes.ok:
delay = i * 2 delay = i * 2
self.logger.warn("ERP5 was not available, sleeping for %ds and retrying", delay) self.logger.warn("ERP5 was not available, sleeping for %ds and retrying", delay)
...@@ -63,36 +62,19 @@ class TestPublishedURLIsReachableMixin(object): ...@@ -63,36 +62,19 @@ class TestPublishedURLIsReachableMixin(object):
self.assertIn("ERP5", r.text) self.assertIn("ERP5", r.text)
def _getCaucaseServiceCACertificate(self):
ca_cert = tempfile.NamedTemporaryFile(
prefix="ca.crt.pem",
mode="w",
delete=False,
)
ca_cert.write(
requests.get(
urlparse.urljoin(
self.getRootPartitionConnectionParameterDict()['caucase-http-url'],
'/cas/crt/ca.crt.pem',
)).text)
self.addCleanup(os.unlink, ca_cert.name)
return ca_cert.name
def test_published_family_default_v6_is_reachable(self): def test_published_family_default_v6_is_reachable(self):
"""Tests the IPv6 URL published by the root partition is reachable. """Tests the IPv6 URL published by the root partition is reachable.
""" """
param_dict = self.getRootPartitionConnectionParameterDict() param_dict = self.getRootPartitionConnectionParameterDict()
self._checkERP5IsReachable( self._checkERP5IsReachable(
urlparse.urljoin(param_dict['family-default-v6'], param_dict['site-id']), urlparse.urljoin(param_dict['family-default-v6'], param_dict['site-id']))
self._getCaucaseServiceCACertificate())
def test_published_family_default_v4_is_reachable(self): def test_published_family_default_v4_is_reachable(self):
"""Tests the IPv4 URL published by the root partition is reachable. """Tests the IPv4 URL published by the root partition is reachable.
""" """
param_dict = self.getRootPartitionConnectionParameterDict() param_dict = self.getRootPartitionConnectionParameterDict()
self._checkERP5IsReachable( self._checkERP5IsReachable(
urlparse.urljoin(param_dict['family-default'], param_dict['site-id']), urlparse.urljoin(param_dict['family-default'], param_dict['site-id']))
self._getCaucaseServiceCACertificate())
class TestDefaultParameters(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin): class TestDefaultParameters(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
......
...@@ -90,7 +90,7 @@ md5sum = 2f3ddd328ac1c375e483ecb2ef5ffb57 ...@@ -90,7 +90,7 @@ md5sum = 2f3ddd328ac1c375e483ecb2ef5ffb57
[template-balancer] [template-balancer]
filename = instance-balancer.cfg.in filename = instance-balancer.cfg.in
md5sum = ecf119142e6b5cd85a2ba397552d2142 md5sum = 4ba93d28d93bd066d5d19f4f74fc13d7
[template-haproxy-cfg] [template-haproxy-cfg]
filename = haproxy.cfg.in filename = haproxy.cfg.in
......
...@@ -18,56 +18,25 @@ per partition. No more (undefined result), no less (IndexError). ...@@ -18,56 +18,25 @@ per partition. No more (undefined result), no less (IndexError).
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
mode = 644 mode = 644
[balancer-csr-request-config]
< = jinja2-template-base
template = inline:
[req]
prompt = no
req_extensions = req_ext
distinguished_name = dn
[ dn ]
CN = example.com
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = {{ ipv4 }}
{% if ipv6_set -%}
IP.2 = {{ ipv6 }}
{% endif %}
rendered = ${buildout:parts-directory}/${:_buildout_section_name_}/${:_buildout_section_name_}.txt
[balancer-csr-request]
recipe = plone.recipe.command
command = {{ parameter_dict["openssl"] }}/bin/openssl req \
-newkey rsa:2048 \
-batch \
-new \
-nodes \
-keyout '${apache-conf-ssl:key}' \
-config '${balancer-csr-request-config:rendered}' \
-out '${:csr}'
stop-on-error = true
csr = ${directory:etc}/${:_buildout_section_name_}.csr.pem
{{ caucase.updater( {{ caucase.updater(
prefix='caucase-updater', prefix='caucase-updater',
buildout_bin_directory=parameter_dict['bin-directory'], buildout_bin_directory=parameter_dict['bin-directory'],
updater_path='${directory:services-on-watch}/caucase-updater', updater_path='${directory:services-on-watch}/caucase-updater',
url=ssl_parameter_dict['caucase-url'], url=ssl_parameter_dict['caucase-url'],
data_dir='${directory:srv}/caucase-updater', data_dir='${directory:srv}/caucase-updater',
crt_path='${apache-conf-ssl:cert}', crt_path='${apache-conf-ssl:caucase-cert}',
ca_path='${directory:srv}/caucase-updater/ca.crt', ca_path='${directory:srv}/caucase-updater/ca.crt',
crl_path='${directory:srv}/caucase-updater/crl.pem', crl_path='${directory:srv}/caucase-updater/crl.pem',
key_path='${apache-conf-ssl:key}', key_path='${apache-conf-ssl:caucase-key}',
on_renew='${apache-graceful:output}', on_renew='${apache-graceful:output}',
max_sleep=ssl_parameter_dict.get('max-crl-update-delay', 1.0), max_sleep=ssl_parameter_dict.get('max-crl-update-delay', 1.0),
template_csr_pem=ssl_parameter_dict.get('csr'), template_csr_pem=ssl_parameter_dict.get('csr'),
template_csr=None if ssl_parameter_dict.get('csr') else '${balancer-csr-request:csr}',
openssl=parameter_dict['openssl'] ~ '/bin/openssl', openssl=parameter_dict['openssl'] ~ '/bin/openssl',
)}} )}}
{# XXX we don't use caucase yet.
{% do section('caucase-updater') -%} {% do section('caucase-updater') -%}
{% do section('caucase-updater-promise') -%} {% do section('caucase-updater-promise') -%}
#}
{% set frontend_caucase_url_hash_list = [] -%} {% set frontend_caucase_url_hash_list = [] -%}
{% for frontend_caucase_url in frontend_caucase_url_list -%} {% for frontend_caucase_url in frontend_caucase_url_list -%}
...@@ -209,6 +178,10 @@ hash-files = ${haproxy-cfg:rendered} ...@@ -209,6 +178,10 @@ hash-files = ${haproxy-cfg:rendered}
[apache-conf-ssl] [apache-conf-ssl]
cert = ${directory:apache-conf}/apache.crt cert = ${directory:apache-conf}/apache.crt
key = ${directory:apache-conf}/apache.pem key = ${directory:apache-conf}/apache.pem
# XXX caucase is/was buggy and this certificate does not match key for instances
# that were updated, so don't use it yet.
caucase-cert = ${directory:apache-conf}/apache-caucase.crt
caucase-key = ${directory:apache-conf}/apache-caucase.pem
{% if frontend_caucase_url_list -%} {% if frontend_caucase_url_list -%}
depends = ${caucase-updater-housekeeper-run:recipe} depends = ${caucase-updater-housekeeper-run:recipe}
ca-cert-dir = ${directory:apache-ca-cert-dir} ca-cert-dir = ${directory:apache-ca-cert-dir}
...@@ -231,6 +204,19 @@ context = key content {{content_section_name}}:content ...@@ -231,6 +204,19 @@ context = key content {{content_section_name}}:content
mode = {{ mode }} mode = {{ mode }}
{%- endmacro %} {%- endmacro %}
[apache-ssl]
{% if ssl_parameter_dict.get('key') -%}
key = ${apache-ssl-key:rendered}
cert = ${apache-ssl-cert:rendered}
{{ simplefile('apache-ssl-key', '${apache-conf-ssl:key}', ssl_parameter_dict['key']) }}
{{ simplefile('apache-ssl-cert', '${apache-conf-ssl:cert}', ssl_parameter_dict['cert']) }}
{% else %}
recipe = plone.recipe.command
command = "{{ parameter_dict['openssl'] }}/bin/openssl" req -newkey rsa -batch -new -x509 -days 3650 -nodes -keyout "${:key}" -out "${:cert}"
key = ${apache-conf-ssl:key}
cert = ${apache-conf-ssl:cert}
{%- endif %}
[apache-conf-parameter-dict] [apache-conf-parameter-dict]
backend-list = {{ dumps(apache_dict.values()) }} backend-list = {{ dumps(apache_dict.values()) }}
zope-virtualhost-monster-backend-dict = {{ dumps(zope_virtualhost_monster_backend_dict) }} zope-virtualhost-monster-backend-dict = {{ dumps(zope_virtualhost_monster_backend_dict) }}
...@@ -242,8 +228,8 @@ access-log = ${directory:log}/apache-access.log ...@@ -242,8 +228,8 @@ access-log = ${directory:log}/apache-access.log
# Apache 2.4's default value (60 seconds) can be a bit too short # Apache 2.4's default value (60 seconds) can be a bit too short
timeout = 300 timeout = 300
# Basic SSL server configuration # Basic SSL server configuration
cert = ${apache-conf-ssl:cert} cert = ${apache-ssl:cert}
key = ${apache-conf-ssl:key} key = ${apache-ssl:key}
cipher = cipher =
ssl-session-cache = ${directory:log}/apache-ssl-session-cache ssl-session-cache = ${directory:log}/apache-ssl-session-cache
{% if frontend_caucase_url_list -%} {% if frontend_caucase_url_list -%}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment