Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Audit Logging] Authz policy support for audit logging #32944

Merged
merged 77 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
5f36c9b
audit logging APIs
rockspore Mar 29, 2023
1ef7536
const functions
rockspore Mar 29, 2023
d73779e
fix include guards
rockspore Mar 29, 2023
5fa99bc
move .cc to src/cpp/server
rockspore Mar 29, 2023
8a75d02
trailing newline
rockspore Mar 29, 2023
e77a037
changed the design
rockspore Mar 30, 2023
c6b1174
add register impl in C++
rockspore Mar 30, 2023
6ab33b3
remove extra include
rockspore Mar 30, 2023
c623c9e
newlines
rockspore Mar 30, 2023
3b85b67
include port_platform
rockspore Mar 30, 2023
ba8b7ae
move API headers inside for now
rockspore Apr 7, 2023
f9b7748
Merge branch 'master' of https://github.com/grpc/grpc into audit-log-api
rockspore Apr 7, 2023
eb82b4e
put current src into BUILD targets
rockspore Apr 7, 2023
c7776d3
generate projects
rockspore Apr 7, 2023
579fb6b
virtual dtors, etc
rockspore Apr 10, 2023
ccb3a0c
BUILD
rockspore Apr 10, 2023
70098a0
virtual dtors
rockspore Apr 10, 2023
b323fc8
generate projects
rockspore Apr 10, 2023
03a449a
iwyu
rockspore Apr 10, 2023
f98b12d
external deps in BUILD
rockspore Apr 10, 2023
dc3f3b8
iwyu again
rockspore Apr 10, 2023
0d2fcb5
ctor for audit context
rockspore Apr 10, 2023
07365a4
sanity check
rockspore Apr 10, 2023
c5a8598
add tests and move APIs to public headers
rockspore Apr 11, 2023
fc810a3
generate projects
rockspore Apr 11, 2023
8e8faec
fix iwyu
rockspore Apr 11, 2023
c83b7f0
remove grpc_audit_logging.h from GRPC_PUBLIC_HDRS
rockspore Apr 11, 2023
ced9d10
remove unused params
rockspore Apr 11, 2023
8f536de
remove wrapping in C++
rockspore Apr 19, 2023
f68d735
Merge branch 'master' of github.com:grpc/grpc into audit-log-api
rockspore Apr 19, 2023
81ef1ed
comments and iwyu
rockspore Apr 19, 2023
8af53a5
iwyu
rockspore Apr 19, 2023
ee874d4
add external deps to grpc_public_hdrs
rockspore Apr 20, 2023
2e575a2
comments
rockspore Apr 21, 2023
5dc1b6b
move definition into .cc
rockspore Apr 21, 2023
57c4044
make registry getter private
rockspore Apr 21, 2023
daa86a5
generate projects
rockspore Apr 21, 2023
8e5f24d
remove naked include
rockspore Apr 21, 2023
e32af2b
fix BUILD
rockspore Apr 21, 2023
930d88b
generate projects
rockspore Apr 21, 2023
8664063
fix cpp header
rockspore Apr 21, 2023
a8295ed
constexpr the register func
rockspore Apr 21, 2023
6f6acd1
no lint for unused using decls
rockspore Apr 21, 2023
127f12e
add factory existence API
rockspore Apr 21, 2023
df129cc
change registry's parsing API
rockspore Apr 21, 2023
974bb05
remove extraneous directory and file
rockspore Apr 24, 2023
d426f9d
address comments
rockspore Apr 24, 2023
06bb361
remove naked include
rockspore Apr 24, 2023
7e51701
Automated change: Fix sanity tests
rockspore Apr 24, 2023
6e4b0ec
Merge pull request #13 from rockspore/create-pull-request/patch-06bb361
rockspore Apr 24, 2023
2f3b29b
change to static members
rockspore Apr 25, 2023
dee212e
change to pointers
rockspore Apr 25, 2023
e019b5c
remove naked include
rockspore Apr 25, 2023
5f72e8d
audit logging support in rbac policy
rockspore Apr 25, 2023
73fce55
test to propagate error from config parsing
rockspore Apr 25, 2023
abbcdbd
Automated change: Fix sanity tests
rockspore Apr 26, 2023
bf74d38
remove previous rbac ctor
rockspore Apr 26, 2023
da0d370
Merge branch 'master' of github.com:grpc/grpc into rbac_policy
rockspore Apr 26, 2023
8d254a0
Merge pull request #15 from rockspore/create-pull-request/patch-73fce55
rockspore Apr 26, 2023
ad9353a
update comment on name
rockspore Apr 26, 2023
cdfc43c
address comments
rockspore Apr 27, 2023
80b7305
add assertion on second parsing
rockspore Apr 27, 2023
d192bd8
misc
rockspore Apr 27, 2023
107870f
typo
rockspore Apr 27, 2023
ae94a3f
iwyu
rockspore Apr 27, 2023
ee8f0e1
address comments
rockspore Apr 27, 2023
58c34b7
address comments
rockspore Apr 27, 2023
cf6ca41
empty audit logging options
rockspore Apr 27, 2023
8c84dce
dedup include
rockspore Apr 27, 2023
2d49fb2
reject unknown fields
rockspore Apr 27, 2023
7b12304
Merge branch 'master' of github.com:grpc/grpc into rbac_policy
rockspore May 1, 2023
af73586
address PR comments
rockspore May 2, 2023
69baf28
Merge branch 'master' of github.com:grpc/grpc into rbac_policy
rockspore May 2, 2023
4b3930c
use new Json APIs
rockspore May 2, 2023
c8ed194
fix test
rockspore May 2, 2023
02f44f5
address PR comments
rockspore May 2, 2023
c4851c2
iwyu
rockspore May 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/core/ext/filters/rbac/rbac_service_config_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ struct RbacConfig {
void JsonPostLoad(const Json&, const JsonArgs&, ValidationErrors* errors);
};

std::string name;
absl::optional<Rules> rules;

Rbac TakeAsRbac();
Expand Down Expand Up @@ -756,15 +757,15 @@ Rbac RbacConfig::RbacPolicy::TakeAsRbac() {
if (!rules.has_value()) {
// No enforcing to be applied. An empty deny policy with an empty map
// is equivalent to no enforcing.
// TODO(lwge): Pass the fitler name when working on this parser.
return Rbac(Rbac::Action::kDeny, {}, "");
return Rbac(name, Rbac::Action::kDeny, {});
}
return rules->TakeAsRbac();
}

const JsonLoaderInterface* RbacConfig::RbacPolicy::JsonLoader(const JsonArgs&) {
static const auto* loader = JsonObjectLoader<RbacPolicy>()
.OptionalField("rules", &RbacPolicy::rules)
.Field("filter_name", &RbacPolicy::name)
.Finish();
return loader;
}
Expand Down
24 changes: 14 additions & 10 deletions src/core/lib/security/authorization/rbac_policy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,29 @@ namespace grpc_core {
// Rbac
//

Rbac::Rbac(Rbac::Action action, std::map<std::string, Policy> policies,
absl::string_view name)
: action(action), policies(std::move(policies)), name(name) {}
Rbac::Rbac(absl::string_view name, Rbac::Action action,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well take the name parameter as std::string instead of absl::string_view, since we're making a copy anyway. That way, if a caller happens to have a temp string that they don't need to keep, they can pass it in with std::move() and avoid the copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks for the tip!

std::map<std::string, Policy> policies)
: name(name), action(action), policies(std::move(policies)) {}

Rbac::Rbac(Rbac&& other) noexcept
: action(other.action),
audit_condition(other.audit_condition),
: name(std::move(other.name)),
action(other.action),
policies(std::move(other.policies)),
logger_configs(std::move(other.logger_configs)),
name(std::move(other.name)) {}
audit_condition(other.audit_condition),
logger_configs(std::move(other.logger_configs)) {}

Rbac& Rbac::operator=(Rbac&& other) noexcept {
name = std::move(other.name);
action = other.action;
audit_condition = other.audit_condition;
policies = std::move(other.policies);
audit_condition = other.audit_condition;
logger_configs = std::move(other.logger_configs);
name = std::move(other.name);
return *this;
}

std::string Rbac::ToString() const {
std::vector<std::string> contents;
std::string condition_str;
absl::string_view condition_str;
switch (audit_condition) {
case Rbac::AuditCondition::kNone:
condition_str = "None";
Expand All @@ -73,6 +73,10 @@ std::string Rbac::ToString() const {
contents.push_back(absl::StrFormat("{\n policy_name=%s\n%s\n}", p.first,
p.second.ToString()));
}
for (const auto& config : logger_configs) {
contents.push_back(absl::StrFormat("{\n audit_logger=%s\n%s\n}",
config->name(), config->ToString()));
}
contents.push_back("}");
return absl::StrJoin(contents, "\n");
}
Expand Down
13 changes: 7 additions & 6 deletions src/core/lib/security/authorization/rbac_policy.h
rockspore marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,23 @@ struct Rbac {
};

Rbac() = default;
Rbac(Rbac::Action action, std::map<std::string, Policy> policies,
absl::string_view name);
Rbac(absl::string_view name, Rbac::Action action,
std::map<std::string, Policy> policies);

Rbac(Rbac&& other) noexcept;
Rbac& operator=(Rbac&& other) noexcept;

std::string ToString() const;

Action action;
AuditCondition audit_condition;
// The authorization policy name or the HTTP RBAC filter name.
std::string name;

Action action;
std::map<std::string, Policy> policies;

AuditCondition audit_condition;
std::vector<std::unique_ptr<experimental::AuditLoggerFactory::Config>>
logger_configs;
// The authorization policy name or the HTTP RBAC filter name.
std::string name;
};

} // namespace grpc_core
Expand Down
34 changes: 17 additions & 17 deletions src/core/lib/security/authorization/rbac_translator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,14 @@ absl::StatusOr<Rbac> ParseDenyRulesArray(const Json& json,
absl::string_view name) {
auto policies_or = ParseRulesArray(json);
if (!policies_or.ok()) return policies_or.status();
return Rbac(Rbac::Action::kDeny, std::move(policies_or.value()), name);
return Rbac(name, Rbac::Action::kDeny, std::move(policies_or.value()));
}

absl::StatusOr<Rbac> ParseAllowRulesArray(const Json& json,
absl::string_view name) {
auto policies_or = ParseRulesArray(json);
if (!policies_or.ok()) return policies_or.status();
return Rbac(Rbac::Action::kAllow, std::move(policies_or.value()), name);
return Rbac(name, Rbac::Action::kAllow, std::move(policies_or.value()));
}

absl::StatusOr<std::unique_ptr<experimental::AuditLoggerFactory::Config>>
Expand All @@ -374,10 +374,8 @@ ParseAuditLogger(const Json& json, size_t pos) {
auto it = json.object().find("is_optional");
markdroth marked this conversation as resolved.
Show resolved Hide resolved
if (it != json.object().end()) {
switch (it->second.type()) {
case Json::Type::kTrue:
is_optional = true;
break;
case Json::Type::kFalse:
case Json::Type::kBoolean:
is_optional = it->second.boolean();
break;
default:
return absl::InvalidArgumentError(absl::StrFormat(
Expand All @@ -394,7 +392,8 @@ ParseAuditLogger(const Json& json, size_t pos) {
absl::StrFormat("\"audit_loggers[%d].name\" is not a string.", pos));
}
absl::string_view name = it->second.string();
Json config = Json::Object();
// The config defaults to an empty object.
Json config = Json::FromObject(Json::Object());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just say Json::FromObject({}). The compiler is smart enough to know the type of the {}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

it = json.object().find("config");
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kObject) {
Expand All @@ -420,7 +419,8 @@ ParseAuditLogger(const Json& json, size_t pos) {
return result;
}

absl::Status ParseAuditLoggingOptions(RbacPolicies& rbacs, const Json& json) {
absl::Status ParseAuditLoggingOptions(const Json& json, RbacPolicies* rbacs) {
GPR_ASSERT(rbacs != nullptr);
for (auto it = json.object().begin(); it != json.object().end(); ++it) {
if (it->first == "audit_condition") {
if (it->second.type() != Json::Type::kString) {
Expand All @@ -445,10 +445,10 @@ absl::Status ParseAuditLoggingOptions(RbacPolicies& rbacs, const Json& json) {
return absl::InvalidArgumentError(absl::StrFormat(
"Unsupported \"audit_condition\" value %s.", condition));
}
if (rbacs.deny_policy != absl::nullopt) {
rbacs.deny_policy->audit_condition = deny_condition;
if (rbacs->deny_policy.has_value()) {
rbacs->deny_policy->audit_condition = deny_condition;
}
rbacs.allow_policy.audit_condition = allow_condition;
rbacs->allow_policy.audit_condition = allow_condition;
} else if (it->first == "audit_loggers") {
if (it->second.type() != Json::Type::kArray) {
return absl::InvalidArgumentError("\"audit_loggers\" is not an array.");
Expand All @@ -463,19 +463,19 @@ absl::Status ParseAuditLoggingOptions(RbacPolicies& rbacs, const Json& json) {
// return ok when marked as optional.
if (result.value() != nullptr) {
// Only move the logger config over if audit condition is not NONE.
if (rbacs.allow_policy.audit_condition !=
if (rbacs->allow_policy.audit_condition !=
Rbac::AuditCondition::kNone) {
rbacs.allow_policy.logger_configs.push_back(
rbacs->allow_policy.logger_configs.push_back(
std::move(result.value()));
}
if (rbacs.deny_policy != absl::nullopt &&
rbacs.deny_policy->audit_condition !=
if (rbacs->deny_policy.has_value() &&
rbacs->deny_policy->audit_condition !=
Rbac::AuditCondition::kNone) {
// Parse again since it returns unique_ptr, but result should be ok
// this time.
auto result = ParseAuditLogger(loggers.at(i), i);
GPR_ASSERT(result.ok());
rbacs.deny_policy->logger_configs.push_back(
rbacs->deny_policy->logger_configs.push_back(
std::move(result.value()));
}
}
Expand Down Expand Up @@ -553,7 +553,7 @@ absl::StatusOr<RbacPolicies> GenerateRbacPolicies(
return absl::InvalidArgumentError(
"\"audit_logging_options\" is not an object.");
}
absl::Status status = ParseAuditLoggingOptions(rbacs, it->second);
absl::Status status = ParseAuditLoggingOptions(it->second, &rbacs);
if (!status.ok()) {
return status;
}
Expand Down
40 changes: 37 additions & 3 deletions test/core/ext/filters/rbac/rbac_service_config_parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace grpc_core {
namespace testing {
namespace {

// Test basic parsing of RBAC policy
// Filter name is required in RBAC policy.
TEST(RbacServiceConfigParsingTest, EmptyRbacPolicy) {
const char* test_json =
"{\n"
Expand All @@ -44,6 +44,27 @@ TEST(RbacServiceConfigParsingTest, EmptyRbacPolicy) {
"}";
ChannelArgs args = ChannelArgs().Set(GRPC_ARG_PARSE_RBAC_METHOD_CONFIG, 1);
auto service_config = ServiceConfigImpl::Create(args, test_json);
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(service_config.status().message(),
"errors validating service config: ["
"field:methodConfig[0].rbacPolicy[0].filter_name error:field not "
"present]")
<< service_config.status();
}

// Test basic parsing of RBAC policy
TEST(RbacServiceConfigParsingTest, RbacPolicyWithoutRules) {
const char* test_json =
"{\n"
" \"methodConfig\": [ {\n"
" \"name\": [\n"
" {}\n"
" ],\n"
" \"rbacPolicy\": [ {\"filter_name\": \"rbac\"} ]\n"
" } ]\n"
"}";
ChannelArgs args = ChannelArgs().Set(GRPC_ARG_PARSE_RBAC_METHOD_CONFIG, 1);
auto service_config = ServiceConfigImpl::Create(args, test_json);
ASSERT_TRUE(service_config.ok()) << service_config.status();
const auto* vector_ptr =
(*service_config)->GetMethodParsedConfigVector(grpc_empty_slice());
Expand Down Expand Up @@ -110,7 +131,11 @@ TEST(RbacServiceConfigParsingTest, MultipleRbacPolicies) {
" \"name\": [\n"
" {}\n"
" ],\n"
" \"rbacPolicy\": [ {}, {}, {} ]"
" \"rbacPolicy\": [\n"
" { \"filter_name\": \"rbac-1\" },\n"
" { \"filter_name\": \"rbac-2\" },\n"
" { \"filter_name\": \"rbac-3\" }\n"
" ]"
" } ]\n"
"}";
ChannelArgs args = ChannelArgs().Set(GRPC_ARG_PARSE_RBAC_METHOD_CONFIG, 1);
Expand Down Expand Up @@ -156,7 +181,7 @@ TEST(RbacServiceConfigParsingTest, BadRulesType) {
" \"name\": [\n"
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\"rules\":1}]"
" \"rbacPolicy\": [{\"filter_name\": \"rbac\", \"rules\":1}]\n"
" } ]\n"
"}";
ChannelArgs args = ChannelArgs().Set(GRPC_ARG_PARSE_RBAC_METHOD_CONFIG, 1);
Expand All @@ -176,6 +201,7 @@ TEST(RbacServiceConfigParsingTest, BadActionAndPolicyType) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":{},\n"
" \"policies\":123\n"
Expand Down Expand Up @@ -203,6 +229,7 @@ TEST(RbacServiceConfigParsingTest, MissingPermissionAndPrincipals) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -233,6 +260,7 @@ TEST(RbacServiceConfigParsingTest, EmptyPrincipalAndPermission) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -265,6 +293,7 @@ TEST(RbacServiceConfigParsingTest, VariousPermissionsAndPrincipalsTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -322,6 +351,7 @@ TEST(RbacServiceConfigParsingTest, VariousPermissionsAndPrincipalsBadTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -416,6 +446,7 @@ TEST(RbacServiceConfigParsingTest, HeaderMatcherVariousTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -460,6 +491,7 @@ TEST(RbacServiceConfigParsingTest, HeaderMatcherBadTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -516,6 +548,7 @@ TEST(RbacServiceConfigParsingTest, StringMatcherVariousTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down Expand Up @@ -557,6 +590,7 @@ TEST(RbacServiceConfigParsingTest, StringMatcherBadTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
Expand Down
8 changes: 4 additions & 4 deletions test/core/security/grpc_authorization_engine_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ TEST(GrpcAuthorizationEngineTest, AllowEngineWithMatchingPolicy) {
std::map<std::string, Rbac::Policy> policies;
policies["policy1"] = std::move(policy1);
policies["policy2"] = std::move(policy2);
Rbac rbac(Rbac::Action::kAllow, std::move(policies), "authz");
Rbac rbac("authz", Rbac::Action::kAllow, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
Expand All @@ -46,7 +46,7 @@ TEST(GrpcAuthorizationEngineTest, AllowEngineWithNoMatchingPolicy) {
Rbac::Principal::MakeNotPrincipal(Rbac::Principal::MakeAnyPrincipal()));
std::map<std::string, Rbac::Policy> policies;
policies["policy1"] = std::move(policy1);
Rbac rbac(Rbac::Action::kAllow, std::move(policies), "authz");
Rbac rbac("authz", Rbac::Action::kAllow, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
Expand All @@ -72,7 +72,7 @@ TEST(GrpcAuthorizationEngineTest, DenyEngineWithMatchingPolicy) {
std::map<std::string, Rbac::Policy> policies;
policies["policy1"] = std::move(policy1);
policies["policy2"] = std::move(policy2);
Rbac rbac(Rbac::Action::kDeny, std::move(policies), "authz");
Rbac rbac("authz", Rbac::Action::kDeny, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
Expand All @@ -87,7 +87,7 @@ TEST(GrpcAuthorizationEngineTest, DenyEngineWithNoMatchingPolicy) {
Rbac::Principal::MakeNotPrincipal(Rbac::Principal::MakeAnyPrincipal()));
std::map<std::string, Rbac::Policy> policies;
policies["policy1"] = std::move(policy1);
Rbac rbac(Rbac::Action::kDeny, std::move(policies), "authz");
Rbac rbac("authz", Rbac::Action::kDeny, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
Expand Down