Commit a07fa66b authored by Jérome Perrin's avatar Jérome Perrin

dufs, restic: Use hash-existing-file to restart services after certificate renewal

See merge request nexedi/slapos!1407
parents 70b7c017 81ce2902
Pipeline #29122 failed with stage
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
[instance.cfg.in] [instance.cfg.in]
filename = instance.cfg.in filename = instance.cfg.in
md5sum = 9ed5d03f4f0cdc022f28b39e8ff1323e md5sum = 6edf5c64bf25dfd2e6e8a4e74c9b9812
...@@ -55,7 +55,20 @@ dash_path = {{ dash_bin }} ...@@ -55,7 +55,20 @@ dash_path = {{ dash_bin }}
curl_path = {{ curl_bin }} curl_path = {{ curl_bin }}
# Caucase # Caucase
[dufs-certificate-init-certificate]
recipe = slapos.recipe.build
init =
# pre-create a file at the path of the certificate,
# so that we can use hash-existing-files options
import pathlib
cert_file = pathlib.Path(self.buildout['dufs-certificate']['cert-file'])
if not cert_file.parent.exists():
cert_file.parent.mkdir()
if not cert_file.exists():
cert_file.touch()
[dufs-certificate] [dufs-certificate]
init = ${dufs-certificate-init-certificate:init}
key-file = ${directory:etc}/${:_buildout_section_name_}.key key-file = ${directory:etc}/${:_buildout_section_name_}.key
cert-file = ${directory:etc}/${:_buildout_section_name_}.crt cert-file = ${directory:etc}/${:_buildout_section_name_}.crt
common-name = ${:_buildout_section_name_} common-name = ${:_buildout_section_name_}
...@@ -154,6 +167,8 @@ wrapper-path = ${directory:service}/${:_buildout_section_name_} ...@@ -154,6 +167,8 @@ wrapper-path = ${directory:service}/${:_buildout_section_name_}
port = 19080 port = 19080
ip = ${instance-parameter:ipv6-random} ip = ${instance-parameter:ipv6-random}
url = https://[${:ip}]:${:port} url = https://[${:ip}]:${:port}
hash-existing-files =
${dufs-certificate:cert-file}
[dufs-listen-promise] [dufs-listen-promise]
<= check-port-listening-promise <= check-port-listening-promise
......
...@@ -10,7 +10,6 @@ parts = ...@@ -10,7 +10,6 @@ parts =
caucase-eggs caucase-eggs
instance.cfg.in instance.cfg.in
[dufs] [dufs]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
shared = true shared = true
......
...@@ -25,8 +25,11 @@ ...@@ -25,8 +25,11 @@
# #
############################################################################## ##############################################################################
import contextlib
import io import io
import os import os
import pathlib
import subprocess
import tempfile import tempfile
import urllib.parse import urllib.parse
...@@ -115,3 +118,38 @@ class TestFileServer(SlapOSInstanceTestCase): ...@@ -115,3 +118,38 @@ class TestFileServer(SlapOSInstanceTestCase):
) )
self.assertEqual(resp.text, 'hello') self.assertEqual(resp.text, 'hello')
self.assertEqual(resp.status_code, requests.codes.ok) self.assertEqual(resp.status_code, requests.codes.ok)
def test_renew_certificate(self):
def _getpeercert():
# XXX low level way to get get the server certificate
with requests.Session() as session:
pool = session.get(
self.connection_parameters['public-url'],
verify=self.ca_cert,
).raw._pool.pool
with contextlib.closing(pool.get()) as cnx:
return cnx.sock._sslobj.getpeercert()
cert_before = _getpeercert()
# execute certificate updater two month later, when it's time to renew certificate.
# use a timeout, because this service runs forever
subprocess.run(
(
'timeout',
'5',
'faketime',
'+2 months',
os.path.join(
self.computer_partition_root_path,
'etc/service/dufs-certificate-updater'),
),
capture_output=not self._debug,
)
# reprocess instance to get the new certificate, after removing the timestamp
# to force execution
(pathlib.Path(self.computer_partition_root_path) / '.timestamp').unlink()
self.waitForInstance()
cert_after = _getpeercert()
self.assertNotEqual(cert_before['notAfter'], cert_after['notAfter'])
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
[instance.cfg.in] [instance.cfg.in]
filename = instance.cfg.in filename = instance.cfg.in
md5sum = 573e23c88fea6a11ab5c79b1eb106601 md5sum = 69237df07b8819e2eb683702b8cd199a
...@@ -54,7 +54,20 @@ dash_path = {{ dash_bin }} ...@@ -54,7 +54,20 @@ dash_path = {{ dash_bin }}
curl_path = {{ curl_bin }} curl_path = {{ curl_bin }}
# Caucase # Caucase
[rest-server-certificate-init-certificate]
recipe = slapos.recipe.build
init =
# pre-create a file at the path of the certificate,
# so that we can use hash-existing-files options
import pathlib
cert_file = pathlib.Path(self.buildout['rest-server-certificate']['cert-file'])
if not cert_file.parent.exists():
cert_file.parent.mkdir()
if not cert_file.exists():
cert_file.touch()
[rest-server-certificate] [rest-server-certificate]
init = ${rest-server-certificate-init-certificate:init}
key-file = ${directory:etc}/${:_buildout_section_name_}.key key-file = ${directory:etc}/${:_buildout_section_name_}.key
cert-file = ${directory:etc}/${:_buildout_section_name_}.crt cert-file = ${directory:etc}/${:_buildout_section_name_}.crt
common-name = ${:_buildout_section_name_} common-name = ${:_buildout_section_name_}
...@@ -165,6 +178,8 @@ ip = ${instance-parameter:ipv6-random} ...@@ -165,6 +178,8 @@ ip = ${instance-parameter:ipv6-random}
url = https://[${:ip}]:${:port} url = https://[${:ip}]:${:port}
depends = depends =
${rest-server-htpassword:recipe} ${rest-server-htpassword:recipe}
hash-existing-files =
${rest-server-certificate:cert-file}
[rest-server-listen-promise] [rest-server-listen-promise]
<= check-port-listening-promise <= check-port-listening-promise
......
...@@ -25,8 +25,9 @@ ...@@ -25,8 +25,9 @@
# #
############################################################################## ##############################################################################
import glob import contextlib
import os import os
import pathlib
import subprocess import subprocess
import tempfile import tempfile
import urllib.parse import urllib.parse
...@@ -35,6 +36,7 @@ import requests ...@@ -35,6 +36,7 @@ import requests
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass( setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath( os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'software.cfg'))) os.path.join(os.path.dirname(__file__), '..', 'software.cfg')))
...@@ -143,3 +145,38 @@ class TestResticRestServer(SlapOSInstanceTestCase): ...@@ -143,3 +145,38 @@ class TestResticRestServer(SlapOSInstanceTestCase):
self.assertIn('restoring <Snapshot', out) self.assertIn('restoring <Snapshot', out)
with open(os.path.join(restore_directory, backup_path, 'data')) as f: with open(os.path.join(restore_directory, backup_path, 'data')) as f:
self.assertEqual(f.read(), 'data to backup') self.assertEqual(f.read(), 'data to backup')
def test_renew_certificate(self):
def _getpeercert():
# XXX low level way to get get the server certificate
with requests.Session() as session:
pool = session.get(
self.connection_parameters['url'],
verify=self.ca_cert,
).raw._pool.pool
with contextlib.closing(pool.get()) as cnx:
return cnx.sock._sslobj.getpeercert()
cert_before = _getpeercert()
# execute certificate updater two month later, when it's time to renew certificate.
# use a timeout, because this service runs forever
subprocess.run(
(
'timeout',
'5',
'faketime',
'+2 months',
os.path.join(
self.computer_partition_root_path,
'etc/service/rest-server-certificate-updater'),
),
capture_output=not self._debug,
)
# reprocess instance to get the new certificate, after removing the timestamp
# to force execution
(pathlib.Path(self.computer_partition_root_path) / '.timestamp').unlink()
self.waitForInstance()
cert_after = _getpeercert()
self.assertNotEqual(cert_before['notAfter'], cert_after['notAfter'])
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