2023-06-19 18:05:19 +02:00
|
|
|
namespace _heimdall.check_kinds.http_request
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
function parameters_schema(
|
|
|
|
|
) : _heimdall.helpers.json_schema.type_schema
|
|
|
|
|
{
|
|
|
|
|
return {
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": false,
|
|
|
|
|
"properties": {
|
|
|
|
|
"request": {
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": false,
|
|
|
|
|
"properties": {
|
|
|
|
|
"target": {
|
|
|
|
|
"description": "URL",
|
|
|
|
|
"type": "string"
|
|
|
|
|
},
|
|
|
|
|
"method": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"enum": [
|
|
|
|
|
"GET",
|
|
|
|
|
"POST"
|
|
|
|
|
],
|
|
|
|
|
"default": "GET"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
"target"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"timeout": {
|
|
|
|
|
"description": "maximum allowed execution time in seconds",
|
2023-06-19 18:27:04 +02:00
|
|
|
"type": "number",
|
2023-06-19 18:05:19 +02:00
|
|
|
"default": 5.0
|
|
|
|
|
},
|
|
|
|
|
"follow_redirects": {
|
|
|
|
|
"description": "whether redirect instructions in responses shall be followend instead of being exposed as result",
|
|
|
|
|
"type": "boolean",
|
|
|
|
|
"default": false
|
|
|
|
|
},
|
|
|
|
|
"response": {
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": false,
|
|
|
|
|
"properties": {
|
|
|
|
|
"status_code": {
|
|
|
|
|
"description": "checks whether the response status code is this",
|
2023-07-06 16:13:26 +02:00
|
|
|
"anyOf": [
|
|
|
|
|
{
|
|
|
|
|
"type": "null",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "integer",
|
|
|
|
|
},
|
|
|
|
|
],
|
2023-06-19 18:05:19 +02:00
|
|
|
"default": 200
|
|
|
|
|
},
|
|
|
|
|
"headers": {
|
|
|
|
|
"description": "conjunctively checks header key-value pairs",
|
|
|
|
|
"type": "object",
|
|
|
|
|
"additionalProperties": {
|
|
|
|
|
"description": "header value",
|
|
|
|
|
"type": "string"
|
|
|
|
|
},
|
|
|
|
|
"properties": {
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
],
|
|
|
|
|
"default": {}
|
|
|
|
|
},
|
|
|
|
|
"body_part": {
|
|
|
|
|
"description": "checks whether the response body contains this string",
|
|
|
|
|
"type": "string"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": [
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"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": [
|
|
|
|
|
"request",
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
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": {
|
|
|
|
|
const node_ = lib_plankton.object.patched(
|
|
|
|
|
{
|
|
|
|
|
"request": {
|
|
|
|
|
"method": "GET"
|
|
|
|
|
},
|
|
|
|
|
"timeout": 5.0,
|
|
|
|
|
"follow_redirects": false,
|
|
|
|
|
"response": {
|
|
|
|
|
"status_code": 200
|
|
|
|
|
},
|
|
|
|
|
"strict": true,
|
|
|
|
|
},
|
|
|
|
|
node,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
const allowed_methods : Array<string> = ["GET", "POST"];
|
|
|
|
|
if (! allowed_methods.includes(node_["request"]["method"])) {
|
|
|
|
|
throw (new Error("invalid HTTP request method: " + node_["request"]["method"]));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return {
|
|
|
|
|
"request": node_["request"],
|
|
|
|
|
"timeout": node_["timeout"],
|
|
|
|
|
"follow_redirects": node_["follow_redirects"],
|
|
|
|
|
"response": node_["response"],
|
|
|
|
|
"critical": node_["strict"],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "v2": {
|
|
|
|
|
const node_ = lib_plankton.object.patched(
|
|
|
|
|
{
|
|
|
|
|
"request": {
|
|
|
|
|
"method": "GET"
|
|
|
|
|
},
|
|
|
|
|
"timeout": 5.0,
|
|
|
|
|
"follow_redirects": false,
|
|
|
|
|
"response": {
|
|
|
|
|
"status_code": 200
|
|
|
|
|
},
|
|
|
|
|
"critical": true,
|
|
|
|
|
},
|
|
|
|
|
node,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
const allowed_methods : Array<string> = ["GET", "POST"];
|
|
|
|
|
if (! allowed_methods.includes(node_["request"]["method"])) {
|
|
|
|
|
throw (new Error("invalid HTTP request method: " + node_["request"]["method"]));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return node_;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
async function run(
|
2023-07-23 09:33:04 +02:00
|
|
|
parameters : {
|
|
|
|
|
request : {
|
|
|
|
|
target : string;
|
|
|
|
|
method : string;
|
|
|
|
|
};
|
|
|
|
|
timeout : float;
|
|
|
|
|
follow_redirects : boolean;
|
|
|
|
|
response : {
|
|
|
|
|
status_code : int;
|
|
|
|
|
headers ?: Record<string, string>;
|
|
|
|
|
body_part ?: string;
|
|
|
|
|
};
|
|
|
|
|
critical : boolean;
|
|
|
|
|
}
|
2023-06-19 18:05:19 +02:00
|
|
|
) : Promise<_heimdall.type_result>
|
|
|
|
|
{
|
|
|
|
|
let error : (null | Error);
|
2023-07-27 17:37:45 +02:00
|
|
|
const url : URL = new URL(parameters.request.target);
|
2023-06-19 18:05:19 +02:00
|
|
|
const http_request : lib_plankton.http.type_request = {
|
2023-07-27 17:37:45 +02:00
|
|
|
"version": "HTTP/1.1",
|
|
|
|
|
"scheme": (
|
|
|
|
|
(url.protocol.slice(0, -1) === "https")
|
|
|
|
|
? "https"
|
|
|
|
|
: "http"
|
|
|
|
|
),
|
|
|
|
|
"host": url.host,
|
2023-06-19 18:05:19 +02:00
|
|
|
"method": {
|
|
|
|
|
"GET": lib_plankton.http.enum_method.get,
|
|
|
|
|
"POST": lib_plankton.http.enum_method.post,
|
2023-07-23 09:33:04 +02:00
|
|
|
}[parameters.request.method],
|
2023-07-27 17:37:45 +02:00
|
|
|
"path": url.pathname,
|
|
|
|
|
"query": url.search,
|
2023-07-27 18:18:08 +02:00
|
|
|
"headers": {
|
|
|
|
|
"Host": url.host,
|
|
|
|
|
},
|
|
|
|
|
"body": null,
|
2023-06-19 18:05:19 +02:00
|
|
|
};
|
|
|
|
|
let http_response : (null | lib_plankton.http.type_response);
|
|
|
|
|
try {
|
|
|
|
|
http_response = await lib_plankton.http.call(
|
|
|
|
|
http_request,
|
|
|
|
|
{
|
2023-07-27 17:37:45 +02:00
|
|
|
"timeout": parameters.timeout,
|
2023-07-23 09:33:04 +02:00
|
|
|
"follow_redirects": parameters.follow_redirects,
|
2023-07-27 17:37:45 +02:00
|
|
|
"implementation": "fetch",
|
2023-06-19 18:05:19 +02:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
error = null;
|
|
|
|
|
}
|
|
|
|
|
catch (error_) {
|
|
|
|
|
http_response = null;
|
|
|
|
|
error = error_;
|
|
|
|
|
}
|
|
|
|
|
if (http_response === null) {
|
|
|
|
|
return {
|
|
|
|
|
"condition": (
|
2023-07-23 09:33:04 +02:00
|
|
|
parameters.critical
|
2023-06-19 18:05:19 +02:00
|
|
|
? _heimdall.enum_condition.critical
|
|
|
|
|
: _heimdall.enum_condition.concerning
|
|
|
|
|
),
|
|
|
|
|
"info": {
|
2023-07-23 09:33:04 +02:00
|
|
|
"request": parameters.request,
|
2023-06-19 18:05:19 +02:00
|
|
|
"faults": [
|
2023-07-23 09:33:04 +02:00
|
|
|
lib_plankton.translate.get("checks.http_request.request_failed", {"reason": error.toString()}),
|
2023-06-19 18:05:19 +02:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
let faults : Array<string> = [];
|
|
|
|
|
// status code
|
|
|
|
|
{
|
|
|
|
|
if (
|
2023-07-23 09:33:04 +02:00
|
|
|
(! ("status_code" in parameters.response))
|
2023-06-19 18:05:19 +02:00
|
|
|
||
|
2023-07-23 09:33:04 +02:00
|
|
|
(parameters.response.status_code === null)
|
2023-06-19 18:05:19 +02:00
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
else {
|
2023-07-23 09:33:04 +02:00
|
|
|
const status_code_expected : int = (parameters.response.status_code as int);
|
2023-07-27 17:37:45 +02:00
|
|
|
if (! (http_response.status_code === status_code_expected)) {
|
2023-06-19 18:05:19 +02:00
|
|
|
faults.push(
|
|
|
|
|
lib_plankton.translate.get(
|
|
|
|
|
"checks.http_request.status_code_mismatch",
|
|
|
|
|
{
|
2023-07-27 17:37:45 +02:00
|
|
|
"status_code_actual": http_response.status_code.toFixed(0),
|
2023-06-19 18:05:19 +02:00
|
|
|
"status_code_expected": status_code_expected.toFixed(0),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// headers
|
|
|
|
|
{
|
|
|
|
|
if (
|
2023-07-23 09:33:04 +02:00
|
|
|
(! ("headers" in parameters.response))
|
2023-06-19 18:05:19 +02:00
|
|
|
||
|
2023-07-23 09:33:04 +02:00
|
|
|
(parameters.response.headers === null)
|
2023-06-19 18:05:19 +02:00
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
else {
|
2023-07-23 09:33:04 +02:00
|
|
|
const headers_expected : Record<string, string> = (parameters.response.headers as Record<string, string>);
|
2023-06-19 18:05:19 +02:00
|
|
|
Object.entries(headers_expected).forEach(
|
|
|
|
|
([header_key, header_value]) => {
|
2023-07-23 09:33:04 +02:00
|
|
|
if (
|
|
|
|
|
(! (header_key in http_response.headers))
|
|
|
|
|
&&
|
|
|
|
|
(! (header_key.toLowerCase() in http_response.headers))
|
|
|
|
|
) {
|
2023-06-19 18:05:19 +02:00
|
|
|
faults.push(
|
|
|
|
|
lib_plankton.translate.get(
|
|
|
|
|
"checks.http_request.header_missing",
|
|
|
|
|
{
|
|
|
|
|
"key": header_key,
|
|
|
|
|
"value_expected": header_value,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2023-07-23 09:33:04 +02:00
|
|
|
if (
|
|
|
|
|
(! (http_response.headers[header_key] === header_value))
|
|
|
|
|
&&
|
|
|
|
|
(! (http_response.headers[header_key.toLowerCase()].toLowerCase() === header_value.toLowerCase()))
|
|
|
|
|
) {
|
2023-06-19 18:05:19 +02:00
|
|
|
faults.push(
|
|
|
|
|
lib_plankton.translate.get(
|
|
|
|
|
"checks.http_request.header_value_mismatch",
|
|
|
|
|
{
|
|
|
|
|
"key": header_key,
|
|
|
|
|
"value_actual": http_response.headers[header_key],
|
|
|
|
|
"value_expected": header_value,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// body
|
|
|
|
|
{
|
|
|
|
|
if (
|
2023-07-23 09:33:04 +02:00
|
|
|
(! ("body_part" in parameters.response))
|
2023-06-19 18:05:19 +02:00
|
|
|
||
|
2023-07-23 09:33:04 +02:00
|
|
|
(parameters.response.body_part === null)
|
2023-06-19 18:05:19 +02:00
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
else {
|
2023-07-23 09:33:04 +02:00
|
|
|
const body_part : string = (parameters.response.body_part as string);
|
2023-07-27 17:37:45 +02:00
|
|
|
if (! http_response.body.toString().includes(body_part)) {
|
2023-06-19 18:05:19 +02:00
|
|
|
faults.push(
|
|
|
|
|
lib_plankton.translate.get(
|
|
|
|
|
"checks.http_request.body_misses_part",
|
|
|
|
|
{
|
|
|
|
|
"part": body_part,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"condition": (
|
|
|
|
|
(faults.length <= 0)
|
|
|
|
|
? _heimdall.enum_condition.ok
|
|
|
|
|
: (
|
2023-07-23 09:33:04 +02:00
|
|
|
parameters.critical
|
2023-06-19 18:05:19 +02:00
|
|
|
? _heimdall.enum_condition.critical
|
|
|
|
|
: _heimdall.enum_condition.concerning
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
"info": {
|
2023-07-23 09:33:04 +02:00
|
|
|
"request": parameters.request,
|
2023-06-19 18:05:19 +02:00
|
|
|
"response": {
|
2023-07-27 17:37:45 +02:00
|
|
|
"status_code": http_response.status_code,
|
2023-06-19 18:05:19 +02:00
|
|
|
"headers": http_response.headers,
|
|
|
|
|
// "body": http_response.body,
|
|
|
|
|
},
|
|
|
|
|
"faults": faults,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
export function check_kind_implementation(
|
|
|
|
|
) : type_check_kind
|
|
|
|
|
{
|
|
|
|
|
return {
|
|
|
|
|
"parameters_schema": parameters_schema,
|
|
|
|
|
"normalize_order_node": normalize_order_node,
|
|
|
|
|
"run": run,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|