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