[add] check:tls_certificate [add] check:generic_remote

This commit is contained in:
Christian Fraß 2023-07-23 09:33:04 +02:00
parent d75bec75a9
commit 054208a79e
12 changed files with 616 additions and 515 deletions

View file

@ -26,7 +26,7 @@
"checks.tls_certificate.not_obtainable": "TLS-Zertifikat nicht abrufbar; evtl. bereits ausgelaufen", "checks.tls_certificate.not_obtainable": "TLS-Zertifikat nicht abrufbar; evtl. bereits ausgelaufen",
"checks.tls_certificate.expires_soon": "TLS-Zertifikat läuft bald aus", "checks.tls_certificate.expires_soon": "TLS-Zertifikat läuft bald aus",
"checks.generic_remote.overflow": "Laufwerk fast voll", "checks.generic_remote.overflow": "Laufwerk fast voll",
"checks.http_request.request_failed": "HTTP-Abfrage fehlgeschlagen", "checks.http_request.request_failed": "HTTP-Abfrage fehlgeschlagen: {{reason}}",
"checks.http_request.status_code_mismatch": "Status-Code {{status_code_actual}} stimmt nicht mit dem erwarteten Wert {{status_code_expected}} überein", "checks.http_request.status_code_mismatch": "Status-Code {{status_code_actual}} stimmt nicht mit dem erwarteten Wert {{status_code_expected}} überein",
"checks.http_request.header_missing": "Header '{{key}}' ist nicht gesetzt und hat somit nicht den erwarteten Wert '{{value_expected}}'", "checks.http_request.header_missing": "Header '{{key}}' ist nicht gesetzt und hat somit nicht den erwarteten Wert '{{value_expected}}'",
"checks.http_request.header_value_mismatch": "Header-Wert für Schlüssel '{{key}}' '{{value_actual}}' stimmt nicht mit erwartetem Wert '{{value_expected}}' überein", "checks.http_request.header_value_mismatch": "Header-Wert für Schlüssel '{{key}}' '{{value_actual}}' stimmt nicht mit erwartetem Wert '{{value_expected}}' überein",

View file

@ -26,7 +26,7 @@
"checks.tls_certificate.not_obtainable": "TLS certificate not obtainable; maybe already expired", "checks.tls_certificate.not_obtainable": "TLS certificate not obtainable; maybe already expired",
"checks.tls_certificate.expires_soon": "TLS certificate will expire soon", "checks.tls_certificate.expires_soon": "TLS certificate will expire soon",
"checks.generic_remote.overflow": "disk drive almost full", "checks.generic_remote.overflow": "disk drive almost full",
"checks.http_request.request_failed": "HTTP request failed", "checks.http_request.request_failed": "HTTP request failed: {{reason}}",
"checks.http_request.status_code_mismatch": "actual status code {{status_code_actual}} does not match expected value {{status_code_expected}}", "checks.http_request.status_code_mismatch": "actual status code {{status_code_actual}} does not match expected value {{status_code_expected}}",
"checks.http_request.header_missing": "header '{{key}}' is unset and hence does not match the expected value '{{value_expected}}'", "checks.http_request.header_missing": "header '{{key}}' is unset and hence does not match the expected value '{{value_expected}}'",
"checks.http_request.header_value_mismatch": "actual header value for key '{{key}}' '{{value_actual}}' does not match the expected value {{value_expected}}", "checks.http_request.header_value_mismatch": "actual header value for key '{{key}}' '{{value_actual}}' does not match the expected value {{value_expected}}",

View file

@ -1,254 +0,0 @@
class implementation_check_kind_file_state(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"path": {
"type": "string"
},
"exist_mode": {
"description": "whether the file is supposed to exist or not",
"type": "boolean",
"default": True,
},
"exist_critical": {
"description": "whether a violation of the extist state (parameter 'exist_mode') shall be considered as critical (true) or concerning (false)",
"type": "boolean",
"default": True,
},
"age_threshold_concerning": {
"description": "in seconds; ignored if 'exist_mode' is set to false",
"type": ["null", "integer"],
"exclusiveMinimum": 0,
"default": None,
},
"age_threshold_critical": {
"description": "in seconds; ignored if 'exist_mode' is set to false",
"type": ["null", "integer"],
"exclusiveMinimum": 0,
"default": None,
},
"size_threshold_concerning": {
"description": "in bytes; ignored if 'exist_mode' is set to false",
"type": ["null", "integer"],
"exclusiveMinimum": 0,
"default": None,
},
"size_threshold_critical": {
"description": "in bytes; ignored if 'exist_mode' is set to false",
"type": ["null", "integer"],
"exclusiveMinimum": 0,
"default": None,
},
# deprecated
"strict": {
"deprecated": True,
"description": "",
"type": "boolean",
"default": True,
},
"exist": {
"deprecated": True,
"description": "",
"type": "boolean",
"default": True,
},
"age_threshold": {
"deprecated": True,
"description": "",
"type": ["null", "integer"],
"exclusiveMinimum": 0,
"default": None,
},
"size_threshold": {
"deprecated": True,
"description": "",
"type": ["null", "integer"],
"exclusiveMinimum": 0,
"default": None,
},
},
"required": [
"path",
]
}
'''
[implementation]
'''
def normalize_order_node(self, node):
version = (
"v1"
if (not ("exist_mode" in node)) else
"v2"
)
if (version == "v1"):
if ("path" not in node):
raise ValueError("missing mandatory field 'path'")
else:
node_ = dict_merge(
{
"critical": True,
"exist": True,
"age_threshold": None,
"size_threshold": None,
},
node
)
return {
"exist_mode": node_["exist"],
"exist_critical": node_["strict"],
"age_threshold_concerning": (
None
if node_["strict"] else
node_["age_threshold"]
),
"age_threshold_critical": (
node_["age_threshold"]
if node_["strict"] else
None
),
"size_threshold_concerning": (
None
if node_["strict"] else
node_["age_threshold"]
),
"size_threshold_critical": (
node_["age_threshold"]
if node_["strict"] else
None
),
}
elif (version == "v2"):
if ("path" not in node):
raise ValueError("missing mandatory field 'path'")
else:
node_ = dict_merge(
{
"exist_mode": True,
"exist_critical": True,
"age_threshold_concerning": None,
"age_threshold_critical": None,
"size_threshold_concerning": None,
"size_threshold_critical": None,
},
node
)
return node_
else:
raise ValueError("unhandled")
'''
[implementation]
'''
def run(self, parameters):
condition = enum_condition.ok
faults = []
data = {}
exists = _os.path.exists(parameters["path"])
if (not parameters["exist_mode"]):
if (exists):
condition = (
enum_condition.critical
if parameters["exist_critical"] else
enum_condition.concerning
)
faults.append(translation_get("checks.file_state.exists"))
else:
pass
else:
if (not exists):
condition = (
enum_condition.critical
if parameters["exist_critical"] else
enum_condition.concerning
)
faults.append(translation_get("checks.file_state.missing"))
else:
stat = _os.stat(parameters["path"])
## age
if True:
timestamp_this = get_current_timestamp()
timestamp_that = int(stat.st_atime)
age = (timestamp_this - timestamp_that)
if (age < 0):
condition = enum_condition.critical
faults.append(translation_get("checks.file_state.timestamp_implausible"))
else:
if (
(parameters["age_threshold_critical"] is not None)
and
(age > parameters["age_threshold_critical"])
):
condition = enum_condition.critical
faults.append(translation_get("checks.file_state.too_old"))
else:
if (
(parameters["age_threshold_concerning"] is not None)
and
(age > parameters["age_threshold_concerning"])
):
condition = enum_condition.concerning
faults.append(translation_get("checks.file_state.too_old"))
else:
pass
data = dict_merge(
data,
{
"timestamp_of_checking_instance": timestamp_this,
"timestamp_of_file": timestamp_that,
"age_value_in_seconds": age,
"age_threshold_in_seconds_concerning": parameters["age_threshold_concerning"],
"age_threshold_in_seconds_concerning": parameters["age_threshold_critical"],
}
)
## size
if True:
size = stat.st_size
if (size < 0):
condition = enum_condition.critical
faults.append(translation_get("checks.file_state.size_implausible"))
else:
if (
(parameters["size_threshold_critical"] is not None)
and
(size > parameters["size_threshold_critical"])
):
condition = enum_condition.critical
faults.append(translation_get("checks.file_state.too_big"))
else:
if (
(parameters["size_threshold_concerning"] is not None)
and
(size > parameters["size_threshold_concerning"])
):
condition = enum_condition.concerning
faults.append(translation_get("checks.file_state.too_big"))
else:
pass
data = dict_merge(
data,
{
"size_value_in_bytes": size,
"size_threshold_in_bytes": parameters["size_threshold"],
}
)
return {
"condition": condition,
"info": {
"path": parameters["path"],
"faults": faults,
"data": data,
}
}

View file

@ -1,63 +0,0 @@
class implementation_check_kind_script(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"path": {
"type": "string"
},
"arguments": {
"type": "array",
"item": {
"type": "string"
}
},
},
"required": [
"path",
]
}
'''
[implementation]
'''
def normalize_order_node(self, node):
return dict_merge(
{
},
node
)
'''
[implementation]
'''
def run(self, parameters):
result = shell_command(
" ".join([parameters["path"]] + parameters["arguments"])
)
if (result["return_code"] == 0):
condition = enum_condition.ok
elif (result["return_code"] == 1):
condition = enum_condition.unknown
elif (result["return_code"] == 2):
condition = enum_condition.concerning
elif (result["return_code"] == 3):
condition = enum_condition.critical
else:
# raise ValueError("invalid exit code: %i" % result.returncode)
condition = enum_condition.unknown
return {
"condition": condition,
"info": {
"stdout": result["stdout"],
"stderr": result["stderr"],
},
}

View file

@ -1,172 +0,0 @@
'''
todo: allow_self_signed
todo: allow_bad_domain
todo:
'''
class implementation_check_kind_tls_certificate(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer",
"default": 443
},
"expiry_threshold_concerning": {
"description": "in days; allowed amount of valid days before the certificate expires; threshold for condition 'concerning'; 'null' means 'report at no value'",
"type": ["null", "integer"],
"default": 7,
"minimum": 0
},
"expiry_threshold_critical": {
"description": "in days; allowed amount of valid days before the certificate expires; threshold for condition 'critical'; 'null' means 'report at no value'",
"type": ["null", "integer"],
"default": 1,
"minimum": 0
},
"expiry_threshold": {
"deprecated": True,
"description": "",
"type": ["null", "integer"],
"minimum": 0,
"default": None,
},
"strict": {
"deprecated": True,
"description": "",
"type": ["null", "boolean"],
"default": None,
},
},
"required": [
"host",
]
}
'''
[implementation]
'''
def normalize_order_node(self, node):
version = (
"v1"
if (
(not ("expiry_threshold_concerning" in node))
and
(not ("expiry_threshold_critical" in node))
) else
"v2"
)
if (version == "v1"):
if (not "host" in node):
raise ValueError("missing mandatory field 'host'")
else:
node_ = dict_merge(
{
"port": 443,
"expiry_threshold": 7,
"strict": True,
# "allow_self_signed": False,
# "allow_bad_domain": False,
},
node
)
return {
"port": node_["port"],
"expiry_threshold_concerning": (
None
if node_["strict"] else
node_["expiry_threshold"]
),
"expiry_threshold_critical": (
node_["expiry_threshold"]
if node_["strict"] else
None
),
}
elif (version == "v2"):
if (not "host" in node):
raise ValueError("missing mandatory field 'host'")
else:
node_ = dict_merge(
{
"port": 443,
"expiry_threshold_concerning": 7,
"expiry_threshold_critical": 1,
# "allow_self_signed": False,
# "allow_bad_domain": False,
},
node
)
return node_
else:
raise ValueError("unhandled")
'''
[implementation]
'''
def run(self, parameters):
faults = []
data = {}
context = _ssl.create_default_context()
condition = enum_condition.ok
try:
socket = _socket.create_connection((parameters["host"], parameters["port"], ))
socket_wrapped = context.wrap_socket(socket, server_hostname = parameters["host"])
version = socket_wrapped.version()
stuff = socket_wrapped.getpeercert(False)
except _ssl.SSLCertVerificationError as error:
version = None
stuff = None
if (stuff is None):
faults.append(translation_get("checks.tls_certificate.not_obtainable"))
condition = enum_condition.critical
else:
# version == "TLSv1.3"
expiry_timestamp = _ssl.cert_time_to_seconds(stuff["notAfter"])
current_timestamp = get_current_timestamp()
days = _math.ceil((expiry_timestamp - current_timestamp) / (60 * 60 * 24))
data = dict_merge(
data,
{
"expiry_timestamp": expiry_timestamp,
"days": days,
},
)
if (
(parameters["expiry_threshold_critical"] is not None)
and
(days <= parameters["expiry_threshold_critical"])
):
faults.append(translation_get("checks.tls_certificate.expires_soon"))
condition = enum_condition.critical
else:
if (
(parameters["expiry_threshold_concerning"] is not None)
and
(days <= parameters["expiry_threshold_concerning"])
):
faults.append(translation_get("checks.tls_certificate.expires_soon"))
condition = enum_condition.concerning
else:
pass
return {
"condition": condition,
"info": {
"host": parameters["host"],
"port": parameters["port"],
"faults": faults,
"data": data,
}
}

View file

@ -0,0 +1,282 @@
namespace _heimdall.check_kinds.generic_remote
{
/**
*/
function parameters_schema(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"type": "object",
"additionalProperties": false,
"properties": {
"host" : {
"type" : "string"
},
"ssh_port": {
"anyOf": [
{
"type": "null",
"default": null
},
{
"type": "integer",
"default": null
},
]
},
"ssh_user" : {
"anyOf": [
{
"type": "null",
"default": null
},
{
"type": "integer",
"default": null
},
]
},
"ssh_key" : {
"anyOf": [
{
"type": "null",
"default": null
},
{
"type": "integer",
"default": null
},
]
},
"mount_point" : {
"type" : "string",
"default" : "/"
},
"threshold" : {
"type" : "integer",
"default" : 95,
"description" : "maximaler Füllstand in Prozent"
},
"critical": {
"description": "whether a violation of this check shall be leveled as critical instead of concerning",
"type": "boolean",
"default": true
},
"strict": {
"deprecated": true,
"description": "alias for 'critical'",
"type": "boolean",
"default": true
},
},
"required": [
"host",
]
};
}
/**
*/
function normalize_order_node(
node : any
) : any
{
const version : string = (
(! ("critical" in node))
? "v1"
: "v2"
);
switch (version) {
default: {
throw (new Error("unhandled version"));
break;
}
case "v1": {
if (! ("host" in node)) {
throw (new Error("mandatory parameter \"host\" missing"));
}
else {
const node_ = lib_plankton.object.patched(
{
"ssh_port": null,
"ssh_user": null,
"ssh_key": null,
"mount_point": "/",
"threshold": 95,
"strict": false,
},
node,
true
);
return {
"ssh_port": node_["ssh_port"],
"ssh_user": node_["ssh_user"],
"ssh_key": node_["ssh_key"],
"mount_point": node_["ssh_path"],
"threshold": node_["ssh_threshold"],
"critical": node_["strict"],
};
}
break;
}
case "v2": {
if (! ("host" in node)) {
throw (new Error("mandatory parameter \"host\" missing"));
}
else {
const node_ = lib_plankton.object.patched(
{
"ssh_port": null,
"ssh_user": null,
"ssh_key": null,
"mount_point": "/",
"threshold": 95,
"critical": false,
},
node,
true
);
return node_;
}
break;
}
}
}
/**
*/
async function run(
parameters
) : Promise<_heimdall.type_result>
{
const nm_child_process = require("child_process");
const inner_command : string = lib_plankton.string.coin(
"df {{mount_point}} | tr -s \" \"",
{
"mount_point": parameters["mount_point"],
}
);
let outer_command_parts : Array<string> = [];
if (true) {
outer_command_parts.push("ssh");
}
if (true) {
outer_command_parts.push(lib_plankton.string.coin("{{host}}", {"host": parameters["host"]}));
}
if (parameters["ssh_port"] !== null) {
outer_command_parts.push(lib_plankton.string.coin("-p {{port}}", {"port": parameters["ssh_port"].toFixed(0)}));
}
if (parameters["ssh_user"] !== null) {
outer_command_parts.push(lib_plankton.string.coin("-l {{user}}", {"user": parameters["ssh_user"]}));
}
if (parameters["ssh_key"] !== null) {
outer_command_parts.push(lib_plankton.string.coin("-i {{key}}", {"key": parameters["ssh_key"]}));
}
if (true) {
outer_command_parts.push(lib_plankton.string.coin("-o BatchMode=yes", {}));
}
if (true) {
outer_command_parts.push(lib_plankton.string.coin("'{{inner_command}}'", {"inner_command": inner_command}));
}
const outer_command : string = outer_command_parts.join(" ");
type type_result = {
return_code : int;
stdout : string;
stderr : string;
};
/**
* @see https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
*/
const result : type_result = await new Promise<type_result>(
(resolve, reject) => {
nm_child_process.spawnSync(
outer_command,
[],
{
},
(result) => {
if (result.error) {
reject(result.error);
}
else {
resolve(
{
"return_code": result.status,
"stdout": result.stdout,
"stderr": result.stdin,
}
);
}
}
);
}
);
if (result.return_code > 0) {
return {
"condition": _heimdall.enum_condition.unknown,
"info": {
"error": result.stderr,
}
};
}
else {
const stuff : Array<string> = result.stdout.split("\n").slice(-2)[0].split(" ");
const data = {
"device": stuff[0],
"used": parseInt(stuff[2]),
"avail": parseInt(stuff[3]),
"perc": parseInt(stuff[4].slice(-1)),
};
let faults : Array<string> = [];
if (data["perc"] > parameters["threshold"]) {
faults.push(lib_plankton.translate.get("checks.generic_remote.overflow"));
}
else {
// do nothing
}
return {
"condition": (
(faults.length <= 0)
? _heimdall.enum_condition.ok
: (
parameters["critical"]
? _heimdall.enum_condition.critical
: _heimdall.enum_condition.concerning
)
),
"info": {
"data": {
"host": parameters["host"],
"device": data["device"],
"mount_point": parameters["mount_point"],
"used": _heimdall.helpers.misc.format_bytes(data["used"]),
"available": _heimdall.helpers.misc.format_bytes(data["avail"]),
"percentage": (data["perc"].toFixed(0) + "%"),
},
"faults": faults,
},
};
}
}
/**
*/
export function check_kind_implementation(
) : type_check_kind
{
return {
"parameters_schema": parameters_schema,
"normalize_order_node": normalize_order_node,
"run": run,
};
}
}

View file

@ -177,17 +177,29 @@ namespace _heimdall.check_kinds.http_request
/** /**
*/ */
async function run( async function run(
parameters parameters : {
request : {
target : string;
method : string;
};
timeout : float;
follow_redirects : boolean;
response : {
status_code : int;
headers ?: Record<string, string>;
body_part ?: string;
};
critical : boolean;
}
) : Promise<_heimdall.type_result> ) : Promise<_heimdall.type_result>
{ {
let error : (null | Error); let error : (null | Error);
const http_request : lib_plankton.http.type_request = { const http_request : lib_plankton.http.type_request = {
"host": parameters["request"]["target"], "target": parameters.request.target,
"query": "",
"method": { "method": {
"GET": lib_plankton.http.enum_method.get, "GET": lib_plankton.http.enum_method.get,
"POST": lib_plankton.http.enum_method.post, "POST": lib_plankton.http.enum_method.post,
}[parameters["request"]["method"]], }[parameters.request.method],
"headers": {}, "headers": {},
"body": "", "body": "",
}; };
@ -196,8 +208,9 @@ namespace _heimdall.check_kinds.http_request
http_response = await lib_plankton.http.call( http_response = await lib_plankton.http.call(
http_request, http_request,
{ {
"timeout": parameters["timeout"], "timeout": /*parameters.timeout*/20.0,
"follow_redirects": parameters["follow_redirects"], "follow_redirects": parameters.follow_redirects,
"implementation": "http_module",
} }
); );
error = null; error = null;
@ -209,14 +222,14 @@ namespace _heimdall.check_kinds.http_request
if (http_response === null) { if (http_response === null) {
return { return {
"condition": ( "condition": (
parameters["strict"] parameters.critical
? _heimdall.enum_condition.critical ? _heimdall.enum_condition.critical
: _heimdall.enum_condition.concerning : _heimdall.enum_condition.concerning
), ),
"info": { "info": {
"request": parameters["request"], "request": parameters.request,
"faults": [ "faults": [
lib_plankton.translate.get("checks.http_request.request_failed"), lib_plankton.translate.get("checks.http_request.request_failed", {"reason": error.toString()}),
], ],
}, },
}; };
@ -226,15 +239,15 @@ namespace _heimdall.check_kinds.http_request
// status code // status code
{ {
if ( if (
(! ("status_code" in parameters["response"])) (! ("status_code" in parameters.response))
|| ||
(parameters["response"]["status_code"] === null) (parameters.response.status_code === null)
) { ) {
// do nothing // do nothing
} }
else { else {
const status_code_expected : int = (parameters["response"]["status_code"] as int); const status_code_expected : int = (parameters.response.status_code as int);
if (http_response.statuscode === status_code_expected) { if (! (http_response.statuscode === status_code_expected)) {
faults.push( faults.push(
lib_plankton.translate.get( lib_plankton.translate.get(
"checks.http_request.status_code_mismatch", "checks.http_request.status_code_mismatch",
@ -253,17 +266,21 @@ namespace _heimdall.check_kinds.http_request
// headers // headers
{ {
if ( if (
(! ("headers" in parameters["response"])) (! ("headers" in parameters.response))
|| ||
(parameters["response"]["headers"] === null) (parameters.response.headers === null)
) { ) {
// do nothing // do nothing
} }
else { else {
const headers_expected : Record<string, string> = (parameters["response"]["headers"] as Record<string, string>); const headers_expected : Record<string, string> = (parameters.response.headers as Record<string, string>);
Object.entries(headers_expected).forEach( Object.entries(headers_expected).forEach(
([header_key, header_value]) => { ([header_key, header_value]) => {
if (! (header_key in http_response.headers)) { if (
(! (header_key in http_response.headers))
&&
(! (header_key.toLowerCase() in http_response.headers))
) {
faults.push( faults.push(
lib_plankton.translate.get( lib_plankton.translate.get(
"checks.http_request.header_missing", "checks.http_request.header_missing",
@ -275,7 +292,11 @@ namespace _heimdall.check_kinds.http_request
); );
} }
else { else {
if (! (http_response.headers[header_key] === header_value)) { if (
(! (http_response.headers[header_key] === header_value))
&&
(! (http_response.headers[header_key.toLowerCase()].toLowerCase() === header_value.toLowerCase()))
) {
faults.push( faults.push(
lib_plankton.translate.get( lib_plankton.translate.get(
"checks.http_request.header_value_mismatch", "checks.http_request.header_value_mismatch",
@ -298,14 +319,14 @@ namespace _heimdall.check_kinds.http_request
// body // body
{ {
if ( if (
(! ("body_part" in parameters["response"])) (! ("body_part" in parameters.response))
|| ||
(parameters["response"]["body_part"] === null) (parameters.response.body_part === null)
) { ) {
// do nothing // do nothing
} }
else { else {
const body_part : string = (parameters["response"]["body_part"] as string); const body_part : string = (parameters.response.body_part as string);
if (! http_response.body.includes(body_part)) { if (! http_response.body.includes(body_part)) {
faults.push( faults.push(
lib_plankton.translate.get( lib_plankton.translate.get(
@ -327,13 +348,13 @@ namespace _heimdall.check_kinds.http_request
(faults.length <= 0) (faults.length <= 0)
? _heimdall.enum_condition.ok ? _heimdall.enum_condition.ok
: ( : (
parameters["critical"] parameters.critical
? _heimdall.enum_condition.critical ? _heimdall.enum_condition.critical
: _heimdall.enum_condition.concerning : _heimdall.enum_condition.concerning
) )
), ),
"info": { "info": {
"request": parameters["request"], "request": parameters.request,
"response": { "response": {
"status_code": http_response.statuscode, "status_code": http_response.statuscode,
"headers": http_response.headers, "headers": http_response.headers,

View file

@ -122,7 +122,7 @@ namespace _heimdall.check_kinds.script
"info": { "info": {
"result": result, "result": result,
}, },
} };
} }

View file

@ -0,0 +1,250 @@
namespace _heimdall.check_kinds.tls_certificate
{
/**
*/
function parameters_schema(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer",
"default": 443
},
"expiry_threshold_concerning": {
"description": "in days; allowed amount of valid days before the certificate expires; threshold for condition 'concerning'; 'null' means 'report at no value'",
"anyOf": [
{
"type": "null",
},
{
"type": "integer",
"minimum": 0
},
],
"default": 7,
},
"expiry_threshold_critical": {
"description": "in days; allowed amount of valid days before the certificate expires; threshold for condition 'critical'; 'null' means 'report at no value'",
"anyOf": [
{
"type": "null",
},
{
"type": "integer",
"minimum": 0
},
],
"default": 1,
},
"expiry_threshold": {
"deprecated": true,
"description": "",
"anyOf": [
{
"type": "null",
},
{
"type": "integer",
"minimum": 0
},
],
"default": null,
},
"strict": {
"deprecated": true,
"description": "",
"anyOf": [
{
"type": "null",
},
{
"type": "boolean",
},
],
"default": null,
},
},
"required": [
"host",
]
};
}
/**
*/
function normalize_order_node(
node : any
) : any
{
const version : string = (
(
(! ("expiry_threshold_concerning" in node))
&&
(! ("expiry_threshold_critical" in node))
)
? "v1"
: "v2"
);
switch (version) {
default: {
throw (new Error("unhandled version"));
break;
}
case "v1": {
if (! ("host" in node)) {
throw new Error("missing mandatory field 'host'");
}
else {
const node_ = Object.assign(
{
"port": 443,
"expiry_threshold": 7,
"strict": true,
},
node
);
return {
"host": node_["host"],
"port": node_["port"],
"expiry_threshold_concerning": (
node_["strict"]
? null
: node_["expiry_threshold"]
),
"expiry_threshold_critical": (
node_["strict"]
? node_["expiry_threshold"]
: null
),
};
}
break;
}
case "v2": {
if (! ("host" in node)) {
throw new Error("missing mandatory field 'host'");
}
else {
const node_ = Object.assign(
{
"port": 443,
"expiry_threshold_concerning": 7,
"expiry_threshold_critical": 1,
},
node
);
return node_;
}
break;
}
}
}
/**
*/
async function run(
parameters
) : Promise<_heimdall.type_result>
{
// TODO: outsource to parameters
const timeout : float = 5.0;
type type_stuff = {
valid_from : int;
valid_to : int;
};
// const nm_child_process = require("x509");
const nm_tls = require("tls");
const nm_ssl_checker = require("ssl-checker");
let faults : Array<string> = [];
let data : Record<string, any> = {};
let condition : _heimdall.enum_condition = _heimdall.enum_condition.ok;
let version : (null | string);
const stuff : (null | type_stuff) = await (
nm_ssl_checker(parameters["host"], {"port": parameters["port"]})
.then(
x => ({
"valid_from": Math.floor((new Date(x["validFrom"])).getTime() / 1000),
"valid_to": Math.floor((new Date(x["validTo"])).getTime() / 1000),
})
)
);
if (stuff === null) {
faults.push(lib_plankton.translate.get("checks.tls_certificate.not_obtainable"));
condition = _heimdall.enum_condition.critical;
version = null;
}
else {
version = "TLSv1.3";
const current_timestamp : int = _heimdall.get_current_timestamp();
const expiry_timestamp = stuff.valid_to;
const days : int = Math.ceil((expiry_timestamp - current_timestamp) / (60 * 60 * 24));
data = Object.assign(
data,
{
"expiry_timestamp": expiry_timestamp,
"days": days,
}
);
if (
(parameters["expiry_threshold_critical"] !== null)
&&
(days <= parameters["expiry_threshold_critical"])
) {
faults.push(lib_plankton.translate.get("checks.tls_certificate.expires_soon"));
condition = _heimdall.enum_condition.critical;
}
else {
if (
(parameters["expiry_threshold_concerning"] !== null)
&&
(days <= parameters["expiry_threshold_concerning"])
) {
faults.push(lib_plankton.translate.get("checks.tls_certificate.expires_soon"));
condition = _heimdall.enum_condition.concerning;
}
else {
// no nothing
}
}
}
return Promise.resolve({
"condition": condition,
"info": {
"host": parameters["host"],
"port": parameters["port"],
"faults": faults,
"data": data,
}
});
}
/**
*/
export function check_kind_implementation(
) : type_check_kind
{
return {
"parameters_schema": parameters_schema,
"normalize_order_node": normalize_order_node,
"run": run,
};
}
}

View file

@ -0,0 +1,32 @@
namespace _heimdall.helpers.misc
{
/**
*/
export function format_bytes(
bytes : int
) : string
{
const units : Array<{label : string; digits : int;}> = [
{"label": "B", "digits": 0},
{"label": "KB", "digits": 1},
{"label": "MB", "digits": 1},
{"label": "GB", "digits": 1},
{"label": "TB", "digits": 1},
{"label": "PB", "digits": 1},
]
let number_ : int = bytes;
let index : int = 0;
while ((number_ >= 1000) && (index < (units.length - 1))) {
number_ /= 1000;
index += 1;
}
return lib_plankton.string.coin(
"{{number}} {{label}}",
{
"number": number_.toFixed(units[index].digits),
"label": units[index].label,
}
);
}
}

View file

@ -248,6 +248,8 @@ async function main(
"script": _heimdall.check_kinds.script.check_kind_implementation(), "script": _heimdall.check_kinds.script.check_kind_implementation(),
"http_request": _heimdall.check_kinds.http_request.check_kind_implementation(), "http_request": _heimdall.check_kinds.http_request.check_kind_implementation(),
"file_state": _heimdall.check_kinds.file_state.check_kind_implementation(), "file_state": _heimdall.check_kinds.file_state.check_kind_implementation(),
"tls_certificate": _heimdall.check_kinds.tls_certificate.check_kind_implementation(),
"generic_remote": _heimdall.check_kinds.generic_remote.check_kind_implementation(),
}; };
if (args["show_schema"]) { if (args["show_schema"]) {
process.stdout.write( process.stdout.write(

View file

@ -22,12 +22,15 @@
"source/logic/base.ts", "source/logic/base.ts",
"source/logic/helpers/json_schema.ts", "source/logic/helpers/json_schema.ts",
"source/logic/helpers/sqlite.ts", "source/logic/helpers/sqlite.ts",
"source/logic/helpers/misc.ts",
"source/logic/notification_kinds/_abstract.ts", "source/logic/notification_kinds/_abstract.ts",
"source/logic/notification_kinds/console.ts", "source/logic/notification_kinds/console.ts",
"source/logic/check_kinds/_abstract.ts", "source/logic/check_kinds/_abstract.ts",
"source/logic/check_kinds/script.ts", "source/logic/check_kinds/script.ts",
"source/logic/check_kinds/http_request.ts", "source/logic/check_kinds/http_request.ts",
"source/logic/check_kinds/file_state.ts", "source/logic/check_kinds/file_state.ts",
"source/logic/check_kinds/tls_certificate.ts",
"source/logic/check_kinds/generic_remote.ts",
"source/logic/state_repository.ts", "source/logic/state_repository.ts",
"source/logic/order.ts", "source/logic/order.ts",
"source/logic/main.ts" "source/logic/main.ts"