You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2020/05/20 15:29:15 UTC
[trafficserver] branch 9.0.x updated: traffic_dump: add tls
information to dump. (#6727)
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 4f3ac6b traffic_dump: add tls information to dump. (#6727)
4f3ac6b is described below
commit 4f3ac6b4504d9b6fcbef35d931145e9dba153e79
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Fri May 1 09:27:40 2020 -0500
traffic_dump: add tls information to dump. (#6727)
This change adds tls information nodes like the following:
"tls": {
"sni": "<SNI>",
"verify_mode": "<SSL_VERIFY_MODE_VALUE>"
},
Co-authored-by: bneradt <bn...@verizonmedia.com>
(cherry picked from commit bfafd91871111d4c766bfd30fa4146109777d1f2)
---
plugins/experimental/traffic_dump/traffic_dump.cc | 163 ++++++++++++++++++---
.../pluginTest/traffic_dump/traffic_dump.test.py | 8 +-
.../traffic_dump/traffic_dump_sni_filter.test.py | 10 +-
.../pluginTest/traffic_dump/verify_replay.py | 44 ++++++
4 files changed, 202 insertions(+), 23 deletions(-)
diff --git a/plugins/experimental/traffic_dump/traffic_dump.cc b/plugins/experimental/traffic_dump/traffic_dump.cc
index 938d640..f37bb7b 100644
--- a/plugins/experimental/traffic_dump/traffic_dump.cc
+++ b/plugins/experimental/traffic_dump/traffic_dump.cc
@@ -75,9 +75,9 @@ public:
};
/// Fields considered sensitive because they may contain user-private
-/// information. These fields are replaced with auto-generated generic content
-/// by default. To turn off this behavior, the user should add the
-/// --promiscuous-mode flag as a commandline argument.
+/// information. These fields are replaced with auto-generated generic content by
+/// default. To override this behavior, the user should specify their own fields
+/// they consider sensitive with --sensitive-fields.
///
/// While these are specified with case, they are matched case-insensitively.
std::unordered_set<std::string, StringHashByLower, InsensitiveCompare> default_sensitive_fields = {
@@ -568,6 +568,143 @@ session_txn_handler(TSCont contp, TSEvent event, void *edata)
return TS_SUCCESS;
}
+/** Create a TLS characteristics node.
+ *
+ * This function encapsulates the logic common between the client-side and
+ * server-side logic for populating the TLS node.
+ *
+ * @param[in] ssnp The pointer for this session.
+ *
+ * @return The node describing the TLS properties of this session.
+ */
+std::string
+get_tls_description_helper(TSVConn ssn_vc)
+{
+ TSSslConnection ssl_conn = TSVConnSslConnectionGet(ssn_vc);
+ SSL *ssl_obj = (SSL *)ssl_conn;
+ if (ssl_obj == nullptr) {
+ return "";
+ }
+ std::ostringstream tls_description;
+ tls_description << R"("tls":{)";
+ const char *sni_ptr = SSL_get_servername(ssl_obj, TLSEXT_NAMETYPE_host_name);
+ if (sni_ptr != nullptr) {
+ std::string_view sni{sni_ptr};
+ if (!sni.empty()) {
+ tls_description << R"("sni":")" << sni << R"(")";
+ }
+ }
+ tls_description << R"(,"verify_mode":")" << std::to_string(SSL_get_verify_mode(ssl_obj)) << R"(")";
+ tls_description << "}";
+ return tls_description.str();
+}
+
+/** Create a server-side TLS characteristics node.
+ *
+ * @param[in] ssnp The pointer for this session.
+ *
+ * @return The node describing the TLS properties of this session.
+ */
+std::string
+get_server_tls_description(TSHttpSsn ssnp)
+{
+ TSVConn ssn_vc = TSHttpSsnServerVConnGet(ssnp);
+ return get_tls_description_helper(ssn_vc);
+}
+
+/** Create a client-side TLS characteristics node.
+ *
+ * @param[in] ssnp The pointer for this session.
+ *
+ * @return The node describing the TLS properties of this session.
+ */
+std::string
+get_client_tls_description(TSHttpSsn ssnp)
+{
+ TSVConn ssn_vc = TSHttpSsnClientVConnGet(ssnp);
+ return get_tls_description_helper(ssn_vc);
+}
+
+/// A named boolean for callers who pass the is_client parameter.
+constexpr bool IS_CLIENT = true;
+
+/** Create the nodes that describe the session's sub-HTTP protocols.
+ *
+ * This function encapsulates the logic common between the client-side and
+ * server-side logic for describing the session's characteristics.
+ *
+ * This will create the string representing the "protocol" and "tls" nodes. The
+ * "tls" node will only be present if the connection is over SSL/TLS.
+ *
+ * @param[in] ssnp The pointer for this session.
+ *
+ * @return The description of the protocol stack and certain TLS attributes.
+ */
+std::string
+get_protocol_description_helper(TSHttpSsn ssnp, bool is_client)
+{
+ std::ostringstream protocol_description;
+ protocol_description << R"("protocol":[)";
+
+ const char *protocol[10];
+ int count = -1;
+ if (is_client) {
+ TSAssert(TS_SUCCESS == TSHttpSsnClientProtocolStackGet(ssnp, 10, protocol, &count));
+ } else {
+ // See the TODO below in the commented out defintion of get_server_protocol_description.
+ // TSAssert(TS_SUCCESS == TSHttpSsnServerProtocolStackGet(ssnp, 10, protocol, &count));
+ }
+ for (int i = 0; i < count; i++) {
+ if (i > 0) {
+ protocol_description << ",";
+ }
+ protocol_description << '"' << std::string(protocol[i]) << '"';
+ }
+
+ protocol_description << "]";
+ std::string tls_description;
+ if (is_client) {
+ tls_description = get_client_tls_description(ssnp);
+ } else {
+ tls_description = get_server_tls_description(ssnp);
+ }
+ if (!tls_description.empty()) {
+ protocol_description << "," << tls_description;
+ }
+ return protocol_description.str();
+}
+
+#if 0
+// TODO It will be important to add this eventually, but
+// TSHttpSsnServerProtocolStackGet is not defined yet. Once it (or some other
+// mechanism for getting the server side stack) is implemented, we will call
+// this as a part of writing the server-response node.
+
+/** Generate the nodes describing the server session.
+ *
+ * @param[in] ssnp The pointer for this session.
+ *
+ * @return The description of the protocol stack and certain TLS attributes.
+ */
+std::string
+get_server_protocol_description(TSHttpSsn ssnp)
+{
+ return get_protocol_description_helper(ssnp, !IS_CLIENT);
+}
+#endif
+
+/** Generate the nodes describing the client session.
+ *
+ * @param[in] ssnp The pointer for this session.
+ *
+ * @return The description of the protocol stack and certain TLS attributes.
+ */
+std::string
+get_client_protocol_description(TSHttpSsn ssnp)
+{
+ return get_protocol_description_helper(ssnp, IS_CLIENT);
+}
+
// Session handler for global hooks; Assign per-session data structure and log files
static int
global_ssn_handler(TSCont contp, TSEvent event, void *edata)
@@ -621,9 +758,9 @@ global_ssn_handler(TSCont contp, TSEvent event, void *edata)
TSDebug(PLUGIN_NAME, "global_ssn_handler(): Ignore HTTPS session with non-existent SNI.");
break;
} else {
- const std::string sni{sni_ptr};
+ const std::string_view sni{sni_ptr};
if (sni != sni_filter) {
- TSDebug(PLUGIN_NAME, "global_ssn_handler(): Ignore HTTPS session with non-filtered SNI: %s", sni.c_str());
+ TSDebug(PLUGIN_NAME, "global_ssn_handler(): Ignore HTTPS session with non-filtered SNI: %s", sni_ptr);
break;
}
}
@@ -647,19 +784,11 @@ global_ssn_handler(TSCont contp, TSEvent event, void *edata)
TSContDataSet(ssnData->aio_cont, ssnData);
- // 1. "protocol":(string)
- const char *protocol[10];
- int count = 0;
- TSAssert(TS_SUCCESS == TSHttpSsnClientProtocolStackGet(ssnp, 10, protocol, &count));
- std::string result;
- for (int i = 0; i < count; i++) {
- if (i > 0) {
- result += ",";
- }
- result += '"' + std::string(protocol[i]) + '"';
- }
+ // "protocol":(string),"tls":(string)
+ // The "tls" node will only be present if the session is over SSL/TLS.
+ std::string protocol_description = get_client_protocol_description(ssnp);
- std::string beginning = R"({"meta":{"version":"1.0"},"sessions":[{"protocol":[)" + result + "]" + R"(,"connection-time":)" +
+ std::string beginning = R"({"meta":{"version":"1.0"},"sessions":[{)" + protocol_description + R"(,"connection-time":)" +
std::to_string(start.count()) + R"(,"transactions":[)";
// Use the session count's hex string as the filename.
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
index 393c124..5cf9f24 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
@@ -103,13 +103,14 @@ tr = Test.AddTestRun("First transaction")
tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
tr.Processes.Default.StartBefore(Test.Processes.ts)
tr.Processes.Default.Command = \
- ('curl http://127.0.0.1:{0} -H"Cookie: donotlogthis" '
+ ('curl --http1.1 http://127.0.0.1:{0} -H"Cookie: donotlogthis" '
'-H"Host: www.example.com" -H"X-Request-1: ultra_sensitive" --verbose'.format(
ts.Variables.port))
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stderr = "gold/200.gold"
tr.StillRunningAfter = server
tr.StillRunningAfter = ts
+session_1_protocols = "tcp,ipv4"
# Execute the second transaction.
tr = Test.AddTestRun("Second transaction")
@@ -131,11 +132,12 @@ sensitive_fields_arg = (
"--sensitive-fields x-request-1 "
"--sensitive-fields x-request-2 ")
tr.Setup.CopyAs(verify_replay, Test.RunDirectory)
-tr.Processes.Default.Command = "python3 {0} {1} {2} {3}".format(
+tr.Processes.Default.Command = 'python3 {0} {1} {2} {3} --client-protocols "{4}"'.format(
verify_replay,
os.path.join(Test.Variables.AtsTestToolsDir, 'lib', 'replay_schema.json'),
replay_file_session_1,
- sensitive_fields_arg)
+ sensitive_fields_arg,
+ session_1_protocols)
tr.Processes.Default.ReturnCode = 0
tr.StillRunningAfter = server
tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py
index ff656d5..4e269f2 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py
@@ -119,12 +119,14 @@ tr.Setup.Copy("ssl/signed-foo.key")
tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
tr.Processes.Default.StartBefore(Test.Processes.ts)
tr.Processes.Default.Command = \
- ('curl --tls-max 1.2 -k -H"Host: bob" --resolve "bob:{0}:127.0.0.1" '
+ ('curl --http2 --tls-max 1.2 -k -H"Host: bob" --resolve "bob:{0}:127.0.0.1" '
'--cert ./signed-foo.pem --key ./signed-foo.key --verbose https://bob:{0}'.format(ts.Variables.ssl_port))
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stderr = "gold/200_sni_bob.gold"
tr.StillRunningAfter = server
tr.StillRunningAfter = ts
+session_1_protocols = "h2,tls/1.2,tcp,ipv4"
+session_1_tls_features = 'sni:bob'
# Execute the second transaction with an SNI of dave.
tr = Test.AddTestRun("Verify that a session of a different SNI is not dumped.")
@@ -150,10 +152,12 @@ tr.StillRunningAfter = ts
tr = Test.AddTestRun("Verify the json content of the first session")
verify_replay = "verify_replay.py"
tr.Setup.CopyAs(verify_replay, Test.RunDirectory)
-tr.Processes.Default.Command = "python3 {0} {1} {2}".format(
+tr.Processes.Default.Command = 'python3 {0} {1} {2} --client-protocols "{3}" --client-tls-features "{4}"'.format(
verify_replay,
os.path.join(Test.Variables.AtsTestToolsDir, 'lib', 'replay_schema.json'),
- replay_file_session_1)
+ replay_file_session_1,
+ session_1_protocols,
+ session_1_tls_features)
tr.Processes.Default.ReturnCode = 0
tr.StillRunningAfter = server
tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py b/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py
index 39d5987..b48ae4a 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py
@@ -139,6 +139,40 @@ def verify_sensitive_fields_not_dumped(replay_json, sensitive_fields):
return True
+def verify_client_protocols(replay_json, expected_protocol_features):
+ expected_protocols_list = expected_protocol_features.split(',')
+ expected_protocols_list.sort()
+ try:
+ protocol_node = replay_json['sessions'][0]['protocol']
+ protocol_list = protocol_node.copy()
+ protocol_list.sort()
+ if protocol_list == expected_protocols_list:
+ return True
+ else:
+ print('Unexpected protocol stack. Expected: "{}", found: "{}".'.format(
+ ','.join(expected_protocols_list), ','.join(protocol_list)))
+ return False
+ except KeyError:
+ print("Could not find client protocol stack node in the replay file.")
+ return False
+
+
+def verify_client_tls_features(replay_json, expected_tls_features):
+ try:
+ session = replay_json['sessions'][0]
+ for expected_tls_feature in expected_tls_features.split(','):
+ expected_key, expected_value = expected_tls_feature.split(':')
+ tls_features = session['tls']
+ try:
+ return tls_features[expected_key] == expected_value
+ except KeyError:
+ print("Could not find client tls feature in the replay file: {}".format(expected_key))
+ return False
+ except KeyError:
+ print("Could not find client tls node in the replay file.")
+ return False
+
+
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("schema_file",
@@ -155,6 +189,10 @@ def parse_args():
parser.add_argument("--sensitive-fields",
action="append",
help="The fields that are considered sensitive and replaced with insensitive values.")
+ parser.add_argument("--client-protocols",
+ help="The comma-separated protocol features to expect for the client connection.")
+ parser.add_argument("--client-tls-features",
+ help="The TLS values to expect for the client connection.")
return parser.parse_args()
@@ -188,6 +226,12 @@ def main():
if args.sensitive_fields and not verify_sensitive_fields_not_dumped(replay_json, args.sensitive_fields):
return 1
+ if args.client_protocols and not verify_client_protocols(replay_json, args.client_protocols):
+ return 1
+
+ if args.client_tls_features and not verify_client_tls_features(replay_json, args.client_tls_features):
+ return 1
+
return 0