trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From shinr...@apache.org
Subject [trafficserver] branch master updated: traffic_dump: don't dump cookies from the wire (#6586)
Date Fri, 10 Apr 2020 19:00:14 GMT
This is an automated email from the ASF dual-hosted git repository.

shinrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 4d7aeb0  traffic_dump: don't dump cookies from the wire (#6586)
4d7aeb0 is described below

commit 4d7aeb09f83e447b2d3112d23bb0a98ba32ca51d
Author: Brian Neradt <brian.neradt@gmail.com>
AuthorDate: Fri Apr 10 15:00:04 2020 -0400

    traffic_dump: don't dump cookies from the wire (#6586)
    
    Cookies are often very sensitive information and should not be logged.
    This change modifies traffic_dump to not dump the actual cookies from
    the wire.
    
    --promiscuous-mode is added in case the user wants to dump sensitive
    headers.
    
    --sensitive-fields is added so the user can treat custom fields as
    sensitive.
    
    Co-authored-by: bneradt <bneradt@verizonmedia.com>
---
 doc/admin-guide/plugins/traffic_dump.en.rst        |   6 +-
 plugins/experimental/traffic_dump/traffic_dump.cc  | 140 ++++++++++++++++++++-
 .../pluginTest/traffic_dump/gold/200.gold          |   1 +
 .../pluginTest/traffic_dump/traffic_dump.test.py   |  46 ++++---
 .../pluginTest/traffic_dump/verify_replay.py       |  55 ++++++++
 5 files changed, 226 insertions(+), 22 deletions(-)

diff --git a/doc/admin-guide/plugins/traffic_dump.en.rst b/doc/admin-guide/plugins/traffic_dump.en.rst
index 45c7d51..eece215 100644
--- a/doc/admin-guide/plugins/traffic_dump.en.rst
+++ b/doc/admin-guide/plugins/traffic_dump.en.rst
@@ -46,6 +46,10 @@ Plugin Configuration
 
    (`required`) - specifies the max disk usage N bytes (approximate). Traffic Dump will stop
capturing new sessions once disk usage exceeds this limit.
 
+   .. option:: --sensitive-fields <field1,field2,...,fieldn>
+
+   (`optional`) - a comma seperatated list of HTTP case-insensitive field names whose values
are considered sensitive information. Traffic Dump will not dump the incoming field values
for any of these fields but will instead dump a generic value for them of the same length
as the original. If this option is not used, a default list of "Cookie,Set-Cookie" is used.
Providing this option overwrites that default list with whatever values the user provides.
Pass a quoted empty string as the a [...]
+
 ``traffic_ctl`` <command>
    * ``traffic_ctl plugin msg traffic_dump.sample N`` - changes the sampling ratio N as mentioned
above.
    * ``traffic_ctl plugin msg traffic_dump.reset`` - resets the disk usage counter.
@@ -57,7 +61,7 @@ This format contains traffic data including:
 
 * Each session and transactions in the session.
 * Timestamps.
-* The four headers (ua request, proxy request, origin server response, proxy response).
+* The four sets of headers (user agent request, proxy request, origin server response, proxy
response).
 * The protocol stack for the user agent.
 * The transaction count for the outbound session.
 * The content block sizes.
diff --git a/plugins/experimental/traffic_dump/traffic_dump.cc b/plugins/experimental/traffic_dump/traffic_dump.cc
index 31563b6..cfe5b36 100644
--- a/plugins/experimental/traffic_dump/traffic_dump.cc
+++ b/plugins/experimental/traffic_dump/traffic_dump.cc
@@ -32,20 +32,59 @@
 #include <arpa/inet.h>
 #include <netinet/in.h>
 
+#include <algorithm>
 #include <sstream>
 #include <iomanip>
 #include <chrono>
 #include <atomic>
 #include <string>
 #include <string_view>
+#include <unordered_set>
 
 #include "tscore/ts_file.h"
+#include "tscpp/util/TextView.h"
 #include "ts/ts.h"
 
 namespace
 {
 const char *PLUGIN_NAME   = "traffic_dump";
 const std::string closing = "]}]}";
+std::string defaut_sensitive_field_value;
+
+// A case-insensitive comparitor used for comparing HTTP field names.
+struct InsensitiveCompare {
+  bool
+  operator()(std::string_view a, std::string_view b) const
+  {
+    return strcasecmp(a, b) == 0;
+  }
+};
+
+struct StringHashByLower {
+public:
+  size_t
+  operator()(const std::string &str) const
+  {
+    std::string lower;
+    std::transform(str.begin(), str.end(), lower.begin(), [](unsigned char c) -> unsigned
char { return std::tolower(c); });
+    return std::hash<std::string>()(lower);
+  }
+};
+
+/// 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.
+///
+/// While these are specified with case, they are matched case-insensitively.
+std::unordered_set<std::string, StringHashByLower, InsensitiveCompare> default_sensitive_fields
= {
+  "Set-Cookie",
+  "Cookie",
+};
+
+/// The set of fields, default and user-specified, that are sensitive and whose
+/// values will be replaced with auto-generated generic content.
+std::unordered_set<std::string, StringHashByLower, InsensitiveCompare> sensitive_fields;
 
 ts::file::path log_path{"dump"};               // default log directory
 int s_arg_idx = 0;                             // Session Arg Index to pass on session data
@@ -183,10 +222,10 @@ esc_json_out(const char *buf, int64_t len, std::ostream &jsonfile)
 
 /// escape_json(): escape chars in a string and returns json string
 std::string
-escape_json(std::string const &s)
+escape_json(std::string_view s)
 {
   std::ostringstream o;
-  esc_json_out(s.c_str(), s.length(), o);
+  esc_json_out(s.data(), s.length(), o);
   return o.str();
 }
 std::string
@@ -205,9 +244,9 @@ json_entry(std::string const &name, const char *value, int64_t size)
 
 /// json_entry_array(): Formats to array-style entry i.e. ["field","value"]
 inline std::string
-json_entry_array(const char *name, int name_len, const char *value, int value_len)
+json_entry_array(std::string_view name, std::string_view value)
 {
-  return "[\"" + escape_json(name, name_len) + "\", \"" + escape_json(value, value_len) +
"\"]";
+  return "[\"" + escape_json(name) + "\", \"" + escape_json(value) + "\"]";
 }
 
 /** Remove the scheme prefix from the url.
@@ -236,6 +275,49 @@ write_content_node(int64_t num_body_bytes)
   return std::string(R"(,"content":{"encoding":"plain","size":)" + std::to_string(num_body_bytes)
+ '}');
 }
 
+/** Initialize the generic sensitive field to be dumped. This is used instead
+ * of the sensitive field values seen on the wire.
+ */
+void
+initialize_default_sensitive_field()
+{
+  // 128 KB is the maximum size supported for all headers, so this size should
+  // be plenty large for our needs.
+  constexpr size_t default_field_size = 128 * 1024;
+  defaut_sensitive_field_value.resize(default_field_size);
+
+  char *field_buffer = defaut_sensitive_field_value.data();
+  for (auto i = 0u; i < default_field_size; i += 8) {
+    sprintf(field_buffer, "%07x ", i / 8);
+    field_buffer += 8;
+  }
+}
+
+/** Inspect the field to see whether it is sensitive and return a generic value
+ * of equal size to the original if it is.
+ *
+ * @param[in] name The field name to inspect.
+ * @param[in] original_value The field value to inspect.
+ *
+ * @return The value traffic_dump should dump for the given field.
+ */
+std::string_view
+replace_sensitive_fields(std::string_view name, std::string_view original_value)
+{
+  auto search = sensitive_fields.find(std::string(name));
+  if (search == sensitive_fields.end()) {
+    return original_value;
+  }
+  auto new_value_size = original_value.size();
+  if (original_value.size() > defaut_sensitive_field_value.size()) {
+    new_value_size = defaut_sensitive_field_value.size();
+    TSError("[%s] Encountered a sensitive field value larger than our default "
+            "field size. Default size: %zu, incoming field size: %zu",
+            PLUGIN_NAME, defaut_sensitive_field_value.size(), original_value.size());
+  }
+  return std::string_view{defaut_sensitive_field_value.data(), new_value_size};
+}
+
 /// Read the txn information from TSMBuffer and write the header information.
 /// This function does not write the content node.
 std::string
@@ -302,8 +384,11 @@ write_message_node_no_content(TSMBuffer &buffer, TSMLoc &hdr_loc)
     int name_len = 0, value_len = 0;
     // Append to "fields" list if valid value exists
     if ((name = TSMimeHdrFieldNameGet(buffer, hdr_loc, field_loc, &name_len)) &&
name_len) {
+      std::string_view name_view{name, static_cast<size_t>(name_len)};
       value = TSMimeHdrFieldValueStringGet(buffer, hdr_loc, field_loc, -1, &value_len);
-      result += json_entry_array(name, name_len, value, value_len);
+      std::string_view value_view{value, static_cast<size_t>(value_len)};
+      std::string_view new_value = replace_sensitive_fields(name_view, value_view);
+      result += json_entry_array(name_view, new_value);
     }
 
     next_field_loc = TSMimeHdrFieldNext(buffer, hdr_loc, field_loc);
@@ -391,6 +476,7 @@ session_txn_handler(TSCont contp, TSEvent event, void *edata)
     // Get UUID
     char uuid[TS_CRUUID_STRING_LEN + 1];
     TSAssert(TS_SUCCESS == TSClientRequestUuidGet(txnp, uuid));
+    std::string_view uuid_view{uuid, strnlen(uuid, TS_CRUUID_STRING_LEN)};
 
     // Generate per transaction json records
     if (!ssnData->first) {
@@ -408,7 +494,7 @@ session_txn_handler(TSCont contp, TSEvent event, void *edata)
     // The uuid is a header field for each message in the transaction. Use the
     // "all" node to apply to each message.
     std::string_view name = "uuid";
-    txn_info += ",\"all\":{\"headers\":{\"fields\":[" + json_entry_array(name.data(), name.size(),
uuid, strlen(uuid));
+    txn_info += ",\"all\":{\"headers\":{\"fields\":[" + json_entry_array(name, uuid_view);
     txn_info += "]}}";
     ssnData->write_to_disk(txn_info);
     break;
@@ -633,15 +719,38 @@ TSPluginInit(int argc, const char *argv[])
   info.vendor_name   = "Apache Software Foundation";
   info.support_email = "dev@trafficserver.apache.org";
 
+  bool sensitive_fields_were_specified = false;
   /// Commandline options
   static const struct option longopts[] = {{"logdir", required_argument, nullptr, 'l'},
                                            {"sample", required_argument, nullptr, 's'},
                                            {"limit", required_argument, nullptr, 'm'},
+                                           {"sensitive-fields", required_argument, nullptr,
'f'},
                                            {nullptr, no_argument, nullptr, 0}};
   int opt                               = 0;
   while (opt >= 0) {
     opt = getopt_long(argc, const_cast<char *const *>(argv), "l:", longopts, nullptr);
     switch (opt) {
+    case 'f': {
+      // --sensitive-fields takes a comma-separated list of HTTP fields that
+      // are sensitive.  The field values for these fields will be replaced
+      // with generic traffic_dump generated data.
+      //
+      // If this option is not used, then the default values in
+      // default_sensitive_fields is used. If this option is used, then it
+      // replaced the default sensitive fields with the user-supplied list of
+      // sensitive fields.
+      sensitive_fields_were_specified = true;
+      ts::TextView input_filter_fields{std::string_view{optarg}};
+      ts::TextView filter_field;
+      while (!(filter_field = input_filter_fields.take_prefix_at(',')).empty()) {
+        filter_field.trim_if(&isspace);
+        if (filter_field.empty()) {
+          continue;
+        }
+        sensitive_fields.emplace(filter_field);
+      }
+      break;
+    }
     case 'l': {
       log_path = ts::file::path{optarg};
       break;
@@ -664,6 +773,23 @@ TSPluginInit(int argc, const char *argv[])
     }
   }
 
+  if (!sensitive_fields_were_specified) {
+    // The user did not provide their own list of sensitive fields. Use the
+    // default.
+    sensitive_fields.merge(default_sensitive_fields);
+  }
+
+  std::string sensitive_fields_string;
+  bool is_first = true;
+  for (const auto &field : sensitive_fields) {
+    if (!is_first) {
+      sensitive_fields_string += ", ";
+    }
+    is_first = false;
+    sensitive_fields_string += field;
+  }
+  TSDebug(PLUGIN_NAME, "Sensitive fields for which generic values will be dumped: %s", sensitive_fields_string.c_str());
+
   // Make absolute path if not
   if (!log_path.is_absolute()) {
     log_path = ts::file::path(TSInstallDirGet()) / log_path;
@@ -675,6 +801,8 @@ TSPluginInit(int argc, const char *argv[])
   } else if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_SSN, PLUGIN_NAME, "Track log
related data", &s_arg_idx)) {
     TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve ssn arg.", PLUGIN_NAME);
   } else {
+    initialize_default_sensitive_field();
+
     /// Add global hooks
     TSCont ssncont = TSContCreate(global_ssn_handler, nullptr);
     TSHttpHookAdd(TS_HTTP_SSN_START_HOOK, ssncont);
diff --git a/tests/gold_tests/pluginTest/traffic_dump/gold/200.gold b/tests/gold_tests/pluginTest/traffic_dump/gold/200.gold
index 71aa5d5..0ac91ba 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/gold/200.gold
+++ b/tests/gold_tests/pluginTest/traffic_dump/gold/200.gold
@@ -6,6 +6,7 @@
 ``
 < HTTP/1.1 200 OK
 < Content-Length: 0
+< Set-Cookie: classified_not_for_logging
 < Date: ``
 < Age: ``
 < Server: ATS/``
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 afb75db..393c124 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
@@ -33,14 +33,16 @@ request_header = {"headers": "GET / HTTP/1.1\r\n"
                   "Host: www.example.com\r\nContent-Length: 0\r\n\r\n",
                   "timestamp": "1469733493.993", "body": ""}
 response_header = {"headers": "HTTP/1.1 200 OK"
-                   "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
+                   "\r\nConnection: close\r\nContent-Length: 0"
+                   "\r\nSet-Cookie: classified_not_for_logging\r\n\r\n",
                    "timestamp": "1469733493.993", "body": ""}
 server.addResponse("sessionfile.log", request_header, response_header)
 request_header = {"headers": "GET /one HTTP/1.1\r\n"
                   "Host: www.example.com\r\nContent-Length: 0\r\n\r\n",
                   "timestamp": "1469733493.993", "body": ""}
 response_header = {"headers": "HTTP/1.1 200 OK"
-                   "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
+                   "\r\nConnection: close\r\nContent-Length: 0"
+                   "\r\nSet-Cookie: classified_not_for_logging\r\n\r\n",
                    "timestamp": "1469733493.993", "body": ""}
 server.addResponse("sessionfile.log", request_header, response_header)
 request_header = {"headers": "GET /post_with_body HTTP/1.1\r\n"
@@ -63,7 +65,8 @@ ts.Disk.remap_config.AddLine(
 )
 # Configure traffic_dump.
 ts.Disk.plugin_config.AddLine(
-    'traffic_dump.so --logdir {0} --sample 1 --limit 1000000000'.format(replay_dir)
+    'traffic_dump.so --logdir {0} --sample 1 --limit 1000000000 '
+    '--sensitive-fields "cookie,set-cookie,x-request-1,x-request-2"'.format(replay_dir)
 )
 
 # Set up trafficserver expectations.
@@ -99,8 +102,10 @@ 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\'Host: www.example.com\' --verbose'.format(
-    ts.Variables.port)
+tr.Processes.Default.Command = \
+        ('curl 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
@@ -108,8 +113,10 @@ tr.StillRunningAfter = ts
 
 # Execute the second transaction.
 tr = Test.AddTestRun("Second transaction")
-tr.Processes.Default.Command = 'curl http://127.0.0.1:{0}/one -H\'Host: www.example.com\'
--verbose'.format(
-    ts.Variables.port)
+tr.Processes.Default.Command = \
+        ('curl http://127.0.0.1:{0}/one -H"Host: www.example.com" '
+         '-H"X-Request-2: also_very_sensitive" --verbose'.format(
+            ts.Variables.port))
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stderr = "gold/200.gold"
 tr.StillRunningAfter = server
@@ -118,11 +125,17 @@ tr.StillRunningAfter = ts
 # Verify the properties of the replay file for the first transaction.
 tr = Test.AddTestRun("Verify the json content of the first session")
 verify_replay = "verify_replay.py"
+sensitive_fields_arg = (
+        "--sensitive-fields cookie "
+        "--sensitive-fields set-cookie "
+        "--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}".format(
+tr.Processes.Default.Command = "python3 {0} {1} {2} {3}".format(
         verify_replay,
         os.path.join(Test.Variables.AtsTestToolsDir, 'lib', 'replay_schema.json'),
-        replay_file_session_1)
+        replay_file_session_1,
+        sensitive_fields_arg)
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = server
 tr.StillRunningAfter = ts
@@ -130,10 +143,11 @@ tr.StillRunningAfter = ts
 # Verify the properties of the replay file for the second transaction.
 tr = Test.AddTestRun("Verify the json content of the second session")
 tr.Setup.CopyAs(verify_replay, Test.RunDirectory)
-tr.Processes.Default.Command = "python3 {0} {1} {2} --request-target '/one'".format(
+tr.Processes.Default.Command = "python3 {0} {1} {2} {3} --request-target '/one'".format(
         verify_replay,
         os.path.join(Test.Variables.AtsTestToolsDir, 'lib', 'replay_schema.json'),
-        replay_file_session_2)
+        replay_file_session_2,
+        sensitive_fields_arg)
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = server
 tr.StillRunningAfter = ts
@@ -147,7 +161,7 @@ tr = Test.AddTestRun("Make a request with an explicit target.")
 request_target = "http://localhost:{0}/candy".format(ts.Variables.port)
 tr.Processes.Default.Command = (
         'curl --request-target "{0}" '
-        'http://127.0.0.1:{1} -H\'Host: www.example.com\' --verbose'.format(
+        'http://127.0.0.1:{1} -H"Host: www.example.com" --verbose'.format(
             request_target, ts.Variables.port))
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stderr = "gold/explicit_target.gold"
@@ -157,10 +171,11 @@ tr.StillRunningAfter = ts
 tr = Test.AddTestRun("Verify the replay file has the explicit target.")
 tr.Setup.CopyAs(verify_replay, Test.RunDirectory)
 
-tr.Processes.Default.Command = "python3 {0} {1} {2} --request-target '{3}'".format(
+tr.Processes.Default.Command = "python3 {0} {1} {2} {3} --request-target '{4}'".format(
         verify_replay,
         os.path.join(Test.Variables.AtsTestToolsDir, 'lib', 'replay_schema.json'),
         replay_file_session_3,
+        sensitive_fields_arg,
         request_target)
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = server
@@ -178,7 +193,7 @@ request_target = "http://localhost:{0}/post_with_body".format(ts.Variables.port)
 # in the test run directory.
 tr.Processes.Default.Command = (
         'curl --data-binary @{0} --request-target "{1}" '
-        'http://127.0.0.1:{2} -H\'Host: www.example.com\' --verbose'.format(
+        'http://127.0.0.1:{2} -H"Host: www.example.com" --verbose'.format(
             verify_replay, request_target, ts.Variables.port))
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stderr = "gold/post_with_body.gold"
@@ -190,10 +205,11 @@ tr.Setup.CopyAs(verify_replay, Test.RunDirectory)
 
 size_of_verify_replay_file = os.path.getsize(os.path.join(Test.TestDirectory, verify_replay))
 tr.Processes.Default.Command = \
-        "python3 {0} {1} {2} --client-request-size {3}".format(
+        "python3 {0} {1} {2} {3} --client-request-size {4}".format(
             verify_replay,
             os.path.join(Test.Variables.AtsTestToolsDir, 'lib', 'replay_schema.json'),
             replay_file_session_4,
+            sensitive_fields_arg,
             size_of_verify_replay_file)
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = server
diff --git a/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py b/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py
index 532c92b..39d5987 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/verify_replay.py
@@ -23,6 +23,28 @@ import jsonschema
 import sys
 
 
+expected_sensitive_value = \
+    '''0000000 0000001 0000002 0000003 0000004 0000005 0000006 0000007 0000008 0000009 000000a
000000b 000000c 000000d ''' \
+    '''000000e 000000f 0000010 0000011 0000012 0000013 0000014 0000015 0000016 0000017 0000018
0000019 000001a 000001b ''' \
+    '''000001c 000001d 000001e 000001f 0000020 0000021 0000022 0000023 0000024 0000025 0000026
0000027 0000028 0000029 ''' \
+    '''000002a 000002b 000002c 000002d 000002e 000002f 0000030 0000031 0000032 0000033 0000034
0000035 0000036 0000037 ''' \
+    '''0000038 0000039 000003a 000003b 000003c 000003d 000003e 000003f 0000040 0000041 0000042
0000043 0000044 0000045 ''' \
+    '''0000046 0000047 0000048 0000049 000004a 000004b 000004c 000004d 000004e 000004f 0000050
0000051 0000052 0000053 ''' \
+    '''0000054 0000055 0000056 0000057 0000058 0000059 000005a 000005b 000005c 000005d 000005e
000005f 0000060 0000061 ''' \
+    '''0000062 0000063 0000064 0000065 0000066 0000067 0000068 0000069 000006a 000006b 000006c
000006d 000006e 000006f ''' \
+    '''0000070 0000071 0000072 0000073 0000074 0000075 0000076 0000077 0000078 0000079 000007a
000007b 000007c 000007d ''' \
+    '''000007e 000007f 0000080 0000081 0000082 0000083 0000084 0000085 0000086 0000087 0000088
0000089 000008a 000008b ''' \
+    '''000008c 000008d 000008e 000008f 0000090 0000091 0000092 0000093 0000094 0000095 0000096
0000097 0000098 0000099 ''' \
+    '''000009a 000009b 000009c 000009d 000009e 000009f 00000a0 00000a1 00000a2 00000a3 00000a4
00000a5 00000a6 00000a7 ''' \
+    '''00000a8 00000a9 00000aa 00000ab 00000ac 00000ad 00000ae 00000af 00000b0 00000b1 00000b2
00000b3 00000b4 00000b5 ''' \
+    '''00000b6 00000b7 00000b8 00000b9 00000ba 00000bb 00000bc 00000bd 00000be 00000bf 00000c0
00000c1 00000c2 00000c3 ''' \
+    '''00000c4 00000c5 00000c6 00000c7 00000c8 00000c9 00000ca 00000cb 00000cc 00000cd 00000ce
00000cf 00000d0 00000d1 ''' \
+    '''00000d2 00000d3 00000d4 00000d5 00000d6 00000d7 00000d8 00000d9 00000da 00000db 00000dc
00000dd 00000de 00000df ''' \
+    '''00000e0 00000e1 00000e2 00000e3 00000e4 00000e5 00000e6 00000e7 00000e8 00000e9 00000ea
00000eb 00000ec 00000ed ''' \
+    '''00000ee 00000ef 00000f0 00000f1 00000f2 00000f3 00000f4 00000f5 00000f6 00000f7 00000f8
00000f9 00000fa 00000fb ''' \
+    '''00000fc 00000fd 00000fe 00000ff'''
+
+
 def validate_json(schema_json, replay_json):
     """
     Validate the replay file against the provided schema.
@@ -90,6 +112,33 @@ def verify_client_request_size(replay_json, client_request_size):
     return True
 
 
+def verify_sensitive_fields_not_dumped(replay_json, sensitive_fields):
+    """
+    Verify that all of the cookie fields have the expected value.
+    """
+    message_types = ['client-request', 'proxy-request', 'server-response', 'proxy-response']
+    try:
+        for session in replay_json['sessions']:
+            for transaction in session['transactions']:
+                for message_type in transaction:
+                    if message_type not in message_types:
+                        continue
+                    message = transaction[message_type]
+                    for field in message['headers']['fields']:
+                        field_name = field[0].lower()
+                        if field_name in sensitive_fields:
+                            field_value = field[1]
+                            if field_value not in expected_sensitive_value:
+                                print("Found an unexpected cookie: {}: {}".format(field[0],
field[1]))
+                                return False
+
+    except KeyError:
+        print("Could not find headers in the replay file.")
+        return False
+
+    return True
+
+
 def parse_args():
     parser = argparse.ArgumentParser()
     parser.add_argument("schema_file",
@@ -103,6 +152,9 @@ def parse_args():
     parser.add_argument("--client-request-size",
                         type=int,
                         help="The expected size value in the client-request node.")
+    parser.add_argument("--sensitive-fields",
+                        action="append",
+                        help="The fields that are considered sensitive and replaced with
insensitive values.")
     return parser.parse_args()
 
 
@@ -133,6 +185,9 @@ def main():
     if args.client_request_size and not verify_client_request_size(replay_json, args.client_request_size):
         return 1
 
+    if args.sensitive_fields and not verify_sensitive_fields_not_dumped(replay_json, args.sensitive_fields):
+        return 1
+
     return 0
 
 


Mime
View raw message