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 69 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
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,7 @@ grpc_cc_library(
"grpc_trace",
"ref_counted_ptr",
"//src/core:error",
"//src/core:grpc_audit_logging",
"//src/core:grpc_authorization_base",
"//src/core:grpc_matchers",
"//src/core:grpc_rbac_engine",
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions build_autogenerated.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions grpc.gyp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/core/ext/filters/rbac/rbac_service_config_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,8 @@ 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.
return Rbac(Rbac::Action::kDeny, {});
// TODO(lwge): Pass the fitler name when working on this parser.
markdroth marked this conversation as resolved.
Show resolved Hide resolved
return Rbac(Rbac::Action::kDeny, {}, "");
}
return rules->TakeAsRbac();
}
Expand Down
32 changes: 28 additions & 4 deletions src/core/lib/security/authorization/rbac_policy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,46 @@ namespace grpc_core {
// Rbac
//

Rbac::Rbac(Rbac::Action action, std::map<std::string, Policy> policies)
: action(action), policies(std::move(policies)) {}
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(Rbac&& other) noexcept
: action(other.action), policies(std::move(other.policies)) {}
: action(other.action),
audit_condition(other.audit_condition),
policies(std::move(other.policies)),
logger_configs(std::move(other.logger_configs)),
name(std::move(other.name)) {}

Rbac& Rbac::operator=(Rbac&& other) noexcept {
action = other.action;
audit_condition = other.audit_condition;
policies = std::move(other.policies);
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;
markdroth marked this conversation as resolved.
Show resolved Hide resolved
switch (audit_condition) {
case Rbac::AuditCondition::kNone:
condition_str = "None";
break;
case AuditCondition::kOnDeny:
condition_str = "OnDeny";
break;
case AuditCondition::kOnAllow:
condition_str = "OnAllow";
break;
case AuditCondition::kOnDenyAndAllow:
condition_str = "OnDenyAndAllow";
break;
}
contents.push_back(absl::StrFormat(
"Rbac action=%s{", action == Rbac::Action::kAllow ? "Allow" : "Deny"));
"Rbac name=%s action=%s audit_condition=%s{", name,
rockspore marked this conversation as resolved.
Show resolved Hide resolved
markdroth marked this conversation as resolved.
Show resolved Hide resolved
action == Rbac::Action::kAllow ? "Allow" : "Deny", condition_str));
for (const auto& p : policies) {
contents.push_back(absl::StrFormat("{\n policy_name=%s\n%s\n}", p.first,
p.second.ToString()));
Expand Down
21 changes: 19 additions & 2 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 @@ -24,20 +24,30 @@
#include <string>
#include <vector>

#include "absl/strings/string_view.h"
#include "absl/types/optional.h"

#include <grpc/grpc_audit_logging.h>

#include "src/core/lib/matchers/matchers.h"

namespace grpc_core {

// Represents Envoy RBAC Proto. [See
// https://github.com/envoyproxy/envoy/blob/release/v1.17/api/envoy/config/rbac/v3/rbac.proto]
// https://github.com/envoyproxy/envoy/blob/release/v1.26/api/envoy/config/rbac/v3/rbac.proto]
struct Rbac {
enum class Action {
kAllow,
kDeny,
};

enum class AuditCondition {
gtcooke94 marked this conversation as resolved.
Show resolved Hide resolved
kNone,
kOnDeny,
kOnAllow,
kOnDenyAndAllow,
};

struct CidrRange {
CidrRange() = default;
CidrRange(std::string address_prefix, uint32_t prefix_len);
Expand Down Expand Up @@ -162,15 +172,22 @@ struct Rbac {
};

Rbac() = default;
Rbac(Rbac::Action action, std::map<std::string, Policy> policies);
Rbac(Rbac::Action action, std::map<std::string, Policy> policies,
absl::string_view name);
markdroth marked this conversation as resolved.
Show resolved Hide resolved

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

std::string ToString() const;

Action action;
AuditCondition audit_condition;
markdroth marked this conversation as resolved.
Show resolved Hide resolved

std::map<std::string, Policy> policies;
std::vector<std::unique_ptr<experimental::AuditLoggerFactory::Config>>
logger_configs;
// The authorization policy name or the HTTP RBAC filter name.
std::string name;
markdroth marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace grpc_core
Expand Down
154 changes: 147 additions & 7 deletions src/core/lib/security/authorization/rbac_translator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,28 @@
#include <vector>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"

#include <grpc/grpc_audit_logging.h>
#include <grpc/support/log.h>

#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/json/json_reader.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/lib/security/authorization/audit_logging.h"

namespace grpc_core {

namespace {

using experimental::AuditLoggerRegistry;

absl::string_view GetMatcherType(absl::string_view value,
StringMatcher::Type* type) {
if (value == "*") {
Expand Down Expand Up @@ -310,7 +318,7 @@ absl::StatusOr<Rbac::Policy> ParseRule(const Json& json,
}

absl::StatusOr<std::map<std::string, Rbac::Policy>> ParseRulesArray(
const Json& json, absl::string_view name) {
const Json& json) {
if (json.array().empty()) {
return absl::InvalidArgumentError("rules is empty.");
}
Expand All @@ -328,24 +336,142 @@ absl::StatusOr<std::map<std::string, Rbac::Policy>> ParseRulesArray(
policy_or.status().code(),
absl::StrCat("rules ", i, ": ", policy_or.status().message()));
}
policies[std::string(name) + "_" + policy_name] =
std::move(policy_or.value());
policies[policy_name] = std::move(policy_or.value());
}
return std::move(policies);
}

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

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

absl::StatusOr<std::unique_ptr<experimental::AuditLoggerFactory::Config>>
ParseAuditLogger(const Json& json, size_t pos) {
if (json.type() != Json::Type::kObject) {
return absl::InvalidArgumentError(
absl::StrFormat("\"audit_loggers[%d]\" is not an object.", pos));
}
bool is_optional = false;
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:
break;
default:
return absl::InvalidArgumentError(absl::StrFormat(
"\"audit_loggers[%d].is_optional\" is not a boolean.", pos));
}
}
it = json.object().find("name");
if (it == json.object().end()) {
return absl::InvalidArgumentError(
absl::StrFormat("\"audit_loggers[%d].name\" is required.", pos));
}
if (it->second.type() != Json::Type::kString) {
return absl::InvalidArgumentError(
absl::StrFormat("\"audit_loggers[%d].name\" is not a string.", pos));
}
absl::string_view name = it->second.string();
Json config = Json::Object();
it = json.object().find("config");
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kObject) {
return absl::InvalidArgumentError(absl::StrFormat(
"\"audit_loggers[%d].config\" is not an object.", pos));
}
config = it->second;
}
if (!AuditLoggerRegistry::FactoryExists(name)) {
if (is_optional) {
return nullptr;
}
return absl::InvalidArgumentError(
absl::StrFormat("\"audit_loggers[%d].name\" %s is not supported "
"natively or registered.",
pos, name));
}
auto result = AuditLoggerRegistry::ParseConfig(name, config);
if (!result.ok()) {
return absl::InvalidArgumentError(absl::StrFormat(
"\"audit_loggers[%d]\" %s", pos, result.status().message()));
gtcooke94 marked this conversation as resolved.
Show resolved Hide resolved
}
return result;
}

absl::Status ParseAuditLoggingOptions(RbacPolicies& rbacs, const Json& json) {
markdroth marked this conversation as resolved.
Show resolved Hide resolved
auto it = json.object().find("audit_condition");
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kString) {
return absl::InvalidArgumentError("\"audit_condition\" is not a string.");
}
absl::string_view condition = it->second.string();
Rbac::AuditCondition deny_condition, allow_condition;
if (condition == "NONE") {
deny_condition = Rbac::AuditCondition::kNone;
allow_condition = Rbac::AuditCondition::kNone;
} else if (condition == "ON_ALLOW") {
deny_condition = Rbac::AuditCondition::kNone;
allow_condition = Rbac::AuditCondition::kOnAllow;
} else if (condition == "ON_DENY") {
deny_condition = Rbac::AuditCondition::kOnDeny;
allow_condition = Rbac::AuditCondition::kOnDeny;
} else if (condition == "ON_DENY_AND_ALLOW") {
deny_condition = Rbac::AuditCondition::kOnDeny;
allow_condition = Rbac::AuditCondition::kOnDenyAndAllow;
} else {
return absl::InvalidArgumentError(absl::StrFormat(
"Unsupported \"audit_condition\" value %s.", condition));
}
if (rbacs.deny_policy != absl::nullopt) {
rbacs.deny_policy->audit_condition = deny_condition;
}
rbacs.allow_policy.audit_condition = allow_condition;
gtcooke94 marked this conversation as resolved.
Show resolved Hide resolved
}
it = json.object().find("audit_loggers");
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kArray) {
return absl::InvalidArgumentError("\"audit_loggers\" is not an array.");
}
const auto& loggers = it->second.array();
for (size_t i = 0; i < loggers.size(); ++i) {
auto result = ParseAuditLogger(loggers.at(i), i);
if (!result.ok()) {
return result.status();
}
// Check the value since the unsupported logger config can also
// 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 != Rbac::AuditCondition::kNone) {
rbacs.allow_policy.logger_configs.push_back(
std::move(result.value()));
}
if (rbacs.deny_policy != absl::nullopt &&
gtcooke94 marked this conversation as resolved.
Show resolved Hide resolved
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(
std::move(result.value()));
}
}
}
}
return absl::OkStatus();
}

} // namespace
Expand Down Expand Up @@ -398,11 +524,25 @@ absl::StatusOr<RbacPolicies> GenerateRbacPolicies(
}
rbacs.allow_policy = std::move(*allow_policy_or);
has_allow_rbac = true;
} else if (object.first == "audit_logging_options") {
// This must be processed this after policies are all parsed.
continue;
} else {
return absl::InvalidArgumentError(absl::StrFormat(
"policy contains unknown field \"%s\".", object.first));
}
}
it = json->object().find("audit_logging_options");
if (it != json->object().end()) {
if (it->second.type() != Json::Type::kObject) {
return absl::InvalidArgumentError(
"\"audit_logging_options\" is not an object.");
}
absl::Status status = ParseAuditLoggingOptions(rbacs, it->second);
if (!status.ok()) {
return status;
}
}
if (!has_allow_rbac) {
return absl::InvalidArgumentError("\"allow_rules\" is not present.");
}
Expand Down