// 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 #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); bool is_keep_alive_ctx = getenv("SAAS_KEEP_ALIVE_HDR_NAME") != nullptr; map NginxParser::content_encodings = { {Buffer("identity"), CompressionType::NO_COMPRESSION}, {Buffer("gzip"), CompressionType::GZIP}, {Buffer("deflate"), CompressionType::ZLIB} }; Maybe NginxParser::parseStartTrasaction(const Buffer &data) { return HttpTransactionData::createTransactionData(data); } Maybe 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(data.data())[0]; } Maybe 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(0); } Maybe 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(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> 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(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(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 header[2] = {Buffer(), Buffer()}; vector 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; } static vector getActivetenantAndProfile(const string &str, const string &deli = ",") { vector elems; elems.reserve(2); int start = 0; int end = str.find(deli); while (end != -1) { elems.push_back(str.substr(start, end - start)); start = end + deli.size(); end = str.find(deli, start); } elems.push_back(str.substr(start, end - start)); if (elems.size() == 1) { elems.push_back(""); } return elems; } Maybe> NginxParser::parseRequestHeaders(const Buffer &data, const unordered_set &ignored_headers) { auto maybe_parsed_headers = genHeaders(data); if (!maybe_parsed_headers.ok()) return maybe_parsed_headers.passErr(); auto i_transaction_table = Singleton::Consume>::by(); auto parsed_headers = maybe_parsed_headers.unpack(); NginxAttachmentOpaque &opaque = i_transaction_table->getState(); if (is_keep_alive_ctx || !ignored_headers.empty()) { bool is_last_header_removed = false; parsed_headers.erase( remove_if( parsed_headers.begin(), parsed_headers.end(), [&opaque, &is_last_header_removed, &ignored_headers](const HttpHeader &header) { string hdr_key = static_cast(header.getKey()); string hdr_val = static_cast(header.getValue()); if ( opaque.setKeepAliveCtx(hdr_key, hdr_val) || ignored_headers.find(hdr_key) != ignored_headers.end() ) { dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Header was removed from headers list: " << hdr_key; if (header.isLastHeader()) { dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Last header was removed from headers list"; is_last_header_removed = true; } return true; } return false; } ), parsed_headers.end() ); if (is_last_header_removed) { dbgTrace(D_NGINX_ATTACHMENT_PARSER) << "Adjusting last header flag"; if (!parsed_headers.empty()) parsed_headers.back().setIsLastHeader(); } } for (const HttpHeader &header : parsed_headers) { auto source_identifiers = getConfigurationWithDefault( UsersAllIdentifiersConfig(), "rulebase", "usersIdentifiers" ); source_identifiers.parseRequestHeaders(header); opaque.addToSavedData( HttpTransactionData::req_headers, static_cast(header.getKey()) + ": " + static_cast(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()); auto active_tenant_and_profile = getActivetenantAndProfile(header.getValue()); opaque.setSessionTenantAndProfile(active_tenant_and_profile[0], active_tenant_and_profile[1]); } else if (proxy_ip_header_key == header.getKey()) { source_identifiers.setXFFValuesToOpaqueCtx(header, UsersAllIdentifiersConfig::ExtractType::PROXYIP); } } return parsed_headers; } Maybe> NginxParser::parseResponseHeaders(const Buffer &data) { return genHeaders(data); } Maybe 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 parseCompressedHttpBodyData(CompressionStream *compression_stream, const Buffer &body_raw_data) { if (compression_stream == nullptr) return genError("Cannot decompress body without compression stream"); Maybe 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 genBody(const Buffer &raw_response_body, CompressionStream *compression_stream = nullptr) { uint offset = 0; auto is_last_part_maybe = raw_response_body.getTypePtr(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(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 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 NginxParser::parseRequestBody(const Buffer &data) { Maybe 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>::by(); auto &state = i_transaction_table->getState(); state.setSavedData(HttpTransactionData::req_body, (*body).getData()); return body; } Maybe NginxParser::parseResponseBody(const Buffer &raw_response_body, CompressionStream *compression_stream) { Maybe 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 NginxParser::parseContentEncoding(const vector &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 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(content_encoding_header_value) ); } return content_encodings[content_encoding_header_value]; }