diff --git a/Source/common/santa.proto b/Source/common/santa.proto index e09c6df72..fc7da670c 100644 --- a/Source/common/santa.proto +++ b/Source/common/santa.proto @@ -214,8 +214,8 @@ message CertificateInfo { } message Entitlement { - string key = 1; - string value = 2; + string key = 1; + string value = 2; } // Information about a process execution event diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm b/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm index 0d0808c8b..1db360d7c 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm +++ b/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm @@ -72,6 +72,7 @@ namespace santa::santad::logs::endpoint_security::serializers { static constexpr NSUInteger kMaxEncodeObjectEntries = 64; +static constexpr NSUInteger kMaxEncodeObjectLevels = 5; std::shared_ptr Protobuf::Create(std::shared_ptr esapi, SNTDecisionCache *decision_cache, bool json) { @@ -451,11 +452,49 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info, return FinalizeProto(santa_msg); } +id StandardizedNestedObjects(id obj, int level) { + if (level-- == 0) { + return [obj description]; + } + + if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:[NSString class]]) { + return obj; + } else if ([obj isKindOfClass:[NSArray class]]) { + NSMutableArray *arr = [NSMutableArray array]; + for (id item in obj) { + [arr addObject:StandardizedNestedObjects(item, level)]; + } + return arr; + } else if ([obj isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + for (id key in obj) { + [dict setObject:StandardizedNestedObjects(obj[key], level) forKey:key]; + } + return dict; + } else if ([obj isKindOfClass:[NSData class]]) { + return [obj base64EncodedStringWithOptions:0]; + } else if ([obj isKindOfClass:[NSDate class]]) { + return [NSISO8601DateFormatter stringFromDate:obj + timeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"] + formatOptions:NSISO8601DateFormatWithFractionalSeconds | NSISO8601DateFormatWithInternetDateTime]; + + } else { + NSLog(@"Got unknown... %d", level); + LOGW(@"Unexpected object encountered: %@", obj); + return [obj description]; + } +} + void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements) { if (!entitlements) { return; } + // Since nested objects with varying types is hard for the API to serialize to + // JSON, first go through and standardize types to ensure better serialization + // as well as a consitent view of data. + entitlements = StandardizedNestedObjects(entitlements, kMaxEncodeObjectLevels); + __block int numObjectsToEncode = (int)std::min(kMaxEncodeObjectEntries, entitlements.count); pb_exec->mutable_entitlements()->Reserve(numObjectsToEncode); @@ -474,19 +513,7 @@ void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements) NSError *err; NSData *jsonData; @try { - id val = obj; - - // Fixup some types with data that can be better represented in JSON - if ([obj isKindOfClass:[NSDate class]]) { - // Example format output: "November 6, 2023 at 10:25:20 AM EST" - val = [NSDateFormatter localizedStringFromDate:obj - dateStyle:NSDateFormatterLongStyle - timeStyle:NSDateFormatterLongStyle]; - } else if ([obj isKindOfClass:[NSData class]]) { - val = [obj base64EncodedStringWithOptions:0]; - } - - jsonData = [NSJSONSerialization dataWithJSONObject:val + jsonData = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingFragmentsAllowed error:&err]; } @catch (NSException *e) { diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm b/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm index afd9d78dd..d98925d11 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm +++ b/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm @@ -276,7 +276,10 @@ void SerializeAndCheck(es_event_type_t eventType, options:NSJSONReadingMutableContainers error:&jsonError]; XCTAssertNil(jsonError, @"failed to parse got data as JSON"); + XCTAssertNil(FindDelta(wantJSONDict, gotJSONDict)); + // Note: Uncomment this line to help create testfile JSON when the assert above fails + // XCTAssertEqualObjects([NSString stringWithUTF8String:gotData.c_str()], wantData); } XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); diff --git a/Source/santad/testdata/protobuf/v1/exec.json b/Source/santad/testdata/protobuf/v1/exec.json index bec33b2f9..72f1760cb 100644 --- a/Source/santad/testdata/protobuf/v1/exec.json +++ b/Source/santad/testdata/protobuf/v1/exec.json @@ -123,16 +123,16 @@ "quarantine_url": "google.com", "entitlements": [ { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" }, { "key": "key_with_data_val", "value": "\"SGVsbG8gV29ybGQ=\"" }, { - "key": "key_with_arr_val_multitype", - "value": "\"(\\n v1,\\n v2,\\n v3,\\n 123,\\n \\\"2023-11-07 17:00:02 +0000\\\"\\n)\"" + "key": "key_with_str_val", + "value": "\"bar\"" }, { "key": "key_with_arr_val_nested", @@ -140,23 +140,23 @@ }, { "key": "key_with_dict_val", - "value": "{\"k1\":\"v1\",\"k2\":\"v2\"}" + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_date_val", - "value": "\"November 7, 2023 at 5:00:02 PM GMT\"" + "value": "\"2023-11-07T17:00:02.000Z\"" }, { "key": "key_with_dict_val_nested", - "value": "\"{\\n k1 = v1;\\n k2 = v2;\\n k3 = {\\n nk1 = nv1;\\n nk2 = \\\"2023-11-07 17:00:02 +0000\\\";\\n };\\n}\"" + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_num_val", "value": "1234" }, { - "key": "key_with_str_val", - "value": "\"bar\"" + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" } ] } diff --git a/Source/santad/testdata/protobuf/v2/exec.json b/Source/santad/testdata/protobuf/v2/exec.json index 4340a488e..95f2d81ac 100644 --- a/Source/santad/testdata/protobuf/v2/exec.json +++ b/Source/santad/testdata/protobuf/v2/exec.json @@ -151,16 +151,16 @@ "quarantine_url": "google.com", "entitlements": [ { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" }, { "key": "key_with_data_val", "value": "\"SGVsbG8gV29ybGQ=\"" }, { - "key": "key_with_arr_val_multitype", - "value": "\"(\\n v1,\\n v2,\\n v3,\\n 123,\\n \\\"2023-11-07 17:00:02 +0000\\\"\\n)\"" + "key": "key_with_str_val", + "value": "\"bar\"" }, { "key": "key_with_arr_val_nested", @@ -168,23 +168,23 @@ }, { "key": "key_with_dict_val", - "value": "{\"k1\":\"v1\",\"k2\":\"v2\"}" + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_date_val", - "value": "\"November 7, 2023 at 5:00:02 PM GMT\"" + "value": "\"2023-11-07T17:00:02.000Z\"" }, { "key": "key_with_dict_val_nested", - "value": "\"{\\n k1 = v1;\\n k2 = v2;\\n k3 = {\\n nk1 = nv1;\\n nk2 = \\\"2023-11-07 17:00:02 +0000\\\";\\n };\\n}\"" + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_num_val", "value": "1234" }, { - "key": "key_with_str_val", - "value": "\"bar\"" + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" } ] } diff --git a/Source/santad/testdata/protobuf/v4/exec.json b/Source/santad/testdata/protobuf/v4/exec.json index c55d46e86..4781ef27b 100644 --- a/Source/santad/testdata/protobuf/v4/exec.json +++ b/Source/santad/testdata/protobuf/v4/exec.json @@ -200,16 +200,16 @@ "quarantine_url": "google.com", "entitlements": [ { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" }, { "key": "key_with_data_val", "value": "\"SGVsbG8gV29ybGQ=\"" }, { - "key": "key_with_arr_val_multitype", - "value": "\"(\\n v1,\\n v2,\\n v3,\\n 123,\\n \\\"2023-11-07 17:00:02 +0000\\\"\\n)\"" + "key": "key_with_str_val", + "value": "\"bar\"" }, { "key": "key_with_arr_val_nested", @@ -217,23 +217,23 @@ }, { "key": "key_with_dict_val", - "value": "{\"k1\":\"v1\",\"k2\":\"v2\"}" + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_date_val", - "value": "\"November 7, 2023 at 5:00:02 PM GMT\"" + "value": "\"2023-11-07T17:00:02.000Z\"" }, { "key": "key_with_dict_val_nested", - "value": "\"{\\n k1 = v1;\\n k2 = v2;\\n k3 = {\\n nk1 = nv1;\\n nk2 = \\\"2023-11-07 17:00:02 +0000\\\";\\n };\\n}\"" + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_num_val", "value": "1234" }, { - "key": "key_with_str_val", - "value": "\"bar\"" + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" } ] } diff --git a/Source/santad/testdata/protobuf/v5/exec.json b/Source/santad/testdata/protobuf/v5/exec.json index b55e137ea..e6b6ba94f 100644 --- a/Source/santad/testdata/protobuf/v5/exec.json +++ b/Source/santad/testdata/protobuf/v5/exec.json @@ -200,16 +200,16 @@ "quarantine_url": "google.com", "entitlements": [ { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" }, { "key": "key_with_data_val", "value": "\"SGVsbG8gV29ybGQ=\"" }, { - "key": "key_with_arr_val_multitype", - "value": "\"(\\n v1,\\n v2,\\n v3,\\n 123,\\n \\\"2023-11-07 17:00:02 +0000\\\"\\n)\"" + "key": "key_with_str_val", + "value": "\"bar\"" }, { "key": "key_with_arr_val_nested", @@ -217,23 +217,23 @@ }, { "key": "key_with_dict_val", - "value": "{\"k1\":\"v1\",\"k2\":\"v2\"}" + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_date_val", - "value": "\"November 7, 2023 at 5:00:02 PM GMT\"" + "value": "\"2023-11-07T17:00:02.000Z\"" }, { "key": "key_with_dict_val_nested", - "value": "\"{\\n k1 = v1;\\n k2 = v2;\\n k3 = {\\n nk1 = nv1;\\n nk2 = \\\"2023-11-07 17:00:02 +0000\\\";\\n };\\n}\"" + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_num_val", "value": "1234" }, { - "key": "key_with_str_val", - "value": "\"bar\"" + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" } ] } diff --git a/Source/santad/testdata/protobuf/v6/exec.json b/Source/santad/testdata/protobuf/v6/exec.json index c55d46e86..4781ef27b 100644 --- a/Source/santad/testdata/protobuf/v6/exec.json +++ b/Source/santad/testdata/protobuf/v6/exec.json @@ -200,16 +200,16 @@ "quarantine_url": "google.com", "entitlements": [ { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" }, { "key": "key_with_data_val", "value": "\"SGVsbG8gV29ybGQ=\"" }, { - "key": "key_with_arr_val_multitype", - "value": "\"(\\n v1,\\n v2,\\n v3,\\n 123,\\n \\\"2023-11-07 17:00:02 +0000\\\"\\n)\"" + "key": "key_with_str_val", + "value": "\"bar\"" }, { "key": "key_with_arr_val_nested", @@ -217,23 +217,23 @@ }, { "key": "key_with_dict_val", - "value": "{\"k1\":\"v1\",\"k2\":\"v2\"}" + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_date_val", - "value": "\"November 7, 2023 at 5:00:02 PM GMT\"" + "value": "\"2023-11-07T17:00:02.000Z\"" }, { "key": "key_with_dict_val_nested", - "value": "\"{\\n k1 = v1;\\n k2 = v2;\\n k3 = {\\n nk1 = nv1;\\n nk2 = \\\"2023-11-07 17:00:02 +0000\\\";\\n };\\n}\"" + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" }, { "key": "key_with_num_val", "value": "1234" }, { - "key": "key_with_str_val", - "value": "\"bar\"" + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" } ] }