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 @@
# not need these here).
[template]
filename = instance.cfg
md5sum = e1dd16a6f50468959b5c4572b8c82f23
md5sum = cfb3bf67b11e5b1278d94f7e729d740c
[json-test-template]
_update_hash_filename_ = json-test-template.json.in.jinja2
......@@ -26,7 +26,7 @@ md5sum = 6b933beb0744d97c7760e4601298e137
[template-node-monitoring]
_update_hash_filename_ = instance-node-monitoring.jinja2.cfg
md5sum = 2d8bd1224472983e54f36770d3e3f969
md5sum = 6dbc34d9052989225ada3b2a2d0b588c
[network-bench-cfg]
filename = network_bench.cfg.in
......
......@@ -146,6 +146,21 @@
"title": "Boolean to display prediction (unit: N/A)",
"description": "Enable prediction display by setting boolean to True (unit: N/A)",
"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 =
check-network-errors.py
check-network-transit.py
check-cpu-load.py
check-re6stnet-certificate.py
publish-connection-information
extends = {{ monitor_template }}
......@@ -105,3 +106,15 @@ config-last-transit-file = ${directory:var}/promise_network_last_transit_file
promise = check_server_cpu_load
config-frequency = {{ slapparameter_dict.get("promise_cpu_load_frequency", 3) }}
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 =
<= instance-template
url = ${template-node-monitoring:target}
extra-context =
import os_module os
raw buildout_directory ${buildout:directory}
section slap_connection slap-connection
......
......@@ -25,16 +25,26 @@
#
##############################################################################
import datetime
import glob
import hashlib
import json
import os
import re
import requests
import shutil
import subprocess
import tempfile
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.testing.testcase import makeModuleSetUpAndTestCaseClass
from slapos.util import bytes2str
setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath(
......@@ -571,3 +581,131 @@ class TestNodeMonitoring(SlapOSInstanceTestCase):
def test_node_monitoring_instance(self):
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