diff --git a/tools/proto_to_predicate.cc b/tools/proto_to_predicate.cc index 8c89ee2f0..01f35fd05 100644 --- a/tools/proto_to_predicate.cc +++ b/tools/proto_to_predicate.cc @@ -103,6 +103,17 @@ class ProtoToPredicateBuilder final : private ExprFactory { } return e; } + + // Returns either the path specified by the "match_path" annotation, + // or the default path derived from the field name. + Expr GetFieldPath(const Expr& base_expr, + const ::google::protobuf::FieldDescriptor* field) { + std::string match_path_val = GetMatchPath(field); + if (!match_path_val.empty()) { + return ParseAndBuildPath(match_path_val); + } + return NewSelect(NextId(), base_expr, field->name()); + } ExprId NextId() { return id_++; } // --------------------------------------------------------------------------- @@ -246,7 +257,7 @@ class ProtoToPredicateBuilder final : private ExprFactory { const FieldDescriptor* const value_field = field->message_type()->FindFieldByName("value"); - Expr map_path = NewSelect(NextId(), base_expr, field->name()); + Expr map_path = GetFieldPath(base_expr, field); struct MapEntry { const Message* message; @@ -355,7 +366,7 @@ class ProtoToPredicateBuilder final : private ExprFactory { const Message& sub_message = reflection->GetRepeatedMessage(message, field, i); std::vector sub_predicates; - Expr sub_base = NewSelect(NextId(), base_expr, field->name()); + Expr sub_base = GetFieldPath(base_expr, field); CEL_RETURN_IF_ERROR(Walk(sub_message, sub_base, sub_predicates)); message_asts.push_back(LogicalAnd(sub_predicates)); } @@ -426,11 +437,11 @@ class ProtoToPredicateBuilder final : private ExprFactory { } } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { const Message& sub_message = reflection->GetMessage(message, field); - Expr field_path = NewSelect(NextId(), base_expr, field->name()); + Expr field_path = GetFieldPath(base_expr, field); CEL_RETURN_IF_ERROR(Walk(sub_message, field_path, predicates)); } else { // Primitive field: base_expr.field == - Expr field_path = NewSelect(NextId(), base_expr, field->name()); + Expr field_path = GetFieldPath(base_expr, field); predicates.push_back( ConstructEquality(std::move(field_path), PrimitiveToExpr(message, reflection, field))); diff --git a/tools/proto_to_predicate_test.cc b/tools/proto_to_predicate_test.cc index 80ad140c7..2e7b94f6f 100644 --- a/tools/proto_to_predicate_test.cc +++ b/tools/proto_to_predicate_test.cc @@ -549,6 +549,103 @@ INSTANTIATE_TEST_SUITE_P( .json_input = R"({ "destinations": [ { "tool": { } } ] })", .expected_unparsed = "true", }, + PolicyTestCase{ + .name = "AnnotatedSingularFieldInMessage", + .json_input = + R"({ "destinations": [ { + "agent": { + "id": "agent-007", + "location": "us-central1" + } + } ] })", + .expected_unparsed = "dest.agent.name == \"agent-007\" && " + "custom.agent.location == \"us-central1\"", + }, + PolicyTestCase{ + .name = "AnnotatedOneofPrimitiveField", + .json_input = + R"({ "destinations": [ { + "ip": "192.168.1.1" + } ] })", + .expected_unparsed = "custom.ip == \"192.168.1.1\"", + }, + PolicyTestCase{ + .name = "AnnotatedMapFieldPrimitive", + .json_input = + R"({ "destinations": [ { + "tool": { + "annotated_labels": { + "cluster": "us-central1" + } + } + } ] })", + .expected_unparsed = + "\"cluster\" in custom.labels && " + "custom.labels[\"cluster\"] == \"us-central1\"", + }, + PolicyTestCase{ + .name = "AnnotatedMapFieldMessage", + .json_input = + R"({ "destinations": [ { + "tool": { + "annotated_role_members": { + "admin": { + "all_users": true, + "principals": ["alice_user"], + "leader": "alice", + "leaders": ["bob"] + } + } + } + } ] })", + .expected_unparsed = + "\"admin\" in custom.role_members && " + "\"alice_user\" in custom.role_members[\"admin\"].principals " + "&& " + "custom.role_members[\"admin\"].all_users == true && " + "custom.member.leader == \"alice\" && " + "custom.member.leaders in [\"bob\"]", + }, + PolicyTestCase{ + .name = "AnnotatedMessageField", + .json_input = + R"({ "destinations": [ { + "tool": { + "annotated_annotations": { + "read_only_hint": true + } + } + } ] })", + .expected_unparsed = + "custom.tool_annotations.read_only_hint == true", + }, + PolicyTestCase{ + .name = "AnnotatedRepeatedMessageFieldSingle", + .json_input = + R"({ "destinations": [ { + "tool": { + "backup_agents": [ + { "id": "agent-007" } + ] + } + } ] })", + .expected_unparsed = "custom.backup_agents.name == \"agent-007\"", + }, + PolicyTestCase{ + .name = "AnnotatedRepeatedMessageFieldMultiple", + .json_input = + R"({ "destinations": [ { + "tool": { + "backup_agents": [ + { "id": "agent-007" }, + { "id": "agent-008" } + ] + } + } ] })", + .expected_unparsed = + "custom.backup_agents.name == \"agent-007\" || " + "custom.backup_agents.name == \"agent-008\"", + }, PolicyTestCase{ .name = "MapEquality", .json_input = diff --git a/tools/testdata/BUILD b/tools/testdata/BUILD index f9d8ea4b9..956a13eaf 100644 --- a/tools/testdata/BUILD +++ b/tools/testdata/BUILD @@ -32,6 +32,7 @@ proto_library( name = "test_policy_proto", srcs = ["test_policy.proto"], visibility = ["//tools:__subpackages__"], + deps = ["@com_google_protobuf//:descriptor_proto"], ) cc_proto_library( diff --git a/tools/testdata/test_policy.proto b/tools/testdata/test_policy.proto index b5d424c04..5db0641e3 100644 --- a/tools/testdata/test_policy.proto +++ b/tools/testdata/test_policy.proto @@ -19,11 +19,18 @@ edition = "2023"; package cel.cpp.tools; +import "net/proto2/proto/descriptor.proto"; // copybara:replace import "google/protobuf/descriptor.proto"; + option cc_enable_arenas = true; +extend proto2.FieldOptions { + string match_path = 51074; +} + // Represents the targeted client agent. message Agent { string name = 1 [json_name = "id"]; + string location = 2 [(match_path) = "custom.agent.location"]; } // Specifies additional metadata tool annotations. @@ -40,6 +47,10 @@ message Members { bool all_users = 3; bool all_authenticated_users = 4; + + string leader = 5 [(match_path) = "custom.member.leader"]; + + repeated string leaders = 6 [(match_path) = "custom.member.leaders"]; } // Represents a metadata tool block. @@ -56,6 +67,21 @@ message Tool { // A map with string keys representing roles and Member instances as values. map role_members = 4; + + // A map with string keys representing roles and Member instances as values, + // annotated with a match_path. + map annotated_role_members = 5 + [(match_path) = "custom.role_members"]; + + // A string-to-string map, annotated with a match_path. + map annotated_labels = 6 [(match_path) = "custom.labels"]; + + // A non-repeated message field, annotated with a match_path. + ToolAnnotations annotated_annotations = 7 + [(match_path) = "custom.tool_annotations"]; + + // A repeated message field, annotated with a match_path. + repeated Agent backup_agents = 8 [(match_path) = "custom.backup_agents"]; } // Represents a policy mapping destination block. @@ -63,6 +89,7 @@ message Target { oneof kind { Agent agent = 1; Tool tool = 2; + string ip = 3 [(match_path) = "custom.ip"]; } }