From 7de93846d8c504c5c87e424c0a3ae709a9382070 Mon Sep 17 00:00:00 2001 From: Dom Del Nano Date: Wed, 20 Nov 2024 08:05:03 +0000 Subject: [PATCH] Add support for TLS protocol tracing Signed-off-by: Dom Del Nano --- src/stirling/binaries/stirling_wrapper.cc | 2 +- .../socket_tracer/BUILD.bazel | 21 +++ .../socket_tracer/bcc_bpf/BUILD.bazel | 1 + .../bcc_bpf/protocol_inference.h | 51 +++++- .../bcc_bpf/protocol_inference_test.cc | 24 +++ .../socket_tracer/bcc_bpf_intf/common.h | 1 + .../socket_tracer/conn_tracker.cc | 1 + .../socket_tracer/data_stream.cc | 4 + .../socket_tracer/protocols/BUILD.bazel | 1 + .../socket_tracer/protocols/stitchers.h | 1 + .../socket_tracer/protocols/types.h | 4 +- .../socket_tracer/socket_trace_connector.cc | 39 ++++ .../socket_tracer/socket_trace_connector.h | 9 +- .../socket_tracer/socket_trace_tables.h | 1 + .../testing/containers/ssl/nginx.conf | 2 +- .../testing/protocol_checkers.cc | 16 ++ .../socket_tracer/testing/protocol_checkers.h | 1 + .../socket_tracer/tls_table.h | 69 ++++++++ .../socket_tracer/tls_trace_bpf_test.cc | 166 ++++++++++++++++++ 19 files changed, 407 insertions(+), 7 deletions(-) create mode 100644 src/stirling/source_connectors/socket_tracer/tls_table.h create mode 100644 src/stirling/source_connectors/socket_tracer/tls_trace_bpf_test.cc diff --git a/src/stirling/binaries/stirling_wrapper.cc b/src/stirling/binaries/stirling_wrapper.cc index 8bb8ac8bb63..fb5e59126cf 100644 --- a/src/stirling/binaries/stirling_wrapper.cc +++ b/src/stirling/binaries/stirling_wrapper.cc @@ -62,7 +62,7 @@ DEFINE_string(trace, "", "Dynamic trace to deploy. Either (1) the path to a file containing PxL or IR trace " "spec, or (2) : for full-function tracing."); DEFINE_string(print_record_batches, - "http_events,mysql_events,pgsql_events,redis_events,cql_events,dns_events", + "http_events,mysql_events,pgsql_events,redis_events,cql_events,dns_events,tls_events", "Comma-separated list of tables to print."); DEFINE_bool(init_only, false, "If true, only runs the init phase and exits. For testing."); DEFINE_int32(timeout_secs, -1, diff --git a/src/stirling/source_connectors/socket_tracer/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/BUILD.bazel index f738dd92d8c..c552a49ac50 100644 --- a/src/stirling/source_connectors/socket_tracer/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/BUILD.bazel @@ -516,6 +516,27 @@ pl_cc_bpf_test( ], ) +pl_cc_bpf_test( + name = "tls_trace_bpf_test", + timeout = "long", + srcs = ["tls_trace_bpf_test.cc"], + flaky = True, + shard_count = 2, + tags = [ + "cpu:16", + "no_asan", + "requires_bpf", + ], + deps = [ + ":cc_library", + "//src/common/testing/test_utils:cc_library", + "//src/stirling/source_connectors/socket_tracer/testing:cc_library", + "//src/stirling/source_connectors/socket_tracer/testing/container_images:curl_container", + "//src/stirling/source_connectors/socket_tracer/testing/container_images:nginx_openssl_3_0_8_container", + "//src/stirling/testing:cc_library", + ], +) + pl_cc_bpf_test( name = "dyn_lib_trace_bpf_test", timeout = "moderate", diff --git a/src/stirling/source_connectors/socket_tracer/bcc_bpf/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/bcc_bpf/BUILD.bazel index 5d13f478652..1309620ac16 100644 --- a/src/stirling/source_connectors/socket_tracer/bcc_bpf/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/bcc_bpf/BUILD.bazel @@ -82,6 +82,7 @@ pl_cc_test( "ENABLE_NATS_TRACING=true", "ENABLE_MONGO_TRACING=true", "ENABLE_AMQP_TRACING=true", + "ENABLE_TLS_TRACING=true", ], deps = [ "//src/stirling/bpf_tools/bcc_bpf:headers", diff --git a/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference.h b/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference.h index 36aedc1d5d2..e6f89610b98 100644 --- a/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference.h +++ b/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference.h @@ -60,6 +60,46 @@ static __inline enum message_type_t infer_http_message(const char* buf, size_t c return kUnknown; } +static __inline enum message_type_t infer_tls_message(const char* buf, size_t count) { + if (count < 6) { + return kUnknown; + } + + uint8_t content_type = buf[0]; + // TLS content types correspond to the following: + // 0x14: ChangeCipherSpec + // 0x15: Alert + // 0x16: Handshake + // 0x17: ApplicationData + // 0x18: Heartbeat + if (content_type != 0x16) { + return kUnknown; + } + + uint16_t legacy_version = buf[1] << 8 | buf[2]; + // TLS versions correspond to the following: + // 0x0300: SSL 3.0 + // 0x0301: TLS 1.0 + // 0x0302: TLS 1.1 + // 0x0303: TLS 1.2 + // 0x0304: TLS 1.3 + if (legacy_version < 0x0300 || legacy_version > 0x0304) { + return kUnknown; + } + + uint8_t handshake_type = buf[5]; + // Check for ServerHello + if (handshake_type == 2) { + return kResponse; + } + // Check for ClientHello + if (handshake_type == 1) { + return kRequest; + } + + return kUnknown; +} + // Cassandra frame: // 0 8 16 24 32 40 // +---------+---------+---------+---------+---------+ @@ -699,7 +739,16 @@ static __inline struct protocol_message_t infer_protocol(const char* buf, size_t // role by considering which side called accept() vs connect(). Once the clean-up // above is done, the code below can be turned into a chained ternary. // PROTOCOL_LIST: Requires update on new protocols. - if (ENABLE_HTTP_TRACING && (inferred_message.type = infer_http_message(buf, count)) != kUnknown) { + // + // TODO(ddelnano): TLS tracing should be handled differently in the future as we want to be able + // to trace the handshake and the application data separately (gh#2095). The current connection + // tracker model only works with one or the other, meaning if TLS tracing is enabled, tracing the + // plaintext within an encrypted conn will not work. ENABLE_TLS_TRACING will default to false + // until this is revisted. + if (ENABLE_TLS_TRACING && (inferred_message.type = infer_tls_message(buf, count)) != kUnknown) { + inferred_message.protocol = kProtocolTLS; + } else if (ENABLE_HTTP_TRACING && + (inferred_message.type = infer_http_message(buf, count)) != kUnknown) { inferred_message.protocol = kProtocolHTTP; } else if (ENABLE_CQL_TRACING && (inferred_message.type = infer_cql_message(buf, count)) != kUnknown) { diff --git a/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference_test.cc b/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference_test.cc index 0bb30f0e86c..2ad70eac4a5 100644 --- a/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference_test.cc +++ b/src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference_test.cc @@ -482,3 +482,27 @@ TEST(ProtocolInferenceTest, AMQPResponse) { EXPECT_EQ(protocol_message.protocol, kProtocolAMQP); EXPECT_EQ(protocol_message.type, kResponse); } + +TEST(ProtocolInferenceTest, TLSRequest) { + struct conn_info_t conn_info = {}; + // TLS Client Hello + constexpr uint8_t kReqFrame[] = { + 0x16, 0x03, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x7b, 0x7b, 0x7b, + }; + auto protocol_message = + infer_protocol(reinterpret_cast(kReqFrame), sizeof(kReqFrame), &conn_info); + EXPECT_EQ(protocol_message.protocol, kProtocolTLS); + EXPECT_EQ(protocol_message.type, kRequest); +} + +TEST(ProtocolInferenceTest, TLSResponse) { + struct conn_info_t conn_info = {}; + // TLS Server Hello + constexpr uint8_t kRespFrame[] = { + 0x16, 0x03, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0xfc, 0x03, 0x03, 0x7b, 0x7b, 0x7b, + }; + auto protocol_message = + infer_protocol(reinterpret_cast(kRespFrame), sizeof(kRespFrame), &conn_info); + EXPECT_EQ(protocol_message.protocol, kProtocolTLS); + EXPECT_EQ(protocol_message.type, kResponse); +} diff --git a/src/stirling/source_connectors/socket_tracer/bcc_bpf_intf/common.h b/src/stirling/source_connectors/socket_tracer/bcc_bpf_intf/common.h index 18018ea0a85..7277c3f864f 100644 --- a/src/stirling/source_connectors/socket_tracer/bcc_bpf_intf/common.h +++ b/src/stirling/source_connectors/socket_tracer/bcc_bpf_intf/common.h @@ -51,6 +51,7 @@ enum traffic_protocol_t { kProtocolKafka = 10, kProtocolMux = 11, kProtocolAMQP = 12, + kProtocolTLS = 13, // We use magic enum to iterate through protocols in C++ land, // and don't want the C-enum-size trick to show up there. #ifndef __cplusplus diff --git a/src/stirling/source_connectors/socket_tracer/conn_tracker.cc b/src/stirling/source_connectors/socket_tracer/conn_tracker.cc index 3ce7e20a064..35ca7f6fc38 100644 --- a/src/stirling/source_connectors/socket_tracer/conn_tracker.cc +++ b/src/stirling/source_connectors/socket_tracer/conn_tracker.cc @@ -674,6 +674,7 @@ auto CreateTraceRoles() { res.Set(kProtocolKafka, {kRoleServer}); res.Set(kProtocolMux, {kRoleServer}); res.Set(kProtocolAMQP, {kRoleServer}); + res.Set(kProtocolTLS, {kRoleServer}); DCHECK(res.AreAllKeysSet()); return res; diff --git a/src/stirling/source_connectors/socket_tracer/data_stream.cc b/src/stirling/source_connectors/socket_tracer/data_stream.cc index 7a394eb4dc0..d0fda642539 100644 --- a/src/stirling/source_connectors/socket_tracer/data_stream.cc +++ b/src/stirling/source_connectors/socket_tracer/data_stream.cc @@ -215,6 +215,10 @@ template void DataStream::ProcessBytesToFrames( message_type_t type, protocols::mongodb::StateWrapper* state); + +template void DataStream::ProcessBytesToFrames(message_type_t type, + protocols::NoState* state); void DataStream::Reset() { data_buffer_.Reset(); has_new_events_ = false; diff --git a/src/stirling/source_connectors/socket_tracer/protocols/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/protocols/BUILD.bazel index dba40e9a05a..1026cd6945c 100644 --- a/src/stirling/source_connectors/socket_tracer/protocols/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/protocols/BUILD.bazel @@ -46,5 +46,6 @@ pl_cc_library( "//src/stirling/source_connectors/socket_tracer/protocols/nats:cc_library", "//src/stirling/source_connectors/socket_tracer/protocols/pgsql:cc_library", "//src/stirling/source_connectors/socket_tracer/protocols/redis:cc_library", + "//src/stirling/source_connectors/socket_tracer/protocols/tls:cc_library", ], ) diff --git a/src/stirling/source_connectors/socket_tracer/protocols/stitchers.h b/src/stirling/source_connectors/socket_tracer/protocols/stitchers.h index 81cc490ec06..bbdbd2449c0 100644 --- a/src/stirling/source_connectors/socket_tracer/protocols/stitchers.h +++ b/src/stirling/source_connectors/socket_tracer/protocols/stitchers.h @@ -31,3 +31,4 @@ #include "src/stirling/source_connectors/socket_tracer/protocols/nats/stitcher.h" // IWYU pragma: export #include "src/stirling/source_connectors/socket_tracer/protocols/pgsql/stitcher.h" // IWYU pragma: export #include "src/stirling/source_connectors/socket_tracer/protocols/redis/stitcher.h" // IWYU pragma: export +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h" // IWYU pragma: export diff --git a/src/stirling/source_connectors/socket_tracer/protocols/types.h b/src/stirling/source_connectors/socket_tracer/protocols/types.h index 24aa3ffd024..560da8e2433 100644 --- a/src/stirling/source_connectors/socket_tracer/protocols/types.h +++ b/src/stirling/source_connectors/socket_tracer/protocols/types.h @@ -34,6 +34,7 @@ #include "src/stirling/source_connectors/socket_tracer/protocols/nats/types.h" #include "src/stirling/source_connectors/socket_tracer/protocols/pgsql/types.h" #include "src/stirling/source_connectors/socket_tracer/protocols/redis/types.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h" namespace px { namespace stirling { @@ -53,7 +54,8 @@ using FrameDequeVariant = std::variant>, absl::flat_hash_map>, absl::flat_hash_map>, - absl::flat_hash_map>>; + absl::flat_hash_map>, + absl::flat_hash_map>>; // clang-format off } // namespace protocols diff --git a/src/stirling/source_connectors/socket_tracer/socket_trace_connector.cc b/src/stirling/source_connectors/socket_tracer/socket_trace_connector.cc index 5e18e70d504..35247403aa4 100644 --- a/src/stirling/source_connectors/socket_tracer/socket_trace_connector.cc +++ b/src/stirling/source_connectors/socket_tracer/socket_trace_connector.cc @@ -117,6 +117,11 @@ DEFINE_int32(stirling_enable_mongodb_tracing, gflags::Int32FromEnv("PX_STIRLING_ENABLE_MONGODB_TRACING", px::stirling::TraceMode::OnForNewerKernel), "If true, stirling will trace and process MongoDB messages"); +DEFINE_int32( + stirling_enable_tls_tracing, + gflags::Int32FromEnv("PX_STIRLING_ENABLE_TLS_TRACING", px::stirling::TraceMode::Off), + "If true, stirling will trace and process TLS protocol (not the TLS payload) messages. Note: " + "this disables tracing the plaintext within encrypted connections until gh#2095 is addressed."); DEFINE_bool(stirling_disable_golang_tls_tracing, gflags::BoolFromEnv("PX_STIRLING_DISABLE_GOLANG_TLS_TRACING", false), "If true, stirling will not trace TLS traffic for Go applications. This implies " @@ -283,6 +288,10 @@ void SocketTraceConnector::InitProtocolTransferSpecs() { kAMQPTableNum, {kRoleClient, kRoleServer}, TRANSFER_STREAM_PROTOCOL(amqp)}}, + {kProtocolTLS, TransferSpec{FLAGS_stirling_enable_tls_tracing, + kTLSTableNum, + {kRoleClient, kRoleServer}, + TRANSFER_STREAM_PROTOCOL(tls)}}, {kProtocolUnknown, TransferSpec{/* trace_mode */ px::stirling::TraceMode::Off, /* table_num */ static_cast(-1), /* trace_roles */ {}, @@ -491,6 +500,7 @@ Status SocketTraceConnector::InitBPF() { absl::StrCat("-DENABLE_NATS_TRACING=", protocol_transfer_specs_[kProtocolNATS].enabled), absl::StrCat("-DENABLE_AMQP_TRACING=", protocol_transfer_specs_[kProtocolAMQP].enabled), absl::StrCat("-DENABLE_MONGO_TRACING=", protocol_transfer_specs_[kProtocolMongo].enabled), + absl::StrCat("-DENABLE_TLS_TRACING=", protocol_transfer_specs_[kProtocolTLS].enabled), absl::StrCat("-DBPF_LOOP_LIMIT=", FLAGS_stirling_bpf_loop_limit), absl::StrCat("-DBPF_CHUNK_LIMIT=", FLAGS_stirling_bpf_chunk_limit), }; @@ -1686,6 +1696,35 @@ void SocketTraceConnector::AppendMessage(ConnectorContext* ctx, const ConnTracke #endif } +template <> +void SocketTraceConnector::AppendMessage(ConnectorContext* ctx, const ConnTracker& conn_tracker, + protocols::tls::Record record, DataTable* data_table) { + protocols::tls::Frame& req_message = record.req; + protocols::tls::Frame& resp_message = record.resp; + + md::UPID upid(ctx->GetASID(), conn_tracker.conn_id().upid.pid, + conn_tracker.conn_id().upid.start_time_ticks); + + DataTable::RecordBuilder<&kTLSTable> r(data_table, resp_message.timestamp_ns); + r.Append(resp_message.timestamp_ns); + r.Append(upid.value()); + // Note that there is a string copy here, + // But std::move is not allowed because we re-use conn object. + r.Append(conn_tracker.remote_endpoint().AddrStr()); + r.Append(conn_tracker.remote_endpoint().port()); + r.Append(conn_tracker.local_endpoint().AddrStr()); + r.Append(conn_tracker.local_endpoint().port()); + r.Append(conn_tracker.role()); + r.Append(static_cast(req_message.content_type)); + r.Append(static_cast(req_message.legacy_version)); + r.Append(ToJSONString(req_message.extensions), kMaxHTTPHeadersBytes); + r.Append( + CalculateLatency(req_message.timestamp_ns, resp_message.timestamp_ns)); +#ifndef NDEBUG + r.Append(PXInfoString(conn_tracker, record)); +#endif +} + void SocketTraceConnector::SetupOutput(const std::filesystem::path& path) { DCHECK(!path.empty()); diff --git a/src/stirling/source_connectors/socket_tracer/socket_trace_connector.h b/src/stirling/source_connectors/socket_tracer/socket_trace_connector.h index b493cb5b922..ca4128a5982 100644 --- a/src/stirling/source_connectors/socket_tracer/socket_trace_connector.h +++ b/src/stirling/source_connectors/socket_tracer/socket_trace_connector.h @@ -66,6 +66,7 @@ DECLARE_int32(stirling_enable_kafka_tracing); DECLARE_int32(stirling_enable_mux_tracing); DECLARE_int32(stirling_enable_amqp_tracing); DECLARE_int32(stirling_enable_mongodb_tracing); +DECLARE_int32(stirling_enable_tls_tracing); DECLARE_bool(stirling_disable_self_tracing); DECLARE_string(stirling_role_to_trace); @@ -91,13 +92,14 @@ enum TraceMode : int32_t { OnForNewerKernel = 2, }; +// PROTOCOL_LIST class SocketTraceConnector : public BCCSourceConnector { public: static constexpr std::string_view kName = "socket_tracer"; // PROTOCOL_LIST - static constexpr auto kTables = - MakeArray(kConnStatsTable, kHTTPTable, kMySQLTable, kCQLTable, kPGSQLTable, kDNSTable, - kRedisTable, kNATSTable, kKafkaTable, kMuxTable, kAMQPTable, kMongoDBTable); + static constexpr auto kTables = MakeArray( + kConnStatsTable, kHTTPTable, kMySQLTable, kCQLTable, kPGSQLTable, kDNSTable, kRedisTable, + kNATSTable, kKafkaTable, kMuxTable, kAMQPTable, kMongoDBTable, kTLSTable); static constexpr uint32_t kConnStatsTableNum = TableNum(kTables, kConnStatsTable); static constexpr uint32_t kHTTPTableNum = TableNum(kTables, kHTTPTable); @@ -111,6 +113,7 @@ class SocketTraceConnector : public BCCSourceConnector { static constexpr uint32_t kMuxTableNum = TableNum(kTables, kMuxTable); static constexpr uint32_t kAMQPTableNum = TableNum(kTables, kAMQPTable); static constexpr uint32_t kMongoDBTableNum = TableNum(kTables, kMongoDBTable); + static constexpr uint32_t kTLSTableNum = TableNum(kTables, kTLSTable); static constexpr auto kSamplingPeriod = std::chrono::milliseconds{200}; // TODO(yzhao): This is not used right now. Eventually use this to control data push frequency. diff --git a/src/stirling/source_connectors/socket_tracer/socket_trace_tables.h b/src/stirling/source_connectors/socket_tracer/socket_trace_tables.h index e37611213cd..20098738c31 100644 --- a/src/stirling/source_connectors/socket_tracer/socket_trace_tables.h +++ b/src/stirling/source_connectors/socket_tracer/socket_trace_tables.h @@ -32,3 +32,4 @@ #include "src/stirling/source_connectors/socket_tracer/nats_table.h" #include "src/stirling/source_connectors/socket_tracer/pgsql_table.h" #include "src/stirling/source_connectors/socket_tracer/redis_table.h" +#include "src/stirling/source_connectors/socket_tracer/tls_table.h" diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/ssl/nginx.conf b/src/stirling/source_connectors/socket_tracer/testing/containers/ssl/nginx.conf index 0adc0a91120..c4893433328 100644 --- a/src/stirling/source_connectors/socket_tracer/testing/containers/ssl/nginx.conf +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/ssl/nginx.conf @@ -11,7 +11,7 @@ http{ ssl_certificate /etc/ssl/server.crt; ssl_certificate_key /etc/ssl/server.key; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH; diff --git a/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.cc b/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.cc index 273e3dba17c..77fd1683254 100644 --- a/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.cc +++ b/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.cc @@ -19,6 +19,7 @@ #include "src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h" #include "src/stirling/source_connectors/socket_tracer/http_table.h" +#include "src/stirling/source_connectors/socket_tracer/tls_table.h" #include "src/stirling/testing/common.h" namespace px { @@ -28,6 +29,7 @@ namespace testing { namespace http = protocols::http; namespace mux = protocols::mux; namespace mongodb = protocols::mongodb; +namespace tls = protocols::tls; //----------------------------------------------------------------------------- // HTTP Checkers @@ -105,6 +107,20 @@ std::vector GetTargetRecords(const types::ColumnWrapperRecordBa return ToRecordVector(record_batch, target_record_indices); } +template <> +std::vector ToRecordVector(const types::ColumnWrapperRecordBatch& rb, + const std::vector& indices) { + std::vector result; + + for (const auto& idx : indices) { + auto version = rb[kTLSVersionIdx]->Get(idx); + tls::Record r; + r.req.legacy_version = static_cast(version.val); + result.push_back(r); + } + return result; +} + } // namespace testing } // namespace stirling } // namespace px diff --git a/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h b/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h index 207eb68e89b..98eeb32d624 100644 --- a/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h +++ b/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h @@ -32,6 +32,7 @@ #include "src/stirling/source_connectors/socket_tracer/protocols/http/types.h" #include "src/stirling/source_connectors/socket_tracer/protocols/mongodb/types.h" #include "src/stirling/source_connectors/socket_tracer/protocols/mux/types.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h" namespace px { namespace stirling { diff --git a/src/stirling/source_connectors/socket_tracer/tls_table.h b/src/stirling/source_connectors/socket_tracer/tls_table.h new file mode 100644 index 00000000000..865fee39944 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/tls_table.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "src/stirling/core/output.h" +#include "src/stirling/core/types.h" +#include "src/stirling/source_connectors/socket_tracer/canonical_types.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h" + +namespace px { +namespace stirling { + +// clang-format off +static constexpr DataElement kTLSElements[] = { + canonical_data_elements::kTime, + canonical_data_elements::kUPID, + canonical_data_elements::kRemoteAddr, + canonical_data_elements::kRemotePort, + canonical_data_elements::kLocalAddr, + canonical_data_elements::kLocalPort, + canonical_data_elements::kTraceRole, + {"req_type", "The type of request from the TLS record (Client/ServerHello, etc.)", + types::DataType::INT64, + types::SemanticType::ST_NONE, + types::PatternType::GENERAL_ENUM}, + {"version", "Version of TLS record", + types::DataType::INT64, + types::SemanticType::ST_NONE, + types::PatternType::GENERAL_ENUM}, + {"extensions", "Extensions in the TLS record", + types::DataType::STRING, + types::SemanticType::ST_NONE, + types::PatternType::GENERAL}, + canonical_data_elements::kLatencyNS, +#ifndef NDEBUG + canonical_data_elements::kPXInfo, +#endif +}; +// clang-format on + +static constexpr auto kTLSTable = + DataTableSchema("tls_events", "TLS request-response pair events", kTLSElements); +DEFINE_PRINT_TABLE(TLS) + +constexpr int kTLSUPIDIdx = kTLSTable.ColIndex("upid"); +constexpr int kTLSCmdIdx = kTLSTable.ColIndex("req_type"); +constexpr int kTLSVersionIdx = kTLSTable.ColIndex("version"); +constexpr int kTLSExtensionsIdx = kTLSTable.ColIndex("extensions"); + +} // namespace stirling +} // namespace px diff --git a/src/stirling/source_connectors/socket_tracer/tls_trace_bpf_test.cc b/src/stirling/source_connectors/socket_tracer/tls_trace_bpf_test.cc new file mode 100644 index 00000000000..2af23068fae --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/tls_trace_bpf_test.cc @@ -0,0 +1,166 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "src/common/base/base.h" +#include "src/common/exec/exec.h" +#include "src/common/testing/test_environment.h" +#include "src/shared/types/column_wrapper.h" +#include "src/shared/types/types.h" +#include "src/stirling/source_connectors/socket_tracer/socket_trace_connector.h" +#include "src/stirling/source_connectors/socket_tracer/testing/container_images/curl_container.h" +#include "src/stirling/source_connectors/socket_tracer/testing/container_images/nginx_openssl_3_0_8_container.h" +#include "src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h" +#include "src/stirling/source_connectors/socket_tracer/testing/socket_trace_bpf_test_fixture.h" +#include "src/stirling/testing/common.h" + +namespace px { +namespace stirling { + +namespace tls = protocols::tls; + +using ::px::stirling::testing::FindRecordIdxMatchesPID; +using ::px::stirling::testing::GetTargetRecords; +using ::px::stirling::testing::SocketTraceBPFTestFixture; +using ::px::stirling::testing::ToRecordVector; + +using ::testing::IsTrue; +using ::testing::SizeIs; +using ::testing::StrEq; +using ::testing::UnorderedElementsAre; + +struct TraceRecords { + std::vector tls_records; + std::vector tls_extensions; +}; + +class NginxOpenSSL_3_0_8_ContainerWrapper + : public ::px::stirling::testing::NginxOpenSSL_3_0_8_Container { + public: + int32_t PID() const { return NginxWorkerPID(); } +}; + +bool Init() { + // Make sure TLS tracing is enabled. + FLAGS_stirling_enable_tls_tracing = true; + + // We turn off CQL and NATS tracing to give some BPF instructions back for Mux. + // This is required for older kernels with only 4096 BPF instructions. + FLAGS_stirling_enable_cass_tracing = false; + FLAGS_stirling_enable_nats_tracing = false; + FLAGS_stirling_enable_amqp_tracing = false; + return true; +} + +//----------------------------------------------------------------------------- +// Test Scenarios +//----------------------------------------------------------------------------- + +tls::Record GetExpectedTLSRecord() { + tls::Record expected_record; + return expected_record; +} + +inline std::vector GetExtensions(const types::ColumnWrapperRecordBatch& rb, + const std::vector& indices) { + std::vector exts; + for (size_t idx : indices) { + exts.push_back(rb[kTLSExtensionsIdx]->Get(idx)); + } + return exts; +} + +class TLSVersionParameterizedTest + : public SocketTraceBPFTestFixture, + public ::testing::WithParamInterface { + protected: + TLSVersionParameterizedTest() { + Init(); + + // Run the nginx HTTPS server. + // The container runner will make sure it is in the ready state before unblocking. + // Stirling will run after this unblocks, as part of SocketTraceBPFTest SetUp(). + constexpr bool kHostPid = false; + StatusOr run_result = server_.Run(std::chrono::seconds{60}, {}, {}, kHostPid); + PX_CHECK_OK(run_result); + + // Sleep an additional second, just to be safe. + sleep(1); + } + + void TestTLSVersion(const std::string& tls_version, const std::string& tls_max_version) { + FLAGS_stirling_conn_trace_pid = this->server_.PID(); + + this->StartTransferDataThread(); + + // Make an SSL request with curl. + ::px::stirling::testing::CurlContainer client; + constexpr bool kHostPid = false; + ASSERT_OK( + client.Run(std::chrono::seconds{60}, + {absl::Substitute("--network=container:$0", this->server_.container_name())}, + {"--insecure", "-s", "-S", "--resolve", "test-host:443:127.0.0.1", + absl::Substitute("--tlsv$0", tls_version), "--tls-max", tls_max_version, + "https://test-host/index.html"}, + kHostPid)); + client.Wait(); + this->StopTransferDataThread(); + + TraceRecords records = this->GetTraceRecords(this->server_.PID()); + EXPECT_THAT(records.tls_records, SizeIs(1)); + EXPECT_THAT(records.tls_extensions, SizeIs(1)); + auto sni_str = R"({"server_name":"[\"test-host\"]"})"; + EXPECT_THAT(records.tls_extensions[0], StrEq(sni_str)); + } + + // Returns the trace records of the process specified by the input pid. + TraceRecords GetTraceRecords(int pid) { + std::vector tablets = + this->ConsumeRecords(SocketTraceConnector::kTLSTableNum); + if (tablets.empty()) { + return {}; + } + types::ColumnWrapperRecordBatch record_batch = tablets[0].records; + std::vector server_record_indices = + FindRecordIdxMatchesPID(record_batch, kTLSUPIDIdx, pid); + std::vector tls_records = + ToRecordVector(record_batch, server_record_indices); + std::vector extensions = GetExtensions(record_batch, server_record_indices); + + return {std::move(tls_records), std::move(extensions)}; + } + + NginxOpenSSL_3_0_8_ContainerWrapper server_; +}; + +INSTANTIATE_TEST_SUITE_P(TLSVersions, TLSVersionParameterizedTest, + // TODO(ddelnano): Testing versions earlier than 1.0 will require making + // the server test container support compatible cihpers. + ::testing::Values("1.2", "1.3")); + +TEST_P(TLSVersionParameterizedTest, TestTLSVersions) { + const std::string& tls_version = GetParam(); + TestTLSVersion(tls_version, tls_version); +} + +} // namespace stirling +} // namespace px