First release of open-appsec source code

This commit is contained in:
roybarda
2022-10-26 19:33:19 +03:00
parent 3883109caf
commit a883352f79
1353 changed files with 276290 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
add_subdirectory(nginx_attachment)
add_subdirectory(attachment_registrator)

View File

@@ -0,0 +1 @@
add_library(attachment_registrator attachment_registrator.cc)

View File

@@ -0,0 +1,470 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "attachment_registrator.h"
#include <iostream>
#include <fstream>
#include <map>
#include <sstream>
#include <fstream>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <climits>
#include <unordered_map>
#include <unistd.h>
#include <utility>
#include "common.h"
#include "config.h"
#include "singleton.h"
#include "i_mainloop.h"
#include "buffer.h"
#include "enum_array.h"
#include "nginx_attachment_common.h"
USE_DEBUG_FLAG(D_ATTACHMENT_REGISTRATION);
using namespace std;
class AttachmentRegistrator::Impl
{
public:
void
init()
{
i_socket = Singleton::Consume<I_Socket>::by<AttachmentRegistrator>();
Singleton::Consume<I_MainLoop>::by<AttachmentRegistrator>()->addOneTimeRoutine(
I_MainLoop::RoutineType::System,
[this] ()
{
while(!initSocket()) {
Singleton::Consume<I_MainLoop>::by<AttachmentRegistrator>()->yield(chrono::seconds(1));
}
},
"Initialize attachment registration IPC"
);
uint expiration_timeout = getProfileAgentSettingWithDefault<uint>(
300, "attachmentRegistrator.expirationCheckSeconds"
);
Singleton::Consume<I_MainLoop>::by<AttachmentRegistrator>()->addRecurringRoutine(
I_MainLoop::RoutineType::Timer,
chrono::seconds(expiration_timeout),
[this] () { handleExpiration(); },
"Attachment's expiration handler",
true
);
}
void
fini()
{
if (server_sock > 0) {
i_socket->closeSocket(server_sock);
server_sock = -1;
}
if (shared_registration_path != "") unlink(shared_registration_path.c_str());
}
private:
bool
registerAttachmentProcess(
const uint8_t &uid,
const string &family_id,
const uint8_t num_of_members,
const AttachmentType type)
{
registered_attachments[family_id] = vector<bool>(num_of_members, true);
const int cmd_tmout = 900;
I_ShellCmd *shell_cmd = Singleton::Consume<I_ShellCmd>::by<AttachmentRegistrator>();
Maybe<string> registration_res = shell_cmd->getExecOutput(
genRegCommand(family_id, num_of_members, type),
cmd_tmout
);
if (!registration_res.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to register attachment."
<< "Attachment Type: "
<< static_cast<int>(type)
<< ", Attachment id: "
<< uid
<<", Family id: "
<< family_id
<< ", Total number of instances: "
<< num_of_members;
return false;
}
return true;
}
void
replyWithRelevantHandler(
I_Socket::socketFd socket,
const uint8_t &uid,
const string &family_id,
const AttachmentType type)
{
string handler_path = genHandlerPath(uid, family_id, type);
uint8_t path_size = handler_path.size();
vector<char> path_size_data(reinterpret_cast<char *>(&path_size), reinterpret_cast<char *>(&path_size) + 1);
if (!i_socket->writeData(socket, path_size_data)) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to send handler path size to attachment";
return;
}
dbgDebug(D_ATTACHMENT_REGISTRATION)
<< "Successfully sent handler path size to attachment. Size: "
<< to_string(path_size);
vector<char> path_data(handler_path.data(), handler_path.data() + handler_path.size());
if (!i_socket->writeData(socket, path_data)) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to send handler path data to attachment. Path: "
<< handler_path;
return;
}
dbgDebug(D_ATTACHMENT_REGISTRATION)
<< "Successfully sent handler path data to attachment. Path: "
<< handler_path;
}
string
genHandlerPath(const uint8_t &uid, const string &family_id, const AttachmentType type) const
{
static const string handler_path_format = "/dev/shm/check-point/cp-nano-";
stringstream handler_path;
handler_path << handler_path_format;
switch(type) {
case (AttachmentType::NGINX_ATT_ID): {
handler_path << "http-transaction-handler-";
break;
}
default:
dbgAssert(false) << "Unsupported Attachment " << static_cast<int>(type);
}
if (!family_id.empty()) handler_path << family_id << "_";
handler_path << to_string(uid);
return handler_path.str();
}
string
genRegCommand(const string &family_id, const uint num_of_members, const AttachmentType type) const
{
dbgAssert(num_of_members > 0) << "Failed to generate a registration command for an empty group of attachments";
static const string registration_format = "/etc/cp/watchdog/cp-nano-watchdog --register ";
stringstream registration_command;
registration_command<< registration_format;
switch(type) {
case (AttachmentType::NGINX_ATT_ID): {
registration_command << "/etc/cp/HttpTransactionHandler/cp-nano-http-transaction-handler";
break;
}
default:
dbgAssert(false) << "Unsupported Attachment " << static_cast<int>(type);
}
if (!family_id.empty()) registration_command << " --family " << family_id;
registration_command << " --count " << to_string(num_of_members);
return registration_command.str();
}
bool
initSocket()
{
shared_registration_path = getConfigurationWithDefault<string>(
"/dev/shm/check-point/cp-nano-attachment-registration",
"Attachment Registration",
"Registration IPC Path"
);
size_t last_slash_idx = shared_registration_path.find_last_of("/");
string directory_path = shared_registration_path.substr(0, last_slash_idx);
mkdir(directory_path.c_str(), 0777);
if (server_sock < 0) {
server_sock = getNewSocket(shared_registration_path);
if (server_sock < 0) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to create server socket. Path: "
<< shared_registration_path;
return false;
}
Singleton::Consume<I_MainLoop>::by<AttachmentRegistrator>()->addFileRoutine(
I_MainLoop::RoutineType::RealTime,
server_sock,
[this] () { handleAttachmentRegistration(); },
"Attachment's registration handler",
true
);
}
string shared_expiration_path = getConfigurationWithDefault<string>(
SHARED_KEEP_ALIVE_PATH,
"Attachment Registration",
"Registration IPC Path"
);
if (keep_alive_sock < 0) {
keep_alive_sock = getNewSocket(shared_expiration_path);
if (keep_alive_sock < 0) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to create keep-alive socket";
return false;
}
Singleton::Consume<I_MainLoop>::by<AttachmentRegistrator>()->addFileRoutine(
I_MainLoop::RoutineType::System,
keep_alive_sock,
[this] () { handleKeepAlives(); },
"Attachment keep alive registration",
true
);
}
return true;
}
I_Socket::socketFd
getNewSocket(const string &path)
{
Maybe<I_Socket::socketFd> new_socket = i_socket->genSocket(
I_Socket::SocketType::UNIX,
false,
true,
path
);
if (!new_socket.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to open a socket. Error: " << new_socket.getErr();
return -1;
}
dbgAssert(new_socket.unpack() > 0) << "Generated socket is OK yet negative";
return new_socket.unpack();
}
void
handleKeepAlives()
{
Maybe<I_Socket::socketFd> accepted_socket = i_socket->acceptSocket(keep_alive_sock, false);
if (!accepted_socket.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to accept new keep-alive request socket: "
<< accepted_socket.getErr();
return;
}
I_Socket::socketFd client_socket = accepted_socket.unpack();
dbgAssert(client_socket > 0) << "Generated client socket is OK yet negative";
auto close_socket_on_exit = make_scope_exit([&]() { i_socket->closeSocket(client_socket); });
Maybe<uint8_t> attachment_id = readNumericParam(client_socket);
if (!attachment_id.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to register new attachment: " << attachment_id.getErr();
return;
}
Maybe<string> family_id = readStringParam(client_socket);
if (!family_id.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to register new attachment: " << family_id.getErr();
return;
}
if (family_id.unpack() == "") return;
auto family_members = registered_attachments.find(family_id.unpack());
if (family_members == registered_attachments.end()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Adding new unregistered family. Family ID: "
<< family_id.unpack();
registered_attachments[family_id.unpack()] = vector<bool>(attachment_id.unpack() + 1, true);
return;
}
if (family_members->second.size() <= attachment_id.unpack()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Adding new non-monitored family members. Family ID: "
<< family_id.unpack()
<< ", Instance ID:"
<< attachment_id.unpack();
registered_attachments[family_id.unpack()] = vector<bool>(attachment_id.unpack() + 1, true);
return;
}
family_members->second[attachment_id.unpack()] = true;
}
void
handleExpiration()
{
I_ShellCmd *shell_cmd = Singleton::Consume<I_ShellCmd>::by<AttachmentRegistrator>();
vector<string> deleted_families;
for (pair<const string, vector<bool, allocator<bool>>> &family : registered_attachments) {
const string &family_id = family.first;
if (family_id == "") continue;
bool is_family_inactive = true;
vector<bool> &family_members = family.second;
for (const bool member : family_members) {
if (member == true) is_family_inactive = false;
}
if (is_family_inactive) {
static const string unregister_format = "/etc/cp/watchdog/cp-nano-watchdog --un-register ";
stringstream unregister_command;
unregister_command << unregister_format;
unregister_command << "/etc/cp/HttpTransactionHandler/cp-nano-http-transaction-handler";
unregister_command << " --family " << family_id;
Maybe<string> res = shell_cmd->getExecOutput(unregister_command.str());
if (!res.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to un-register attachment. Family id: "
<< family_id;
} else {
deleted_families.push_back(family_id);
}
} else {
fill(family_members.begin(), family_members.end(), false);
}
}
for (const string &family : deleted_families) {
registered_attachments.erase(family);
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Successfully un-registered attachments family. Family id: "
<< family;
}
}
void
handleAttachmentRegistration()
{
Maybe<I_Socket::socketFd> accepted_socket = i_socket->acceptSocket(server_sock, false);
if (!accepted_socket.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to accept a new client socket: "
<< accepted_socket.getErr();
return;
}
I_Socket::socketFd client_socket = accepted_socket.unpack();
dbgAssert(client_socket > 0) << "Generated client socket is OK yet negative";
auto close_socket_on_exit = make_scope_exit([&]() { i_socket->closeSocket(client_socket); });
Maybe<AttachmentType> attachment_type = readAttachmentType(client_socket);
if (!attachment_type.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to register a new attachment: "
<< attachment_type.getErr();
return;
}
Maybe<uint8_t> attachment_id = readNumericParam(client_socket);
if (!attachment_id.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to register a new attachment: " << attachment_id.getErr();
return;
}
Maybe<uint8_t> instances_count = readNumericParam(client_socket);
if (!instances_count.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION)
<< "Failed to register a new attachment: "
<< instances_count.getErr();
return;
}
Maybe<string> family_id = readStringParam(client_socket);
if (!family_id.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to register a new attachment: " << family_id.getErr();
return;
}
if (!registerAttachmentProcess(*attachment_id, *family_id, *instances_count, *attachment_type)) {
return;
}
replyWithRelevantHandler(client_socket, *attachment_id, *family_id, *attachment_type);
}
Maybe<uint8_t>
readNumericParam(I_Socket::socketFd socket)
{
Maybe<vector<char>> param_to_read = i_socket->receiveData(socket, sizeof(uint8_t));
if (!param_to_read.ok()) {
dbgWarning(D_ATTACHMENT_REGISTRATION) << "Failed to read param: " << param_to_read.getErr();
return genError("Failed to read numeric parameter");
}
return *reinterpret_cast<const uint8_t *>(param_to_read.unpack().data());
}
Maybe<AttachmentType>
readAttachmentType(I_Socket::socketFd socket)
{
Maybe<uint8_t> attachment_type = readNumericParam(socket);
if (!attachment_type.ok()) return attachment_type.passErr();
dbgTrace(D_ATTACHMENT_REGISTRATION)
<< "Successfully received attachment type. Attachment type value: "
<< static_cast<int>(*attachment_type);
return convertToEnum<AttachmentType>(*attachment_type);
}
Maybe<string>
readStringParam(I_Socket::socketFd socket)
{
Maybe<uint8_t> param_size = readNumericParam(socket);
if (!param_size.ok()) return param_size.passErr();
dbgTrace(D_ATTACHMENT_REGISTRATION)
<< "Successfully received string size. Size: "
<< static_cast<int>(*param_size);
Maybe<vector<char>> param_to_read = i_socket->receiveData(socket, param_size.unpack());
return string(param_to_read.unpack().begin(), param_to_read.unpack().end());
}
I_Socket::socketFd server_sock = -1;
I_Socket::socketFd keep_alive_sock = -1;
I_Socket *i_socket = nullptr;
map<string, vector<bool>> registered_attachments;
string shared_registration_path;
};
AttachmentRegistrator::AttachmentRegistrator() : Component("AttachmentRegistrator"), pimpl(make_unique<Impl>()) {}
AttachmentRegistrator::~AttachmentRegistrator() {}
void AttachmentRegistrator::init() { pimpl->init(); }
void AttachmentRegistrator::fini() { pimpl->fini(); }
void
AttachmentRegistrator::preload()
{
registerExpectedConfiguration<string>("Attachment Registration", "Registration IPC Path");
}

View File

@@ -0,0 +1,5 @@
add_definitions(-DUSERSPACE)
add_library(nginx_attachment nginx_attachment.cc nginx_attachment_config.cc nginx_attachment_opaque.cc nginx_parser.cc user_identifiers_config.cc nginx_intaker_metric.cc nginx_attachment_metric.cc cidrs_data.cc)
target_link_libraries(nginx_attachment http_configuration http_transaction_data connkey table buffers -lshmem_ipc)

View File

@@ -0,0 +1,128 @@
#include "cidrs_data.h"
#include "log_generator.h"
using namespace std;
USE_DEBUG_FLAG(D_NGINX_ATTACHMENT_PARSER);
bool
CIDRSData::matchCidr(const in_addr &address, const in_addr &network) const
{
if (network_bits == 0) {
// C99 6.5.7 (3): u32 << 32 is undefined behaviour
return true;
}
return !((address.s_addr ^ network.s_addr) & htonl(0xFFFFFFFFu << (32 - network_bits)));
}
bool
CIDRSData::matchCidr(const in6_addr &address, const in6_addr &network) const
{
#ifdef __linux__
const uint32_t *a = address.s6_addr32;
const uint32_t *n = network.s6_addr32;
#else
const uint32_t *a = address.__u6_addr.__u6_addr32;
const uint32_t *n = network.__u6_addr.__u6_addr32;
#endif
int bits_whole, bits_incomplete;
bits_whole = network_bits >> 5; // number of whole u32
bits_incomplete = network_bits & 0x1F; // number of bits in incomplete u32
if (bits_whole) {
if (memcmp(a, n, bits_whole << 2)) {
return false;
}
}
if (bits_incomplete) {
uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
if ((a[bits_whole] ^ n[bits_whole]) & mask) {
return false;
}
}
return true;
}
CIDRSData::CIDRSData(const string &str_cidr)
{
size_t processed_bits = 0;
size_t pos = str_cidr.find_last_of('/');
// get ip from targetCidr
string str_prefix = pos != string::npos ? str_cidr.substr(0, pos) : str_cidr;
// get subnet mask from targetCidr or calculate it based on ipv4 / ipv6
string str_suffix;
if (pos != string::npos) {
str_suffix = str_cidr.substr(pos + 1);
} else if (str_cidr.find(':') == string::npos) {
str_suffix = "32";
} else {
str_suffix = "128";
}
int bits = -1;
try {
bits = stoi(str_suffix, &processed_bits);
network_bits = (uint8_t)bits;
// convert int to uint8_t
} catch (...) {
dbgWarning(D_NGINX_ATTACHMENT_PARSER)
<< "Failed to convert CIDR number of bits from string to int"
<< str_cidr;
return;
}
// check if CIDR is valid
if (processed_bits != str_suffix.length() || bits > 128 || bits < 0) {
dbgWarning(D_NGINX_ATTACHMENT_PARSER)
<< "Failed to convert CIDR number of bits from string to int (out of range)."
<< str_cidr;
return;
}
if (IPAddr::isValidIPAddr(str_prefix)) {
ip_addr = IPAddr::createIPAddr(str_prefix).unpack();
} else {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Failed to convert CIDR number of bits from string to int";
return;
}
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "successfully created cidr from the following string: " << str_cidr;
valid_cidr = true;
}
bool
CIDRSData::contains(const string &source_ip) const
{
if(!valid_cidr) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Invalid CIDR.";
return false;
}
// check from which type the target ip and check if ip belongs to is mask ip
//convert source_ip to ip v4 or v6.
switch (ip_addr.getType()) {
case IPType::V4: {
struct in_addr source_inaddr;
if (inet_pton(AF_INET, source_ip.c_str(), &source_inaddr) == 1) {
return matchCidr(source_inaddr, ip_addr.getIPv4());
}
break;
}
case IPType::V6: {
struct in6_addr source_inaddr6;
if (inet_pton(AF_INET6, source_ip.c_str(), &source_inaddr6) == 1) {
return matchCidr(source_inaddr6, ip_addr.getIPv6());
}
break;
}
default: {
dbgWarning(D_NGINX_ATTACHMENT_PARSER) << "Unexpected ip type";
}
}
return false;
}

View File

@@ -0,0 +1,28 @@
#ifndef __CIDRS_DATA_H__
#define __CIDRS_DATA_H__
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include "maybe_res.h"
#include "connkey.h"
class CIDRSData
{
public:
CIDRSData(const std::string &str_cidr);
bool contains(const std::string &source_ip) const;
private:
bool matchCidr(const in_addr &address, const in_addr &net) const;
bool matchCidr(const in6_addr &address, const in6_addr &network) const;
IPAddr ip_addr;
uint8_t network_bits;
bool valid_cidr = false;
};
#endif // __CIDRS_DATA_H__

View File

@@ -0,0 +1,145 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "intentional_failure.h"
#include <string>
#include <unistd.h>
#include "config.h"
#include "debug.h"
using namespace std;
USE_DEBUG_FLAG(D_NGINX_ATTACHMENT);
IntentionalFailureHandler::FailureType
getFailureTypeFromString(const string &failure)
{
if (failure == "create socket") return IntentionalFailureHandler::FailureType::CreateSocket;
if (failure == "accept socket") return IntentionalFailureHandler::FailureType::AcceptSocket;
if (failure == "initialize connection channel")
return IntentionalFailureHandler::FailureType::InitializeConnectionChannel;
if (failure == "write to socket") return IntentionalFailureHandler::FailureType::WriteDataToSocket;
if (failure == "read from socket") return IntentionalFailureHandler::FailureType::ReceiveDataFromSocket;
if (failure == "parse response") return IntentionalFailureHandler::FailureType::ParsingResponse;
if (failure == "get data from attachment") return IntentionalFailureHandler::FailureType::GetDataFromAttchment;
if (failure == "register attachment") return IntentionalFailureHandler::FailureType::RegisterAttchment;
if (failure == "get instance id") return IntentionalFailureHandler::FailureType::GetInstanceID;
if (failure != "") {
dbgInfo(D_NGINX_ATTACHMENT) << "Ignoring unknown intentional failure type:" << failure;
}
return IntentionalFailureHandler::FailureType::None;
}
void
IntentionalFailureHandler::RegisterIntentionalFailure()
{
is_failure_enabled = getConfigurationWithDefault<bool>(
false, "HTTP manager", "Enable intentional failure mode"
);
string failure_type_str = getConfigurationWithDefault<string>("", "HTTP manager", "Intentional failure type");
failure_type = getFailureTypeFromString(failure_type_str);
if (failure_type == FailureType::None) is_failure_enabled = false;
allow_count = getConfigurationWithDefault<int>(0, "HTTP manager", "Intentional failure allow times");
fail_count = getConfigurationWithDefault<int>(-1, "HTTP manager", "Intentional failure limit");
is_limited = fail_count > 0;
is_delay_enabled = getConfigurationWithDefault<bool>(
false, "HTTP manager", "Enable intentional delay mode"
);
string delay_failure_type_str = getConfigurationWithDefault<string>(
"", "HTTP manager", "Intentional delay failure type"
);
delay_failure_type = getFailureTypeFromString(delay_failure_type_str);
delay_amount = chrono::microseconds(
getConfigurationWithDefault<int>(-1, "HTTP manager", "Intentional delay amount")
);
if (delay_failure_type == FailureType::None || delay_amount <= chrono::microseconds(0)) is_delay_enabled = false;
if (is_failure_enabled) {
dbgInfo(D_NGINX_ATTACHMENT) << "Registered Intentional failure. Type: " << failure_type_str
<< ", will allow first " << to_string(allow_count) << " actions"
<< ", fail limit: " << (is_limited ? to_string(fail_count) : "unlimited");
}
if (is_delay_enabled) {
dbgInfo(D_NGINX_ATTACHMENT) << "Registered Intentional delay. Type: " << delay_failure_type_str
<< ", amount: " << delay_amount.count() << " microseconds";
}
}
void
IntentionalFailureHandler::init()
{
RegisterIntentionalFailure();
registerConfigLoadCb([this]() { RegisterIntentionalFailure(); });
if (!is_failure_enabled && !is_delay_enabled) {
dbgInfo(D_NGINX_ATTACHMENT) << "Initialized Intentional failure. No failure/delay was specified";
}
}
bool
IntentionalFailureHandler::shouldFail(
bool was_originaly_successful,
IntentionalFailureHandler::FailureType failure,
bool *failed_on_purpose
)
{
*failed_on_purpose = false;
if (is_failure_enabled && failure_type == failure) {
if (allow_count > 0) {
allow_count --;
dbgInfo(D_NGINX_ATTACHMENT) << "Intentional failure: allowed action, remaining tries to be allowed: "
<< to_string(allow_count);
return !was_originaly_successful;
}
if (is_limited) {
if (fail_count <= 0) return !was_originaly_successful;
fail_count --;
}
dbgInfo(D_NGINX_ATTACHMENT) << "Intentional failure was activated, remaining failures: "
<< (is_limited ? to_string(fail_count) : "unlimited");
*failed_on_purpose = true;
return true;
}
return !was_originaly_successful;
}
void
IntentionalFailureHandler::delayIfNeeded(IntentionalFailureHandler::FailureType failure)
{
if (is_delay_enabled && delay_failure_type == failure) {
dbgInfo(D_NGINX_ATTACHMENT) << "Intentional delay was activated (" << delay_amount.count() << " microseconds)";
usleep(delay_amount.count());
}
}
void
IntentionalFailureHandler::preload()
{
registerExpectedConfiguration<bool>("HTTP manager", "Enable intentional failure mode");
registerExpectedConfiguration<string>("HTTP manager", "Intentional failure type");
registerExpectedConfiguration<int>("HTTP manager", "Intentional failure limit");
registerExpectedConfiguration<int>("HTTP manager", "Intentional failure allow times");
registerExpectedConfiguration<bool>("HTTP manager", "Enable intentional delay mode");
registerExpectedConfiguration<string>("HTTP manager", "Intentional delay failure type");
registerExpectedConfiguration<int>("HTTP manager", "Intentional delay amount");
}

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __INTENTIONAL_FAILURE__
#define __INTENTIONAL_FAILURE__
#include <chrono>
class IntentionalFailureHandler
{
public:
enum class FailureType {
None,
CreateSocket,
AcceptSocket,
InitializeConnectionChannel,
WriteDataToSocket,
ReceiveDataFromSocket,
ParsingResponse,
GetDataFromAttchment,
RegisterAttchment,
GetInstanceID,
COUNT
};
void init();
bool shouldFail(bool was_originaly_successful, FailureType failure, bool *failed_on_purpose);
void delayIfNeeded(FailureType failure);
void preload();
private:
void RegisterIntentionalFailure();
FailureType failure_type;
bool is_failure_enabled;
bool is_limited;
int fail_count;
int allow_count;
FailureType delay_failure_type;
bool is_delay_enabled;
std::chrono::microseconds delay_amount;
};
#endif // __INTENTIONAL_FAILURE__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,331 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nginx_attachment_config.h"
#include <stdlib.h>
#include "nginx_attachment.h"
#include "config.h"
#include "singleton.h"
#include "i_gradual_deployment.h"
#include "debug.h"
USE_DEBUG_FLAG(D_NGINX_ATTACHMENT);
using namespace std;
using DebugLevel = ngx_http_cp_debug_level_e;
void
HttpAttachmentConfig::init()
{
setDebugLevel();
setGradualDeploymentIPs();
setWebTriggerConf();
setStaticResourcesPath();
setFailOpenMode();
setFailOpenTimeout();
setFailOpenWaitMode();
setSessionsPerMinuteLimitVerdict();
setMaxSessionsPerMinute();
setNumOfNginxIpcElements();
setDebugByContextValues();
setKeepAliveIntervalMsec();
}
bool
HttpAttachmentConfig::operator==(const HttpAttachmentConfig &other) const
{
return
web_trigger_conf == other.web_trigger_conf &&
conf_data == other.conf_data;
}
void
HttpAttachmentConfig::save(cereal::JSONOutputArchive &out_ar) const
{
conf_data.save(out_ar);
}
template <typename Conf, typename ...Strings>
static Conf
getAttachmentConf(const Conf &default_val, const string &profile_conf, const Strings & ...conf)
{
const Conf &profile_settings = getProfileAgentSettingWithDefault<Conf>(default_val, profile_conf);
return getConfigurationWithDefault<Conf>(profile_settings, conf...);
}
void
HttpAttachmentConfig::setGradualDeploymentIPs()
{
auto i_gradual_deployment = Singleton::Consume<I_GradualDeployment>::by<NginxAttachment>();
conf_data.setExcludeSources(i_gradual_deployment->getPolicy(I_GradualDeployment::AttachmentType::NGINX));
}
void
HttpAttachmentConfig::setWebTriggerConf()
{
web_trigger_conf = getConfigurationWithDefault<WebTriggerConf>(
WebTriggerConf::default_trigger_conf,
"HTTP manager",
"Web trigger conf"
);
}
void
HttpAttachmentConfig::setDebugLevel() {
string debug_level = getAttachmentConf<string>(
"info",
"agent.debug.flag.nginxModule",
"HTTP manager",
"Attachment debug level"
);
debug_level[0] = toupper(debug_level[0]);
if (debug_level == "Trace") {
conf_data.setNumericalValue("dbg_level", (unsigned int)DebugLevel::DBG_LEVEL_TRACE);
} else if (debug_level == "Debug") {
conf_data.setNumericalValue("dbg_level", (unsigned int)DebugLevel::DBG_LEVEL_DEBUG);
} else if (debug_level == "Info") {
conf_data.setNumericalValue("dbg_level", (unsigned int)DebugLevel::DBG_LEVEL_INFO);
} else if (debug_level == "Warning") {
conf_data.setNumericalValue("dbg_level", (unsigned int)DebugLevel::DBG_LEVEL_WARNING);
} else if (debug_level == "Error") {
conf_data.setNumericalValue("dbg_level", (unsigned int)DebugLevel::DBG_LEVEL_ERROR);
} else {
dbgWarning(D_NGINX_ATTACHMENT)
<< "Debug level \""
<< debug_level
<< "\" is not valid. using default level \"warning\"";
conf_data.setNumericalValue("dbg_level", (unsigned int)DebugLevel::DBG_LEVEL_INFO);
}
}
void
HttpAttachmentConfig::setFailOpenMode()
{
bool is_fail_open_mode_enabled = getAttachmentConf<bool>(
true,
"agent.failOpenState.nginxModule",
"HTTP manager",
"Fail Open Mode state"
);
dbgTrace(D_NGINX_ATTACHMENT)
<< "Attachment failure mode is: "
<< (is_fail_open_mode_enabled ? "Enabled" : "Disabled");
conf_data.setNumericalValue("is_fail_open_mode_enabled", is_fail_open_mode_enabled);
}
void
HttpAttachmentConfig::setFailOpenTimeout()
{
conf_data.setNumericalValue("fail_open_timeout", getAttachmentConf<uint>(
50,
"agent.failOpenTimeout.nginxModule",
"HTTP manager",
"Fail Open timeout msec"
));
conf_data.setNumericalValue("fail_open_hold_timeout", getAttachmentConf<uint>(
150,
"agent.failOpenWaitTimeout.nginxModule",
"HTTP manager",
"Fail Open wait timeout msec"
));
conf_data.setNumericalValue("res_proccessing_timeout_msec", getAttachmentConf<uint>(
3000,
"agent.resProccessingTimeout.nginxModule",
"HTTP manager",
"NGINX response processing timeout msec"
));
conf_data.setNumericalValue("req_proccessing_timeout_msec", getAttachmentConf<uint>(
3000,
"agent.reqProccessingTimeout.nginxModule",
"HTTP manager",
"NGINX request processing timeout msec"
));
conf_data.setNumericalValue("registration_thread_timeout_msec", getAttachmentConf<uint>(
100,
"agent.registrationThreadTimeout.nginxModule",
"HTTP manager",
"NGINX registration thread timeout msec"
));
conf_data.setNumericalValue("req_header_thread_timeout_msec", getAttachmentConf<uint>(
100,
"agent.reqHeaderThreadTimeout.nginxModule",
"HTTP manager",
"NGINX request header thread timeout msec"
));
conf_data.setNumericalValue("req_body_thread_timeout_msec", getAttachmentConf<uint>(
150,
"agent.reqBodyThreadTimeout.nginxModule",
"HTTP manager",
"NGINX request body thread timeout msec"
));
conf_data.setNumericalValue("res_header_thread_timeout_msec", getAttachmentConf<uint>(
100,
"agent.resHeaderThreadTimeout.nginxModule",
"HTTP manager",
"NGINX response header thread timeout msec"
));
conf_data.setNumericalValue("res_body_thread_timeout_msec", getAttachmentConf<uint>(
150,
"agent.resBodyThreadTimeout.nginxModule",
"HTTP manager",
"NGINX response body thread timeout msec"
));
conf_data.setNumericalValue("waiting_for_verdict_thread_timeout_msec", getAttachmentConf<uint>(
150,
"agent.waitThreadTimeout.nginxModule",
"HTTP manager",
"NGINX wait thread timeout msec"
));
uint inspection_mode = getAttachmentConf<uint>(
static_cast<uint>(ngx_http_inspection_mode_e::NON_BLOCKING_THREAD),
"agent.inspectionMode.nginxModule",
"HTTP manager",
"NGINX inspection mode"
);
if (inspection_mode >= ngx_http_inspection_mode_e::INSPECTION_MODE_COUNT) {
inspection_mode = ngx_http_inspection_mode_e::NON_BLOCKING_THREAD;
}
conf_data.setNumericalValue("nginx_inspection_mode", inspection_mode);
}
void
HttpAttachmentConfig::setFailOpenWaitMode()
{
bool is_fail_open_mode_hold_enabled = getAttachmentConf<bool>(
true,
"agent.failOpenWaitState.nginxModule",
"HTTP manager",
"Fail Open Mode state"
);
dbgTrace(D_NGINX_ATTACHMENT)
<< "Attachment waiting failure mode is: "
<< (is_fail_open_mode_hold_enabled ? "Enabled" : "Disabled");
conf_data.setNumericalValue("is_fail_open_mode_hold_enabled", is_fail_open_mode_hold_enabled);
}
void
HttpAttachmentConfig::setSessionsPerMinuteLimitVerdict()
{
string sessions_per_minute_limit_verdict = getAttachmentConf<string>(
"Accept",
"agent.sessionsPerMinuteLimitVerdict.nginxModule",
"HTTP manager",
"Sessions Per Minute Limit Verdict"
);
dbgTrace(D_NGINX_ATTACHMENT)
<< "Attachment sessions per minute limit verdict is: "
<< sessions_per_minute_limit_verdict;
conf_data.setStringValue("sessions_per_minute_limit_verdict", sessions_per_minute_limit_verdict);
}
void
HttpAttachmentConfig::setMaxSessionsPerMinute()
{
uint max_sessions_per_minute = getAttachmentConf<uint>(
0,
"agent.maxSessionsPerMinute.nginxModule",
"HTTP manager",
"Max Sessions Per Minute"
);
dbgTrace(D_NGINX_ATTACHMENT)
<< "Attachment max sessions per minute is: "
<< max_sessions_per_minute;
conf_data.setNumericalValue("max_sessions_per_minute", max_sessions_per_minute);
}
void
HttpAttachmentConfig::setNumOfNginxIpcElements()
{
uint num_of_nginx_ipc_elements = getProfileAgentSettingWithDefault<uint>(
NUM_OF_NGINX_IPC_ELEMENTS, "nginxAttachment.numOfNginxIpcElements"
);
dbgTrace(D_NGINX_ATTACHMENT)
<< "Number of NGINX IPC elements: "
<< num_of_nginx_ipc_elements;
conf_data.setNumericalValue("num_of_nginx_ipc_elements", num_of_nginx_ipc_elements);
}
void
HttpAttachmentConfig::setKeepAliveIntervalMsec()
{
uint keep_alive_interval_msec = getProfileAgentSettingWithDefault<uint>(
300, "attachmentRegistrator.expirationCheckSeconds"
);
keep_alive_interval_msec = (keep_alive_interval_msec * 1000) / 2;
dbgDebug(D_NGINX_ATTACHMENT)
<< "Interval keeps alives size: "
<< keep_alive_interval_msec << " msec";
conf_data.setNumericalValue("keep_alive_interval_msec", keep_alive_interval_msec);
}
void
HttpAttachmentConfig::setStaticResourcesPath()
{
string static_resources_path = getConfigurationWithDefault<string>(
DEFAULT_STATIC_RESOURCES_PATH,
"HTTP manager",
"Static resources path"
);
dbgDebug(D_NGINX_ATTACHMENT) << "Static resources path is : " << static_resources_path;
conf_data.setStringValue("static_resources_path", static_resources_path);
}
void
HttpAttachmentConfig::setDebugByContextValues()
{
DebugConfig new_ctx_cfg;
auto maybe_ctx_config = getSetting<DebugConfig>("HTTP manager", "debug context");
if(!maybe_ctx_config.ok()) {
dbgDebug(D_NGINX_ATTACHMENT) << "Failed to set context values. Setting default values";
conf_data.setDebugContext(new_ctx_cfg);
return;
}
new_ctx_cfg = maybe_ctx_config.unpack();
conf_data.setDebugContext(new_ctx_cfg);
dbgDebug(D_NGINX_ATTACHMENT)
<< "Setting context values : "
<< "client_ip: "
<< new_ctx_cfg.client
<< ", listening_ip: "
<< new_ctx_cfg.server
<< ", uri_prefix: "
<< new_ctx_cfg.uri
<< ", hostname: "
<< new_ctx_cfg.host
<< ", http_method: "
<< new_ctx_cfg.method
<< ", listening_port: "
<< new_ctx_cfg.port;
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __NGINX_ATTACHMENT_CONFIG_H__
#define __NGINX_ATTACHMENT_CONFIG_H__
#include <string>
#include <vector>
#include "nginx_attachment_util.h"
#include "cereal/archives/json.hpp"
#include "generic_rulebase/triggers_config.h"
#include "http_configuration.h"
class HttpAttachmentConfig
{
public:
void init();
bool operator==(const HttpAttachmentConfig &other) const;
void save(cereal::JSONOutputArchive &out_ar) const;
unsigned int getDebugLevel() const { return conf_data.getNumericalValue("dbg_level"); }
bool getIsFailOpenModeEnabled() const { return conf_data.getNumericalValue("is_fail_open_mode_enabled"); }
bool
getSessionsPerMinuteLimitVerdict() const
{
return conf_data.getNumericalValue("sessions_per_minute_limit_verdict");
}
unsigned int getMaxSessionsPerMinute() const { return conf_data.getNumericalValue("max_sessions_per_minute"); }
unsigned int getNumOfNginxElements() const { return conf_data.getNumericalValue("num_of_nginx_ipc_elements"); }
unsigned int getKeepAliveIntervalMsec() const { return conf_data.getNumericalValue("keep_alive_interval_msec"); }
private:
void setGradualDeploymentIPs();
void setWebTriggerConf();
void setDebugLevel();
void setFailOpenMode();
void setFailOpenTimeout();
void setFailOpenWaitMode();
void setSessionsPerMinuteLimitVerdict();
void setMaxSessionsPerMinute();
void setNumOfNginxIpcElements();
void setKeepAliveIntervalMsec();
void setStaticResourcesPath();
void setDebugByContextValues();
WebTriggerConf web_trigger_conf;
HttpAttachmentConfiguration conf_data;
};
#endif // __NGINX_ATTACHMENT_CONFIG_H__

View File

@@ -0,0 +1,163 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nginx_attachment_metric.h"
USE_DEBUG_FLAG(D_METRICS_NGINX_ATTACHMENT);
void
nginxAttachmentEvent::resetAllCounters()
{
successfull_registrations_counter = 0;
failed_registrations_counter = 0;
failed_connections_counter = 0;
accept_verdict_counter = 0;
inspect_verdict_counter = 0;
drop_verdict_counter = 0;
inject_verdict_counter = 0;
irrelevant_verdict_counter = 0;
reconf_verdict_counter = 0;
wait_verdict_counter = 0;
}
void
nginxAttachmentEvent::addNetworkingCounter(networkVerdict _verdict)
{
switch (_verdict) {
case networkVerdict::REGISTRATION_SUCCESS: {
successfull_registrations_counter += 1;
break;
}
case networkVerdict::REGISTRATION_FAIL: {
failed_registrations_counter += 1;
break;
}
case networkVerdict::CONNECTION_FAIL: {
failed_connections_counter += 1;
break;
}
default:
dbgWarning(D_METRICS_NGINX_ATTACHMENT) << "Unsupported metric type. Type: " << static_cast<int>(_verdict);
return;
}
}
void
nginxAttachmentEvent::addTrafficVerdictCounter(trafficVerdict _verdict)
{
switch (_verdict) {
case trafficVerdict::INSPECT: {
inspect_verdict_counter += 1;
break;
}
case trafficVerdict::ACCEPT: {
accept_verdict_counter += 1;
break;
}
case trafficVerdict::DROP: {
drop_verdict_counter += 1;
break;
}
case trafficVerdict::INJECT: {
inject_verdict_counter += 1;
break;
}
case trafficVerdict::IRRELEVANT: {
irrelevant_verdict_counter += 1;
break;
}
case trafficVerdict::RECONF: {
reconf_verdict_counter += 1;
break;
}
case trafficVerdict::WAIT: {
wait_verdict_counter += 1;
break;
}
default:
dbgWarning(D_METRICS_NGINX_ATTACHMENT) << "Unsupported metric type. Type: " << static_cast<int>(_verdict);
return;
}
}
void
nginxAttachmentEvent::addResponseInspectionCounter(uint64_t _counter)
{
response_inspection_counter += _counter;
}
uint64_t
nginxAttachmentEvent::getNetworkingCounter(networkVerdict _verdict) const
{
switch (_verdict) {
case networkVerdict::REGISTRATION_SUCCESS:
return successfull_registrations_counter;
case networkVerdict::REGISTRATION_FAIL:
return failed_registrations_counter;
case networkVerdict::CONNECTION_FAIL:
return failed_connections_counter;
default:
dbgWarning(D_METRICS_NGINX_ATTACHMENT) << "Unsupported metric type. Type: " << static_cast<int>(_verdict);
return 0;
}
}
uint64_t
nginxAttachmentEvent::getTrafficVerdictCounter(trafficVerdict _verdict) const
{
switch (_verdict) {
case trafficVerdict::INSPECT:
return inspect_verdict_counter;
case trafficVerdict::ACCEPT:
return accept_verdict_counter;
case trafficVerdict::DROP:
return drop_verdict_counter;
case trafficVerdict::INJECT:
return inject_verdict_counter;
case trafficVerdict::IRRELEVANT:
return irrelevant_verdict_counter;
case trafficVerdict::RECONF:
return reconf_verdict_counter;
case trafficVerdict::WAIT:
return wait_verdict_counter;
default:
dbgWarning(D_METRICS_NGINX_ATTACHMENT) << "Unsupported metric type. Type: " << static_cast<int>(_verdict);
return 0;
}
}
uint64_t
nginxAttachmentEvent::getResponseInspectionCounter() const
{
return response_inspection_counter;
}
void
nginxAttachmentMetric::upon(const nginxAttachmentEvent &event)
{
successfull_registrations.report(
event.getNetworkingCounter(nginxAttachmentEvent::networkVerdict::REGISTRATION_SUCCESS)
);
failed_registrations.report(
event.getNetworkingCounter(nginxAttachmentEvent::networkVerdict::REGISTRATION_FAIL)
);
failed_connections.report(event.getNetworkingCounter(nginxAttachmentEvent::networkVerdict::CONNECTION_FAIL));
inspect_verdict.report(event.getTrafficVerdictCounter(nginxAttachmentEvent::trafficVerdict::INSPECT));
accept_verdict.report(event.getTrafficVerdictCounter(nginxAttachmentEvent::trafficVerdict::ACCEPT));
drop_verdict.report(event.getTrafficVerdictCounter(nginxAttachmentEvent::trafficVerdict::DROP));
inject_verdict.report(event.getTrafficVerdictCounter(nginxAttachmentEvent::trafficVerdict::INJECT));
irrelevant_verdict.report(event.getTrafficVerdictCounter(nginxAttachmentEvent::trafficVerdict::IRRELEVANT));
reconf_verdict.report(event.getTrafficVerdictCounter(nginxAttachmentEvent::trafficVerdict::RECONF));
response_inspection.report(event.getResponseInspectionCounter());
}

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nginx_attachment_opaque.h"
#include "boost/uuid/uuid.hpp"
#include "boost/uuid/uuid_generators.hpp"
#include "boost/uuid/uuid_io.hpp"
#include "config.h"
#include "sasal.h"
#include "virtual_modifiers.h"
SASAL_START // HTTP Manager - Transaction data
using namespace std;
using namespace boost::uuids;
USE_DEBUG_FLAG(D_HTTP_MANAGER);
NginxAttachmentOpaque::NginxAttachmentOpaque(HttpTransactionData _transaction_data)
:
TableOpaqueSerialize<NginxAttachmentOpaque>(this),
transaction_data(move(_transaction_data)),
ctx(),
session_tenant(),
uuid()
{
try {
uuid = to_string(boost::uuids::random_generator()());
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_HTTP_MANAGER) << "Failed to generate UUID. Error: " << e.what();
}
dbgTrace(D_HTTP_MANAGER) << "Creating nginx opaque environment from: " << transaction_data;
response_compression_stream = initCompressionStream();
auto client_ip = transaction_data.getSourceIP();
std::stringstream client_ip_str;
client_ip_str << client_ip;
setSourceIdentifier("sourceip", client_ip_str.str());
ctx.registerValue("eventReferenceId", uuid, EnvKeyAttr::LogSection::DATA);
ctx.registerValue<string>(HttpTransactionData::http_proto_ctx, transaction_data.getHttpProtocol());
ctx.registerValue<string>(HttpTransactionData::method_ctx, transaction_data.getHttpMethod());
ctx.registerValue<string>(HttpTransactionData::host_name_ctx, transaction_data.getDestinationHost());
ctx.registerValue<uint16_t>(HttpTransactionData::listening_port_ctx, transaction_data.getListeningPort());
ctx.registerValue<IPAddr>(HttpTransactionData::listening_ip_ctx, transaction_data.getListeningIP());
ctx.registerValue<IPAddr>(HttpTransactionData::client_ip_ctx, transaction_data.getSourceIP());
ctx.registerValue<uint16_t>(HttpTransactionData::client_port_ctx, transaction_data.getSourcePort());
ctx.registerFunc<string>(HttpTransactionData::source_identifier, [this](){ return source_identifier; });
ctx.registerValue<string>(HttpTransactionData::uri_ctx, transaction_data.getURI());
auto decoder = makeVirtualContainer<HexDecoder<'%'>>(transaction_data.getURI());
string decoded_url(decoder.begin(), decoder.end());
auto question_mark_location = decoded_url.find('?');
if (question_mark_location != string::npos) {
ctx.registerValue(HttpTransactionData::uri_query_decoded, decoded_url.substr(question_mark_location + 1));
}
ctx.registerValue(HttpTransactionData::uri_path_decoded, decoded_url.substr(0, question_mark_location));
}
NginxAttachmentOpaque::~NginxAttachmentOpaque()
{
finiCompressionStream(response_compression_stream);
}
// LCOV_EXCL_START - sync functions, can only be tested once the sync module exists
std::unique_ptr<TableOpaqueBase>
NginxAttachmentOpaque::prototype()
{
return make_unique<NginxAttachmentOpaque>(HttpTransactionData());
}
// LCOV_EXCL_STOP
void
NginxAttachmentOpaque::setSessionTenant(const string &tenant)
{
session_tenant = tenant;
Singleton::Consume<I_Environment>::by<NginxAttachmentOpaque>()->setActiveTenant(session_tenant);
}
void
NginxAttachmentOpaque::setSourceIdentifier(const string &header_key, const string &new_source_identifier)
{
identifier_type = header_key;
source_identifier = new_source_identifier;
}
const string &
NginxAttachmentOpaque::getSourceIdentifiersType() const
{
return identifier_type;
}
void
NginxAttachmentOpaque::addToSavedData(const string &name, const string &data)
{
saved_data[name] += data;
ctx.registerValue(name, saved_data[name]);
}
void
NginxAttachmentOpaque::setSavedData(const string &name, const string &data, EnvKeyAttr::LogSection log_ctx)
{
saved_data[name] = data;
ctx.registerValue(name, data, log_ctx);
}
SASAL_END

View File

@@ -0,0 +1,94 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __NGINX_ATTACHMENT_OPAQUE_H__
#define __NGINX_ATTACHMENT_OPAQUE_H__
#include <string>
#include <set>
#include <map>
#include "compression_utils.h"
#include "generic_rulebase/generic_rulebase_context.h"
#include "http_transaction_data.h"
#include "table_opaque.h"
#include "context.h"
#include "i_environment.h"
#include "buffer.h"
class NginxAttachmentOpaque : public TableOpaqueSerialize<NginxAttachmentOpaque>, Singleton::Consume<I_Environment>
{
public:
NginxAttachmentOpaque(HttpTransactionData transaction_data);
~NginxAttachmentOpaque();
void
activateContext()
{
ctx.activate();
gen_ctx.activate();
if (session_tenant != "") {
Singleton::Consume<I_Environment>::by<NginxAttachmentOpaque>()->setActiveTenant(session_tenant);
}
}
void
deactivateContext()
{
if (session_tenant != "") {
Singleton::Consume<I_Environment>::by<NginxAttachmentOpaque>()->unsetActiveTenant();
}
gen_ctx.deactivate();
ctx.deactivate();
}
CompressionStream * getResponseCompressionStream() { return response_compression_stream; }
HttpTransactionData & getTransactionData() { return transaction_data; }
// LCOV_EXCL_START - sync functions, can only be tested once the sync module exists
template <typename T> void serialize(T &, uint) {}
static std::unique_ptr<TableOpaqueBase> prototype();
// LCOV_EXCL_STOP
static const std::string name() { return "NginxAttachmentOpaque"; }
static uint currVer() { return 0; }
static uint minVer() { return 0; }
const std::string & getSessionTenant() const { return session_tenant; }
void setSessionTenant(const std::string &tenant);
void setSourceIdentifier(const std::string &header_key, const std::string &source_identifier);
const std::string & getSourceIdentifiersType() const;
const std::string & getSessionUUID() const { return uuid; }
void addToSavedData(const std::string &name, const std::string &data);
void setSavedData(
const std::string &name,
const std::string &data,
EnvKeyAttr::LogSection log_ctx = EnvKeyAttr::LogSection::NONE
);
private:
CompressionStream *response_compression_stream;
HttpTransactionData transaction_data;
GenericRulebaseContext gen_ctx;
Context ctx;
std::string session_tenant;
std::string uuid;
std::string source_identifier;
std::string identifier_type;
std::map<std::string, std::string> saved_data;
};
#endif // __NGINX_ATTACHMENT_OPAQUE_H__

View File

@@ -0,0 +1,502 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nginx_intaker_metric.h"
USE_DEBUG_FLAG(D_METRICS_NGINX_ATTACHMENT);
void
nginxIntakerEvent::resetAllCounters()
{
successfull_inspection_counter = 0;
open_failure_inspection_counter = 0;
close_failure_inspection_counter = 0;
transparent_mode_counter = 0;
total_transparent_time = 0;
accept_verdict_counter = 0;
inspect_verdict_counter = 0;
drop_verdict_counter = 0;
inject_verdict_counter = 0;
irrelevant_verdict_counter = 0;
reconf_verdict_counter = 0;
wait_verdict_counter = 0;
req_failed_compression_counter = 0;
res_failed_compression_counter = 0;
req_failed_decompression_counter = 0;
res_failed_decompression_counter = 0;
req_successful_compression_counter = 0;
res_successful_compression_counter = 0;
req_successful_decompression_counter = 0;
res_successful_decompression_counter = 0;
corrupted_zip_skipped_session_counter = 0;
thread_timeout = 0;
reg_thread_timeout = 0;
req_header_thread_timeout = 0;
req_body_thread_timeout = 0;
res_header_thread_timeout = 0;
res_body_thread_timeout = 0;
thread_failure = 0;
req_proccessing_timeout = 0;
res_proccessing_timeout = 0;
req_failed_to_reach_upstream = 0;
cpu_event.setCPU(0);
}
ngx_http_plugin_metric_type_e
nginxIntakerEvent::EnumOfIndex(int i)
{
return static_cast<ngx_http_plugin_metric_type_e>(i);
}
void
nginxIntakerEvent::addPluginMetricCounter(const ngx_http_cp_metric_data_t *recieved_metric_data)
{
for (int i = 0; i < static_cast<int>(ngx_http_plugin_metric_type_e::METRIC_TYPES_COUNT); i++) {
ngx_http_plugin_metric_type_e metric_type = EnumOfIndex(i);
uint64_t amount = recieved_metric_data->data[i];
switch (metric_type) {
case ngx_http_plugin_metric_type_e::INSPECTION_SUCCESSES_COUNT: {
successfull_inspection_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::INSPECTION_OPEN_FAILURES_COUNT: {
open_failure_inspection_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::INSPECTION_CLOSE_FAILURES_COUNT: {
close_failure_inspection_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::TRANSPARENTS_COUNT: {
transparent_mode_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::TOTAL_TRANSPARENTS_TIME: {
total_transparent_time += amount;
break;
}
case ngx_http_plugin_metric_type_e::INSPECT_VERDICTS_COUNT: {
inspect_verdict_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::ACCEPT_VERDICTS_COUNT: {
accept_verdict_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::DROP_VERDICTS_COUNT: {
drop_verdict_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::INJECT_VERDICTS_COUNT: {
inject_verdict_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::IRRELEVANT_VERDICTS_COUNT: {
irrelevant_verdict_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::RECONF_VERDICTS_COUNT: {
reconf_verdict_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) average_overall_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) max_overall_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) min_overall_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) average_req_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) max_req_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) min_req_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) average_res_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) max_res_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT: {
if (amount > 0) min_res_processing_time_until_verdict = amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_FAILED_COMPRESSION_COUNT: {
req_failed_compression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_FAILED_COMPRESSION_COUNT: {
res_failed_compression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_FAILED_DECOMPRESSION_COUNT: {
req_failed_decompression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_FAILED_DECOMPRESSION_COUNT: {
res_failed_decompression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_SUCCESSFUL_COMPRESSION_COUNT: {
req_successful_compression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_SUCCESSFUL_COMPRESSION_COUNT: {
res_successful_compression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_SUCCESSFUL_DECOMPRESSION_COUNT: {
req_successful_decompression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_SUCCESSFUL_DECOMPRESSION_COUNT: {
res_successful_decompression_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::CORRUPTED_ZIP_SKIPPED_SESSION_COUNT: {
corrupted_zip_skipped_session_counter += amount;
break;
}
case ngx_http_plugin_metric_type_e::THREAD_TIMEOUT: {
thread_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::REG_THREAD_TIMEOUT: {
reg_thread_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_HEADER_THREAD_TIMEOUT: {
req_header_thread_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_BODY_THREAD_TIMEOUT: {
req_body_thread_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::AVERAGE_REQ_BODY_SIZE_UPON_TIMEOUT: {
if (amount > 0) average_req_body_size_upon_timeout = amount;
break;
}
case ngx_http_plugin_metric_type_e::MAX_REQ_BODY_SIZE_UPON_TIMEOUT: {
if (amount > 0) max_req_body_size_upon_timeout = amount;
break;
}
case ngx_http_plugin_metric_type_e::MIN_REQ_BODY_SIZE_UPON_TIMEOUT: {
if (amount > 0) min_req_body_size_upon_timeout = amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_HEADER_THREAD_TIMEOUT: {
res_header_thread_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_BODY_THREAD_TIMEOUT: {
res_body_thread_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::AVERAGE_RES_BODY_SIZE_UPON_TIMEOUT: {
if (amount > 0) average_res_body_size_upon_timeout = amount;
break;
}
case ngx_http_plugin_metric_type_e::MAX_RES_BODY_SIZE_UPON_TIMEOUT: {
if (amount > 0) max_res_body_size_upon_timeout = amount;
break;
}
case ngx_http_plugin_metric_type_e::MIN_RES_BODY_SIZE_UPON_TIMEOUT: {
if (amount > 0) min_res_body_size_upon_timeout = amount;
break;
}
case ngx_http_plugin_metric_type_e::THREAD_FAILURE: {
thread_failure += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_PROCCESSING_TIMEOUT: {
req_proccessing_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::RES_PROCCESSING_TIMEOUT: {
res_proccessing_timeout += amount;
break;
}
case ngx_http_plugin_metric_type_e::REQ_FAILED_TO_REACH_UPSTREAM: {
req_failed_to_reach_upstream += amount;
break;
}
case ngx_http_plugin_metric_type_e::CPU_USAGE: {
cpu_event.setCPU(amount);
break;
}
default:
dbgWarning(D_METRICS_NGINX_ATTACHMENT)
<< "Unsupported metric type. Type: " << static_cast<int>(metric_type);
return;
}
}
}
uint64_t
nginxIntakerEvent::getPluginMetricCounter(ngx_http_plugin_metric_type_e metric_type) const
{
switch (metric_type) {
case ngx_http_plugin_metric_type_e::INSPECTION_SUCCESSES_COUNT:
return successfull_inspection_counter;
case ngx_http_plugin_metric_type_e::INSPECTION_OPEN_FAILURES_COUNT:
return open_failure_inspection_counter;
case ngx_http_plugin_metric_type_e::INSPECTION_CLOSE_FAILURES_COUNT:
return close_failure_inspection_counter;
case ngx_http_plugin_metric_type_e::TRANSPARENTS_COUNT:
return transparent_mode_counter;
case ngx_http_plugin_metric_type_e::TOTAL_TRANSPARENTS_TIME:
return total_transparent_time;
case ngx_http_plugin_metric_type_e::INSPECT_VERDICTS_COUNT:
return inspect_verdict_counter;
case ngx_http_plugin_metric_type_e::ACCEPT_VERDICTS_COUNT:
return accept_verdict_counter;
case ngx_http_plugin_metric_type_e::DROP_VERDICTS_COUNT:
return drop_verdict_counter;
case ngx_http_plugin_metric_type_e::INJECT_VERDICTS_COUNT:
return inject_verdict_counter;
case ngx_http_plugin_metric_type_e::IRRELEVANT_VERDICTS_COUNT:
return irrelevant_verdict_counter;
case ngx_http_plugin_metric_type_e::RECONF_VERDICTS_COUNT:
return reconf_verdict_counter;
case ngx_http_plugin_metric_type_e::AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT:
return average_overall_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT:
return max_overall_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT:
return min_overall_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT:
return average_req_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT:
return max_req_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT:
return min_req_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT:
return average_res_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT:
return max_res_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT:
return min_res_processing_time_until_verdict;
case ngx_http_plugin_metric_type_e::REQ_FAILED_COMPRESSION_COUNT:
return req_failed_compression_counter;
case ngx_http_plugin_metric_type_e::RES_FAILED_COMPRESSION_COUNT:
return res_failed_compression_counter;
case ngx_http_plugin_metric_type_e::REQ_FAILED_DECOMPRESSION_COUNT:
return req_failed_decompression_counter;
case ngx_http_plugin_metric_type_e::RES_FAILED_DECOMPRESSION_COUNT:
return res_failed_decompression_counter;
case ngx_http_plugin_metric_type_e::REQ_SUCCESSFUL_COMPRESSION_COUNT:
return req_successful_compression_counter;
case ngx_http_plugin_metric_type_e::RES_SUCCESSFUL_COMPRESSION_COUNT:
return res_successful_compression_counter;
case ngx_http_plugin_metric_type_e::REQ_SUCCESSFUL_DECOMPRESSION_COUNT:
return req_successful_decompression_counter;
case ngx_http_plugin_metric_type_e::RES_SUCCESSFUL_DECOMPRESSION_COUNT:
return res_successful_decompression_counter;
case ngx_http_plugin_metric_type_e::CORRUPTED_ZIP_SKIPPED_SESSION_COUNT:
return corrupted_zip_skipped_session_counter;
case ngx_http_plugin_metric_type_e::THREAD_TIMEOUT:
return thread_timeout;
case ngx_http_plugin_metric_type_e::REG_THREAD_TIMEOUT:
return reg_thread_timeout;
case ngx_http_plugin_metric_type_e::REQ_HEADER_THREAD_TIMEOUT:
return req_header_thread_timeout;
case ngx_http_plugin_metric_type_e::REQ_BODY_THREAD_TIMEOUT:
return req_body_thread_timeout;
case ngx_http_plugin_metric_type_e::AVERAGE_REQ_BODY_SIZE_UPON_TIMEOUT:
return average_req_body_size_upon_timeout;
case ngx_http_plugin_metric_type_e::MAX_REQ_BODY_SIZE_UPON_TIMEOUT:
return max_req_body_size_upon_timeout;
case ngx_http_plugin_metric_type_e::MIN_REQ_BODY_SIZE_UPON_TIMEOUT:
return min_req_body_size_upon_timeout;
case ngx_http_plugin_metric_type_e::RES_HEADER_THREAD_TIMEOUT:
return res_header_thread_timeout;
case ngx_http_plugin_metric_type_e::RES_BODY_THREAD_TIMEOUT:
return res_body_thread_timeout;
case ngx_http_plugin_metric_type_e::AVERAGE_RES_BODY_SIZE_UPON_TIMEOUT:
return average_res_body_size_upon_timeout;
case ngx_http_plugin_metric_type_e::MAX_RES_BODY_SIZE_UPON_TIMEOUT:
return max_res_body_size_upon_timeout;
case ngx_http_plugin_metric_type_e::MIN_RES_BODY_SIZE_UPON_TIMEOUT:
return min_res_body_size_upon_timeout;
case ngx_http_plugin_metric_type_e::THREAD_FAILURE:
return thread_failure;
case ngx_http_plugin_metric_type_e::REQ_PROCCESSING_TIMEOUT:
return req_proccessing_timeout;
case ngx_http_plugin_metric_type_e::RES_PROCCESSING_TIMEOUT:
return res_proccessing_timeout;
case ngx_http_plugin_metric_type_e::REQ_FAILED_TO_REACH_UPSTREAM:
return req_failed_to_reach_upstream;
case ngx_http_plugin_metric_type_e::CPU_USAGE:
return static_cast<uint64_t>(cpu_event.getCPU());
default:
dbgWarning(D_METRICS_NGINX_ATTACHMENT)
<< "Unsupported metric type. Type: " << static_cast<int>(metric_type);
return 0;
}
}
void
nginxIntakerMetric::upon(const nginxIntakerEvent &event)
{
successfull_inspection_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::INSPECTION_SUCCESSES_COUNT)
);
transparent_mode_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::TRANSPARENTS_COUNT)
);
total_transparent_time.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::TOTAL_TRANSPARENTS_TIME)
);
open_failure_inspection_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::INSPECTION_OPEN_FAILURES_COUNT)
);
close_failure_inspection_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::INSPECTION_CLOSE_FAILURES_COUNT)
);
inject_verdict_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::INJECT_VERDICTS_COUNT)
);
inspect_verdict_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::INSPECT_VERDICTS_COUNT)
);
accept_verdict_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::ACCEPT_VERDICTS_COUNT)
);
drop_verdict_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::DROP_VERDICTS_COUNT)
);
irrelevant_verdict_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::IRRELEVANT_VERDICTS_COUNT)
);
reconf_verdict_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RECONF_VERDICTS_COUNT)
);
average_overall_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::AVERAGE_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT)
);
max_overall_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MAX_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT)
);
min_overall_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MIN_OVERALL_PPROCESSING_TIME_UNTIL_VERDICT)
);
average_req_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::AVERAGE_REQ_PPROCESSING_TIME_UNTIL_VERDICT)
);
max_req_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MAX_REQ_PPROCESSING_TIME_UNTIL_VERDICT)
);
min_req_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MIN_REQ_PPROCESSING_TIME_UNTIL_VERDICT)
);
average_res_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::AVERAGE_RES_PPROCESSING_TIME_UNTIL_VERDICT)
);
max_res_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MAX_RES_PPROCESSING_TIME_UNTIL_VERDICT)
);
min_res_processing_time_until_verdict.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MIN_RES_PPROCESSING_TIME_UNTIL_VERDICT)
);
req_failed_compression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_FAILED_COMPRESSION_COUNT)
);
res_failed_compression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_FAILED_COMPRESSION_COUNT)
);
req_failed_decompression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_FAILED_DECOMPRESSION_COUNT)
);
res_failed_decompression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_FAILED_DECOMPRESSION_COUNT)
);
req_successful_compression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_SUCCESSFUL_COMPRESSION_COUNT)
);
res_successful_compression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_SUCCESSFUL_COMPRESSION_COUNT)
);
req_successful_decompression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_SUCCESSFUL_DECOMPRESSION_COUNT)
);
res_successful_decompression_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_SUCCESSFUL_DECOMPRESSION_COUNT)
);
corrupted_zip_skipped_session_counter.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::CORRUPTED_ZIP_SKIPPED_SESSION_COUNT)
);
thread_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::THREAD_TIMEOUT)
);
reg_thread_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REG_THREAD_TIMEOUT)
);
req_header_thread_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_HEADER_THREAD_TIMEOUT)
);
req_body_thread_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_BODY_THREAD_TIMEOUT)
);
average_req_body_size_upon_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::AVERAGE_REQ_BODY_SIZE_UPON_TIMEOUT)
);
max_req_body_size_upon_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MAX_REQ_BODY_SIZE_UPON_TIMEOUT)
);
min_req_body_size_upon_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MIN_REQ_BODY_SIZE_UPON_TIMEOUT)
);
res_header_thread_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_HEADER_THREAD_TIMEOUT)
);
res_body_thread_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_BODY_THREAD_TIMEOUT)
);
average_res_body_size_upon_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::AVERAGE_RES_BODY_SIZE_UPON_TIMEOUT)
);
max_res_body_size_upon_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MAX_RES_BODY_SIZE_UPON_TIMEOUT)
);
min_res_body_size_upon_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::MIN_RES_BODY_SIZE_UPON_TIMEOUT)
);
thread_failure.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::THREAD_FAILURE)
);
req_proccessing_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_PROCCESSING_TIMEOUT)
);
res_proccessing_timeout.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::RES_PROCCESSING_TIMEOUT)
);
req_failed_to_reach_upstream.report(
event.getPluginMetricCounter(ngx_http_plugin_metric_type_e::REQ_FAILED_TO_REACH_UPSTREAM)
);
event.notifyCPU();
}

View File

@@ -0,0 +1,369 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nginx_parser.h"
#include "config.h"
#include <algorithm>
#include "connkey.h"
#include "compression_utils.h"
#include "nginx_attachment.h"
#include "nginx_attachment_opaque.h"
#include "user_identifiers_config.h"
#include "debug.h"
using namespace std;
USE_DEBUG_FLAG(D_NGINX_ATTACHMENT_PARSER);
Buffer NginxParser::tenant_header_key = Buffer();
static const Buffer proxy_ip_header_key("X-Forwarded-For", 15, Buffer::MemoryType::STATIC);
static const Buffer source_ip("sourceip", 8, Buffer::MemoryType::STATIC);
map<Buffer, CompressionType> NginxParser::content_encodings = {
{Buffer("identity"), CompressionType::NO_COMPRESSION},
{Buffer("gzip"), CompressionType::GZIP},
{Buffer("deflate"), CompressionType::ZLIB}
};
Maybe<HttpTransactionData>
NginxParser::parseStartTrasaction(const Buffer &data)
{
return HttpTransactionData::createTransactionData(data);
}
Maybe<ResponseCode>
NginxParser::parseResponseCode(const Buffer &data)
{
if (data.size() < sizeof(uint16_t)) {
dbgWarning(D_NGINX_ATTACHMENT_PARSER) << "Failed to get response code";
return genError("Response code size is lower than uint16_t");
}
return reinterpret_cast<const uint16_t *>(data.data())[0];
}
Maybe<uint64_t>
NginxParser::parseContentLength(const Buffer &data)
{
if (data.size() < sizeof(uint64_t)) {
dbgWarning(D_NGINX_ATTACHMENT_PARSER)
<< "Failed to get content length";
return genError("Content length size is lower than uint64");
}
return **data.getTypePtr<uint64_t>(0);
}
Maybe<Buffer>
genHeaderPart(const Buffer &raw_data, uint16_t &cur_pos)
{
if (cur_pos >= raw_data.size()) return genError("Current header data possession is after header part end");
auto value = raw_data.getTypePtr<uint16_t>(cur_pos);
if (!value.ok()) {
return genError("Failed to get header part size: " + value.getErr());
}
uint16_t part_len = *(value.unpack());
cur_pos += sizeof(uint16_t);
if (cur_pos + part_len > raw_data.size()) return genError("Header data extends beyond current buffer");
const u_char *part_data = raw_data.data();
Buffer header_part(part_data + cur_pos, part_len, Buffer::MemoryType::VOLATILE);
cur_pos += part_len;
return header_part;
}
Maybe<vector<HttpHeader>>
genHeaders(const Buffer &raw_data)
{
dbgFlow(D_NGINX_ATTACHMENT_PARSER) << "Generating headers";
uint16_t cur_pos = 0;
auto is_last_header_data = raw_data.getTypePtr<uint8_t>(cur_pos);
if (!is_last_header_data.ok()) {
return genError("Failed to get 'is last header' value: " + is_last_header_data.getErr());
}
bool is_last_header = *(is_last_header_data.unpack()) == 1;
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Current header bulk "
<< (is_last_header ? "contains " : "does not contain ")
<< "last header";
cur_pos += sizeof(uint8_t);
auto part_count = raw_data.getTypePtr<uint8_t>(cur_pos);
if (!part_count.ok()) {
return genError("Failed to get part count value: " + part_count.getErr());
}
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Current header bulk index: " << to_string(*(part_count.unpack()));
static const string key_val_desc[] = {"key", "value"};
Maybe<Buffer> header[2] = {Buffer(), Buffer()};
vector<HttpHeader> headers;
cur_pos += sizeof(uint8_t);
uint8_t cur_part = *(part_count.unpack());
while (cur_pos < raw_data.size()) {
for (int i = 0 ; i < 2 ; i ++) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Generating"
<< (is_last_header ? " last " : " ")
<< "header's "
<< key_val_desc[i];
header[i] = genHeaderPart(raw_data, cur_pos);
if (!header[i].ok()) {
return genError("Failed to generate header's " + key_val_desc[i] + ":" + header[i].getErr());
}
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Successfully generated header part. Header part type:"
<< key_val_desc[i]
<< ", data: '"
<< dumpHex(header[i].unpack())
<< "', size: "
<< header[i].unpack().size();
}
// is_last_header in bulk relates only to the last header in the bulk.
headers.emplace_back(
header[0].unpack(),
header[1].unpack(),
cur_part,
cur_pos >= raw_data.size() && is_last_header
);
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "end pos: " << cur_pos;
cur_part++;
}
return headers;
}
Maybe<vector<HttpHeader>>
NginxParser::parseRequestHeaders(const Buffer &data)
{
auto parsed_headers = genHeaders(data);
if (!parsed_headers.ok()) return parsed_headers.passErr();
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
for (const HttpHeader &header : *parsed_headers) {
auto source_identifiers = getConfigurationWithDefault<UsersAllIdentifiersConfig>(
UsersAllIdentifiersConfig(),
"rulebase",
"usersIdentifiers"
);
source_identifiers.parseRequestHeaders(header);
NginxAttachmentOpaque &opaque = i_transaction_table->getState<NginxAttachmentOpaque>();
opaque.addToSavedData(
HttpTransactionData::req_headers,
static_cast<string>(header.getKey()) + ": " + static_cast<string>(header.getValue()) + "\r\n"
);
if (NginxParser::tenant_header_key == header.getKey()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER)
<< "Identified active tenant header. Key: "
<< dumpHex(header.getKey())
<< ", Value: "
<< dumpHex(header.getValue());
string active_tenant(static_cast<string>(header.getValue()));
opaque.setSessionTenant(active_tenant);
} else if (proxy_ip_header_key == header.getKey()) {
source_identifiers.setXFFValuesToOpaqueCtx(header, UsersAllIdentifiersConfig::ExtractType::PROXYIP);
}
}
return parsed_headers;
}
Maybe<vector<HttpHeader>>
NginxParser::parseResponseHeaders(const Buffer &data)
{
return genHeaders(data);
}
Maybe<Buffer>
decompressBuffer(CompressionStream *compression_stream, const Buffer &compressed_buffer)
{
if (compressed_buffer.size() == 0) return Buffer();
auto compression_result = decompressData(compression_stream, compressed_buffer.size(), compressed_buffer.data());
if (!compression_result.ok) return genError("Failed to decompress data");
if (compression_result.output == nullptr) return Buffer();;
Buffer decompressed_buffer(
compression_result.output,
compression_result.num_output_bytes,
Buffer::MemoryType::OWNED
);
free(compression_result.output);
return decompressed_buffer;
}
Maybe<Buffer>
parseCompressedHttpBodyData(CompressionStream *compression_stream, const Buffer &body_raw_data)
{
if (compression_stream == nullptr) return genError("Cannot decompress body without compression stream");
Maybe<Buffer> decompressed_buffer_maybe = decompressBuffer(compression_stream, body_raw_data);
if (!decompressed_buffer_maybe.ok()) {
return genError("Failed to decompress buffer. Error: " + decompressed_buffer_maybe.getErr());
}
return decompressed_buffer_maybe.unpack();
}
Maybe<HttpBody>
genBody(const Buffer &raw_response_body, CompressionStream *compression_stream = nullptr)
{
uint offset = 0;
auto is_last_part_maybe = raw_response_body.getTypePtr<uint8_t>(offset);
if (!is_last_part_maybe.ok()) {
return genError("Failed to get 'is last part' value: " + is_last_part_maybe.getErr());
}
bool is_last_part = *is_last_part_maybe.unpack();
offset += sizeof(uint8_t);
auto part_count_maybe = raw_response_body.getTypePtr<uint8_t>(offset);
if (!part_count_maybe.ok()) {
return genError("Failed to get part count value: " + part_count_maybe.getErr());
}
uint8_t body_chunk_index = *part_count_maybe.unpack();
offset += sizeof(uint8_t);
Buffer body_raw_data(
raw_response_body.data() + offset,
raw_response_body.size() - offset,
Buffer::MemoryType::VOLATILE
);
if (compression_stream == nullptr) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Successfully generated body chunk from non compressed buffer";
return HttpBody(body_raw_data, is_last_part, body_chunk_index);
}
Maybe<Buffer> body_data_maybe = parseCompressedHttpBodyData(compression_stream, body_raw_data);
if (!body_data_maybe.ok()) {
dbgWarning(D_NGINX_ATTACHMENT_PARSER)
<< "Failed to decompress body chunk. Chunk index: "
<< to_string(body_chunk_index)
<< ", raw input size: "
<< body_raw_data.size();
return genError("Failed to parse HTTP body data: " + body_data_maybe.getErr());
}
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Successfully generated decompressed body chunk. Compressed original size: "
<< body_raw_data.size();
return HttpBody(body_data_maybe.unpack(), is_last_part, body_chunk_index);
}
Maybe<HttpBody>
NginxParser::parseRequestBody(const Buffer &data)
{
Maybe<HttpBody> body = genBody(data);
if (!body.ok()) return genError("Failed to generate body from buffer: " + body.getErr());
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Successfully generated request body chunk. Chunk index: "
<< to_string(body.unpack().getBodyChunkIndex())
<< ", is last chunk: "
<< (body.unpack().isLastChunk() ? "true" : "false")
<< ", size: "
<< body.unpack().getData().size()
<< ", value: "
<< dumpHex(body.unpack().getData());
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
auto &state = i_transaction_table->getState<NginxAttachmentOpaque>();
state.setSavedData(HttpTransactionData::req_body, (*body).getData());
return body;
}
Maybe<HttpBody>
NginxParser::parseResponseBody(const Buffer &raw_response_body, CompressionStream *compression_stream)
{
Maybe<HttpBody> body = genBody(raw_response_body, compression_stream);
if (!body.ok()) return genError("Failed to generate body from buffer: " + body.getErr());
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Successfully generated response body chunk. Chunk index: "
<< to_string(body.unpack().getBodyChunkIndex())
<< ", is last chunk: "
<< (body.unpack().isLastChunk() ? "true" : "false")
<< ", size: "
<< body.unpack().getData().size()
<< ", value: "
<< dumpHex(body.unpack().getData());;
return body;
}
Maybe<CompressionType>
NginxParser::parseContentEncoding(const vector<HttpHeader> &headers)
{
static const Buffer content_encoding_header_key("Content-Encoding");
auto it = find_if(
headers.begin(),
headers.end(),
[&] (const HttpHeader &http_header) { return http_header.getKey() == content_encoding_header_key; }
);
if (it == headers.end()) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Headers do not contain \"Content-Encoding\" header: "
<< "body is expected to be plain-text";
return CompressionType::NO_COMPRESSION;
}
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Found header with key \"Content-Encoding\". Value: "
<< dumpHex((*it).getValue());
auto content_encoding_maybe = convertToContentEncoding((*it).getValue());
if (!content_encoding_maybe.ok()) {
return genError(
"Failed to parse value of \"Content-Encoding\" header: " +
content_encoding_maybe.getErr()
);
}
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Successfully parsed value of \"Content-Encoding\" header";
return content_encoding_maybe.unpack();
}
Maybe<CompressionType>
NginxParser::convertToContentEncoding(const Buffer &content_encoding_header_value)
{
if (content_encoding_header_value.contains(',')) {
return genError("Multiple content encodings for a specific HTTP request/response body are not supported");
}
if (content_encodings.find(content_encoding_header_value) == content_encodings.end()) {
return genError(
"Unsupported or undefined \"Content-Encoding\" value: " +
static_cast<string>(content_encoding_header_value)
);
}
return content_encodings[content_encoding_header_value];
}

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __NGINX_PARSER_H__
#define __NGINX_PARSER_H__
#include <vector>
#include "compression_utils.h"
#include "nginx_attachment_common.h"
#include "http_transaction_common.h"
#include "http_inspection_events.h"
#include "i_encryptor.h"
class NginxParser : Singleton::Consume<I_Encryptor>
{
public:
static Maybe<HttpTransactionData> parseStartTrasaction(const Buffer &data);
static Maybe<ResponseCode> parseResponseCode(const Buffer &data);
static Maybe<uint64_t> parseContentLength(const Buffer &data);
static Maybe<std::vector<HttpHeader>> parseRequestHeaders(const Buffer &data);
static Maybe<std::vector<HttpHeader>> parseResponseHeaders(const Buffer &data);
static Maybe<HttpBody> parseRequestBody(const Buffer &data);
static Maybe<HttpBody> parseResponseBody(const Buffer &raw_response_body, CompressionStream *compression_stream);
static Maybe<CompressionType> parseContentEncoding(const std::vector<HttpHeader> &headers);
static Buffer tenant_header_key;
private:
static Maybe<CompressionType> convertToContentEncoding(const Buffer &content_encoding_header_value);
static std::map<Buffer, CompressionType> content_encodings;
};
#endif // __NGINX_PARSER_H__

View File

@@ -0,0 +1,447 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "user_identifiers_config.h"
#include "buffer.h"
#include "nginx_attachment.h"
#include "nginx_attachment_opaque.h"
#include "nginx_parser.h"
#include "cidrs_data.h"
using namespace std;
USE_DEBUG_FLAG(D_NGINX_ATTACHMENT_PARSER);
static const Buffer header_key("headerkey", 9, Buffer::MemoryType::STATIC);
static const Buffer jwt("authorization", 13, Buffer::MemoryType::STATIC);
static const Buffer xff("x-forwarded-for", 15, Buffer::MemoryType::STATIC);
static const Buffer cookie("cookie", 6, Buffer::MemoryType::STATIC);
static const Buffer source_ip("sourceip", 8, Buffer::MemoryType::STATIC);
static const Buffer oauth("_oauth2_proxy", 13, Buffer::MemoryType::STATIC);
static const Buffer empty_buffer("", 0, Buffer::MemoryType::STATIC);
const static string jwt_prefix = "Bearer ";
UsersAllIdentifiersConfig::UsersIdentifiersConfig::UsersIdentifiersConfig() : source_identifier(source_ip){};
UsersAllIdentifiersConfig::UsersIdentifiersConfig::UsersIdentifiersConfig(const std::string &identifier)
:
source_identifier(identifier)
{}
bool
UsersAllIdentifiersConfig::UsersIdentifiersConfig::operator==(const UsersIdentifiersConfig &other) const
{
return source_identifier == other.source_identifier;
}
void
UsersAllIdentifiersConfig::UsersIdentifiersConfig::load(cereal::JSONInputArchive &ar)
{
parseJSONKey<string>("sourceIdentifier", source_identifier, ar);
parseJSONKey<vector<string>>("identifierValues", identifier_values, ar);
}
bool
UsersAllIdentifiersConfig::UsersIdentifiersConfig::isEqualSourceIdentifier(const string &other) const
{
if (source_identifier.size() != other.size()) return false;
return equal(
source_identifier.begin(),
source_identifier.end(),
other.begin(),
[] (char c1, char c2) { return tolower(c1) == tolower(c2); }
);
}
UsersAllIdentifiersConfig::UsersAllIdentifiersConfig()
{
}
vector<string>
UsersAllIdentifiersConfig::getHeaderValuesFromConfig(const string &header_key) const
{
for (auto user_identifier : user_identifiers) {
if (user_identifier.isEqualSourceIdentifier(header_key)) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Match source identifier is found";
return user_identifier.getIdentifierValues();
}
}
return vector<string>();
}
void
UsersAllIdentifiersConfig::load(cereal::JSONInputArchive &ar)
{
vector<UsersIdentifiersConfig> tmp_user_identifiers;
parseJSONKey<vector<UsersIdentifiersConfig>>("sourceIdentifiers", tmp_user_identifiers, ar);
user_identifiers.clear();
user_identifiers.reserve(tmp_user_identifiers.size() + 1);
for (auto &identifier : tmp_user_identifiers) {
if (identifier.getSourceIdentifier() == header_key) {
for (const auto &header : identifier.getIdentifierValues()) {
user_identifiers.emplace_back(header);
}
} else {
user_identifiers.push_back(identifier);
}
}
vector<UsersIdentifiersConfig> default_order = {
UsersIdentifiersConfig(cookie),
UsersIdentifiersConfig(jwt),
UsersIdentifiersConfig(xff)
};
auto last_user_defined_header = find_first_of(
default_order.rbegin(),
default_order.rend(),
user_identifiers.begin(),
user_identifiers.end()
);
if (last_user_defined_header == default_order.rend()) {
user_identifiers.insert(user_identifiers.end(), default_order.begin(), default_order.end());
} else {
auto last_defined_forwards = find(default_order.begin(), default_order.end(), *last_user_defined_header);
user_identifiers.insert(user_identifiers.end(), last_defined_forwards + 1, default_order.end());
}
}
static bool
compareBufferWithoutCase(const Buffer &b1, const Buffer &b2)
{
if (b1.size() != b2.size()) return false;
return equal(b1.begin(), b1.end(), b2.begin(), [] (u_char c1, u_char c2) { return tolower(c1) == tolower(c2); });
}
void
UsersAllIdentifiersConfig::setIdentifierTopaqueCtx(const HttpHeader &header) const
{
if (compareBufferWithoutCase(jwt, header.getKey())) {
setJWTValuesToOpaqueCtx(header);
} else if (compareBufferWithoutCase(xff, header.getKey())) {
setXFFValuesToOpaqueCtx(header, ExtractType::SOURCEIDENTIFIER);
} else if (compareBufferWithoutCase(cookie, header.getKey())) {
setCookieValuesToOpaqueCtx(header);
} else {
setCustomHeaderToOpaqueCtx(header);
}
}
bool
UsersAllIdentifiersConfig::isHigherPriority(const string &current_identifier, const string &header_key) const
{
for (auto user_identifier : user_identifiers) {
if (user_identifier.isEqualSourceIdentifier(current_identifier)) return false;
if (user_identifier.isEqualSourceIdentifier(header_key)) return true;
}
return false;
}
void
UsersAllIdentifiersConfig::setJWTValuesToOpaqueCtx(const HttpHeader &header) const
{
const vector<string> jwt_values = getHeaderValuesFromConfig(header.getKey());
if (jwt_values.size() == 0) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "No JWT keys exists in configuration";
return;
}
if (bcmp(header.getValue().data(), jwt_prefix.c_str(), jwt_prefix.size()) != 0) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Invalid JWT header, 'Bearer' prefix missing";
return;
}
int start_dot = -1;
int end_dot = -1;
for (uint i = 0 ; i < header.getValue().size() ; i++) {
if (header.getValue()[i] == '.') {
if (start_dot < 0) {
start_dot = i;
} else if (end_dot < 0) {
end_dot = i;
}
}
}
if (start_dot < 0 || end_dot < 0) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "The header does not contain dots";
return;
}
string jwt_str(
reinterpret_cast<const char *>(header.getValue().data()),
start_dot + 1,
end_dot - start_dot - 1
);
I_Encryptor *encryptor = Singleton::Consume<I_Encryptor>::by<NginxParser>();
auto decoded_jwt = encryptor->base64Decode(jwt_str);
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Base64 decoded JWT: " << decoded_jwt;
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
if (!i_transaction_table || !i_transaction_table->hasState<NginxAttachmentOpaque>()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Can't get the transaction table";
return;
}
NginxAttachmentOpaque &opaque = i_transaction_table->getState<NginxAttachmentOpaque>();
stringstream ss;
ss.str(decoded_jwt);
cereal::JSONInputArchive in_ar(ss);
for (const string &field_name : jwt_values) {
try {
string tmp_val;
in_ar(cereal::make_nvp(field_name, tmp_val));
opaque.setSourceIdentifier(header.getKey(), tmp_val);
dbgDebug(D_NGINX_ATTACHMENT_PARSER)
<< "Added source identifir to context. Key: "
<< field_name
<< ". Value: "
<< tmp_val;
return;
} catch (const cereal::Exception &e) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER)
<< "Unable to find value for the key: "
<< field_name
<< ". Error: "
<< e.what();
}
}
}
static string
stripOptionalPort(const string::const_iterator &first, const string::const_iterator &last)
{
// Microsoft XFF+IPv6+Port yikes - see also here https://github.com/eclipse/jetty.project/issues/3630
if (*first == '[') {
// Possible bracketed IPv6 address such as "[2001:db8::1]" + optional numeric ":<port>"
auto close_bracket = find(first + 1, last, ']');
if (close_bracket == last) return string(first, last);
return string(first+1, close_bracket);
}
auto first_colon = find(first, last, ':');
if (first_colon == last) return string(first, last);
// If there is more than one colon it means its probably IPv6 address without brackets
auto second_colon = find(first_colon + 1, last, ':');
if (second_colon != last) return string(first, last);
// If there's only one colon it can't be IPv6 and can only be IPv4 with port
return string(first, first_colon);
}
static vector<string>
split(const string &str)
{
vector<string> elems;
elems.reserve(str.size() / 8 + 1);
auto sub_start = str.cbegin(), sub_end = str.cbegin();
for (auto iter = str.cbegin(); iter != str.cend(); ++iter) {
if (isspace(*iter)) {
if (sub_start == iter) {
++sub_start;
++sub_end;
}
} else if (*iter == ',') {
if (sub_start != sub_end) {
elems.push_back(stripOptionalPort(sub_start, sub_end));
}
sub_end = iter + 1;
sub_start = iter + 1;
} else {
sub_end = iter + 1;
}
}
if (sub_start != sub_end) {
elems.push_back(stripOptionalPort(sub_start, sub_end));
}
return elems;
}
static bool
isIpTrusted(const string &value, const vector<CIDRSData> &cidr_values)
{
if (cidr_values.empty()) return true;
for(const auto &cidr_data : cidr_values) {
if (cidr_data.contains(value)) return true;
}
return false;
}
Maybe<string>
UsersAllIdentifiersConfig::parseXForwardedFor(const string &str) const
{
vector<string> header_values = split(str);
if (header_values.empty()) return genError("No IP found in the xff header list");
vector<string> xff_values = getHeaderValuesFromConfig("x-forwarded-for");
vector<CIDRSData> cidr_values(xff_values.begin(), xff_values.end());
for (const string &value : header_values) {
if (!IPAddr::createIPAddr(value).ok()) {
dbgWarning(D_NGINX_ATTACHMENT_PARSER) << "Invalid IP address found in the xff header IPs list: " << value;
return genError("Invalid IP address");
}
if (!isIpTrusted(value, cidr_values)) return genError("Untrusted Ip found");
}
return header_values[0];
}
void
UsersAllIdentifiersConfig::setXFFValuesToOpaqueCtx(const HttpHeader &header, ExtractType type) const
{
auto value = parseXForwardedFor(header.getValue());
if (!value.ok()) {
dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Could not extract source identifier from X-Forwarded-For header";
return;
};
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
if (!i_transaction_table || !i_transaction_table->hasState<NginxAttachmentOpaque>()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Can't get the transaction table";
return;
}
NginxAttachmentOpaque &opaque = i_transaction_table->getState<NginxAttachmentOpaque>();
if (type == ExtractType::SOURCEIDENTIFIER) {
opaque.setSourceIdentifier(header.getKey(), value.unpack());
dbgDebug(D_NGINX_ATTACHMENT_PARSER)
<< "Added source identifir to XFF "
<< value.unpack();
} else {
opaque.setSavedData(HttpTransactionData::proxy_ip_ctx, value.unpack());
}
}
void
UsersAllIdentifiersConfig::setCustomHeaderToOpaqueCtx(const HttpHeader &header) const
{
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
if (!i_transaction_table || !i_transaction_table->hasState<NginxAttachmentOpaque>()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Can't get the transaction table";
return;
}
i_transaction_table->getState<NginxAttachmentOpaque>().setSourceIdentifier(header.getKey(), header.getValue());
dbgDebug(D_NGINX_ATTACHMENT_PARSER)
<< "Added source identifir to custom header: "
<< static_cast<string>(header.getValue());
return;
}
Maybe<string>
UsersAllIdentifiersConfig::parseCookieElement(
const string::const_iterator &start,
const string::const_iterator &end,
const string &key) const
{
auto curr_pos = start;
// Skip whitespace
for (; curr_pos != end && isspace(*curr_pos); ++curr_pos);
// Check key
for (auto key_pos = key.begin(); key_pos != key.end(); ++key_pos) {
if (curr_pos == end || tolower(*curr_pos) != tolower(*key_pos)) return genError("Key value not found");
++curr_pos;
}
// Skip whitespace
for (; curr_pos != end && isspace(*curr_pos); ++curr_pos);
// Check for '='
if (curr_pos == end || *curr_pos != '=') return genError("Equal sign not found");
++curr_pos;
// Skip whitespace
for (; curr_pos != end && isspace(*curr_pos); ++curr_pos);
auto value_start = curr_pos;
// Read value
for (; curr_pos != end && !isspace(*curr_pos); ++curr_pos);
auto value_end = curr_pos;
// Verify value read currectly - should be only whitespaces to the end;
for (; curr_pos != end && isspace(*curr_pos); ++curr_pos);
if (curr_pos != end) return genError("Unexpected characters when reading a value");
return string(value_start, value_end);
}
Buffer
UsersAllIdentifiersConfig::extractKeyValueFromCookie(const string &cookie_value, const string &key) const
{
auto curr_start = cookie_value.begin();
auto end = cookie_value.end();
while (curr_start != end) {
auto curr_end = find(curr_start, end, ';');
auto res = parseCookieElement(curr_start, curr_end, key);
if (res.ok()) {
if (key != oauth) return *res;
I_Encryptor *encryptor = Singleton::Consume<I_Encryptor>::by<NginxParser>();
auto decoded_value = encryptor->base64Decode(*res);
auto decoded_end = find(decoded_value.begin(), decoded_value.end(), '|');
return Buffer(string(decoded_value.begin(), decoded_end));
}
if (curr_end != end) ++curr_end;
curr_start = curr_end;
}
return empty_buffer;
}
void
UsersAllIdentifiersConfig::setCookieValuesToOpaqueCtx(const HttpHeader &header) const
{
vector<string> cookie_keys = getHeaderValuesFromConfig(header.getKey());
cookie_keys.push_back(oauth);
cookie_keys.push_back("jsessionid");
for (const string &key : cookie_keys) {
string value = extractKeyValueFromCookie(header.getValue(), key);
if (!value.empty()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Set source identifier from cookie: Oauth 2";
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
if (!i_transaction_table || !i_transaction_table->hasState<NginxAttachmentOpaque>()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Can't get the transaction table";
return;
}
NginxAttachmentOpaque &opaque = i_transaction_table->getState<NginxAttachmentOpaque>();
opaque.setSourceIdentifier(header.getKey(), value);
return;
}
}
}
void
UsersAllIdentifiersConfig::parseRequestHeaders(const HttpHeader &header) const
{
auto i_transaction_table = Singleton::Consume<I_TableSpecific<SessionID>>::by<NginxAttachment>();
if (!i_transaction_table || !i_transaction_table->hasState<NginxAttachmentOpaque>()) {
dbgDebug(D_NGINX_ATTACHMENT_PARSER) << "Can't get the transaction table";
return;
}
NginxAttachmentOpaque &opaque = i_transaction_table->getState<NginxAttachmentOpaque>();
const string &current_identifier = opaque.getSourceIdentifiersType();
if (!isHigherPriority(current_identifier, header.getKey())) return;
setIdentifierTopaqueCtx(header);
}