Commit 00ac69e6 authored by Łukasz Nowak's avatar Łukasz Nowak

monitor: Implement re6stnet certificate check

parent 06f01fd6
Pipeline #27251 passed with stage
in 0 seconds
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
# not need these here). # not need these here).
[template] [template]
filename = instance.cfg filename = instance.cfg
md5sum = e1dd16a6f50468959b5c4572b8c82f23 md5sum = cfb3bf67b11e5b1278d94f7e729d740c
[json-test-template] [json-test-template]
_update_hash_filename_ = json-test-template.json.in.jinja2 _update_hash_filename_ = json-test-template.json.in.jinja2
...@@ -26,7 +26,7 @@ md5sum = 6b933beb0744d97c7760e4601298e137 ...@@ -26,7 +26,7 @@ md5sum = 6b933beb0744d97c7760e4601298e137
[template-node-monitoring] [template-node-monitoring]
_update_hash_filename_ = instance-node-monitoring.jinja2.cfg _update_hash_filename_ = instance-node-monitoring.jinja2.cfg
md5sum = 2d8bd1224472983e54f36770d3e3f969 md5sum = 6dbc34d9052989225ada3b2a2d0b588c
[network-bench-cfg] [network-bench-cfg]
filename = network_bench.cfg.in filename = network_bench.cfg.in
......
...@@ -146,6 +146,21 @@ ...@@ -146,6 +146,21 @@
"title": "Boolean to display prediction (unit: N/A)", "title": "Boolean to display prediction (unit: N/A)",
"description": "Enable prediction display by setting boolean to True (unit: N/A)", "description": "Enable prediction display by setting boolean to True (unit: N/A)",
"type": "boolean" "type": "boolean"
},
"promise_re6stnet_config_directory": {
"default": "/etc/re6stnet/",
"title": "Directory of re6stnet configuration on the node",
"type": "string"
},
"promise_re6stnet_certificate_file": {
"default": "cert.crt",
"title": "Filename of the re6stnet certificate in the re6stnet directory",
"type": "string"
},
"re6stnet_certificate_expiration_delay": {
"default": 15,
"title": "Days before expiration until certificate is considered valid",
"type": "number"
} }
} }
} }
...@@ -8,6 +8,7 @@ parts = ...@@ -8,6 +8,7 @@ parts =
check-network-errors.py check-network-errors.py
check-network-transit.py check-network-transit.py
check-cpu-load.py check-cpu-load.py
check-re6stnet-certificate.py
publish-connection-information publish-connection-information
extends = {{ monitor_template }} extends = {{ monitor_template }}
...@@ -105,3 +106,15 @@ config-last-transit-file = ${directory:var}/promise_network_last_transit_file ...@@ -105,3 +106,15 @@ config-last-transit-file = ${directory:var}/promise_network_last_transit_file
promise = check_server_cpu_load promise = check_server_cpu_load
config-frequency = {{ slapparameter_dict.get("promise_cpu_load_frequency", 3) }} config-frequency = {{ slapparameter_dict.get("promise_cpu_load_frequency", 3) }}
config-cpu-load-threshold = {{ slapparameter_dict.get("promise_cpu_load_threshold", 1.5) }} config-cpu-load-threshold = {{ slapparameter_dict.get("promise_cpu_load_threshold", 1.5) }}
[check-re6stnet-certificate.py]
<= macro.promise
{% set RE6STNET_CONFIG_DIR = slapparameter_dict.get('promise_re6stnet_config_directory', '/etc/re6stnet') %}
{% if os_module.path.exists(os_module.path.join(RE6STNET_CONFIG_DIR, 're6stnet.conf')) %}
promise = check_certificate
config-certificate = {{ os_module.path.join(RE6STNET_CONFIG_DIR, slapparameter_dict.get('promise_re6stnet_certificate_file', 'cert.crt')) }}
config-certificate-expiration-days = {{ slapparameter_dict.get('re6stnet_certificate_expiration_delay', '15') }}
{% else %}
promise = check_command_execute
config-command = echo "re6stnet disabled on the node"
{% endif %}
...@@ -30,6 +30,7 @@ context = ...@@ -30,6 +30,7 @@ context =
<= instance-template <= instance-template
url = ${template-node-monitoring:target} url = ${template-node-monitoring:target}
extra-context = extra-context =
import os_module os
raw buildout_directory ${buildout:directory} raw buildout_directory ${buildout:directory}
section slap_connection slap-connection section slap_connection slap-connection
......
...@@ -25,16 +25,26 @@ ...@@ -25,16 +25,26 @@
# #
############################################################################## ##############################################################################
import datetime
import glob import glob
import hashlib import hashlib
import json import json
import os import os
import re import re
import requests import requests
import shutil
import subprocess import subprocess
import tempfile
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from slapos.recipe.librecipe import generateHashFromFiles from slapos.recipe.librecipe import generateHashFromFiles
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
from slapos.util import bytes2str
setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass( setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath( os.path.abspath(
...@@ -571,3 +581,131 @@ class TestNodeMonitoring(SlapOSInstanceTestCase): ...@@ -571,3 +581,131 @@ class TestNodeMonitoring(SlapOSInstanceTestCase):
def test_node_monitoring_instance(self): def test_node_monitoring_instance(self):
pass pass
class TestNodeMonitoringRe6stCertificate(SlapOSInstanceTestCase):
@classmethod
def getInstanceSoftwareType(cls):
return 'default'
def reRequestInstance(self, partition_parameter_kw=None, state='started'):
if partition_parameter_kw is None:
partition_parameter_kw = {}
software_url = self.getSoftwareURL()
software_type = self.getInstanceSoftwareType()
return self.slap.request(
software_release=software_url,
software_type=software_type,
partition_reference=self.default_partition_reference,
partition_parameter_kw=partition_parameter_kw,
state=state)
def test_default(self):
self.reRequestInstance()
self.slap.waitForInstance()
promise = os.path.join(
self.computer_partition_root_path, 'etc', 'plugin',
'check-re6stnet-certificate.py')
self.assertTrue(os.path.exists(promise))
with open(promise) as fh:
promise_content = fh.read()
# this test depends on OS level configuration
if os.path.exists('/etc/re6stnet/cert.crt'):
self.assertIn(
"extra_config_dict = {'certificate': '/etc/re6stnet/cert.crt', "
"'certificate-expiration-days': '15'}", promise_content)
self.assertIn(
"from slapos.promise.plugin.check_certificate import RunPromise",
promise_content)
else:
self.assertIn(
"extra_config_dict = {'command': 'echo \"re6stnet disabled on the "
"node\"'}", promise_content)
self.assertIn(
"from slapos.promise.plugin.check_command_execute import RunPromise",
promise_content)
def createKey(self):
key = rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend())
key_pem = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
return key, key_pem
def createCertificate(self, key, days=30):
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"FR"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Nord"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Lille"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Nexedi"),
x509.NameAttribute(NameOID.COMMON_NAME, u"Common"),
])
certificate = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days)
).sign(key, hashes.SHA256(), default_backend())
certificate_pem = certificate.public_bytes(
encoding=serialization.Encoding.PEM)
return certificate, certificate_pem
def createKeyCertificate(self, certificate_path):
key, key_pem = self.createKey()
certificate, certificate_pem = self.createCertificate(key, 30)
with open(certificate_path, 'w') as fh:
fh.write(bytes2str(key_pem))
with open(certificate_path, 'a') as fh:
fh.write(bytes2str(certificate_pem))
def setUp(self):
super().setUp()
self.re6st_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.re6st_dir)
def test_re6st_dir(self, days=None, filename='cert.crt'):
self.createKeyCertificate(os.path.join(self.re6st_dir, filename))
with open(os.path.join(self.re6st_dir, 're6stnet.conf'), 'w') as fh:
fh.write("")
partition_parameter_kw = {
'promise_re6stnet_config_directory': self.re6st_dir
}
if filename != 'cert.crt':
partition_parameter_kw['promise_re6stnet_certificate_file'] = filename
if days is not None:
partition_parameter_kw['re6stnet_certificate_expiration_delay'] = days
self.reRequestInstance(
partition_parameter_kw={'_': json.dumps(partition_parameter_kw)})
self.slap.waitForInstance()
promise = os.path.join(
self.computer_partition_root_path, 'etc', 'plugin',
'check-re6stnet-certificate.py')
self.assertTrue(os.path.exists(promise))
with open(promise) as fh:
promise_content = fh.read()
self.assertIn(
"""extra_config_dict = { 'certificate': '%(re6st_dir)s/%(filename)s',
'certificate-expiration-days': '%(days)s'}""" % {
're6st_dir': self.re6st_dir,
'days': days or 15,
'filename': filename},
promise_content)
self.assertIn(
"from slapos.promise.plugin.check_certificate import RunPromise",
promise_content)
def test_re6st_dir_expiration(self):
self.test_re6st_dir(days=10)
def test_re6st_dir_filename(self):
self.test_re6st_dir(filename="cert.pem")
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