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

Add audit condition and audit logger config into `grpc_core::Rbac`.
Support translation of audit logging options from authz policy to it.

Audit logging options in authz policy looks like:
```json
{
  "audit_logging_options": {
    "audit_condition": "ON_DENY",
    "audit_loggers": [
      {
        "name": "logger",
        "config": {},
        "is_optional": false
      }
    ]
  }
}
```
which is consistent with what's in the xDS RBAC proto but a little
flattened.

---------

Co-authored-by: rockspore <rockspore@users.noreply.github.com>
pull/32994/head
Luwei Ge 2 years ago committed by GitHub
parent d8699afe20
commit 3541ef5d69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      BUILD
  2. 1
      CMakeLists.txt
  3. 2
      build_autogenerated.yaml
  4. 1
      grpc.gyp
  5. 5
      src/core/ext/filters/rbac/rbac_service_config_parser.cc
  6. 37
      src/core/lib/security/authorization/rbac_policy.cc
  7. 21
      src/core/lib/security/authorization/rbac_policy.h
  8. 171
      src/core/lib/security/authorization/rbac_translator.cc
  9. 40
      test/core/ext/filters/rbac/rbac_service_config_parser_test.cc
  10. 8
      test/core/security/grpc_authorization_engine_test.cc
  11. 599
      test/core/security/rbac_translator_test.cc

@ -927,6 +927,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",

1
CMakeLists.txt generated

@ -4408,6 +4408,7 @@ add_library(grpc_authorization_provider
src/core/lib/resource_quota/resource_quota.cc
src/core/lib/resource_quota/thread_quota.cc
src/core/lib/resource_quota/trace.cc
src/core/lib/security/authorization/audit_logging.cc
src/core/lib/security/authorization/authorization_policy_provider_vtable.cc
src/core/lib/security/authorization/evaluate_args.cc
src/core/lib/security/authorization/grpc_authorization_engine.cc

@ -3696,6 +3696,7 @@ libs:
- src/core/lib/resource_quota/resource_quota.h
- src/core/lib/resource_quota/thread_quota.h
- src/core/lib/resource_quota/trace.h
- src/core/lib/security/authorization/audit_logging.h
- src/core/lib/security/authorization/authorization_engine.h
- src/core/lib/security/authorization/authorization_policy_provider.h
- src/core/lib/security/authorization/evaluate_args.h
@ -3943,6 +3944,7 @@ libs:
- src/core/lib/resource_quota/resource_quota.cc
- src/core/lib/resource_quota/thread_quota.cc
- src/core/lib/resource_quota/trace.cc
- src/core/lib/security/authorization/audit_logging.cc
- src/core/lib/security/authorization/authorization_policy_provider_vtable.cc
- src/core/lib/security/authorization/evaluate_args.cc
- src/core/lib/security/authorization/grpc_authorization_engine.cc

1
grpc.gyp generated

@ -1875,6 +1875,7 @@
'src/core/lib/resource_quota/resource_quota.cc',
'src/core/lib/resource_quota/thread_quota.cc',
'src/core/lib/resource_quota/trace.cc',
'src/core/lib/security/authorization/audit_logging.cc',
'src/core/lib/security/authorization/authorization_policy_provider_vtable.cc',
'src/core/lib/security/authorization/evaluate_args.cc',
'src/core/lib/security/authorization/grpc_authorization_engine.cc',

@ -193,6 +193,7 @@ struct RbacConfig {
void JsonPostLoad(const Json&, const JsonArgs&, ValidationErrors* errors);
};
std::string name;
absl::optional<Rules> rules;
Rbac TakeAsRbac();
@ -756,14 +757,16 @@ 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, {});
return Rbac(std::move(name), Rbac::Action::kDeny, {});
}
// TODO(lwge): This also needs to take the name.
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;
}

@ -22,6 +22,7 @@
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
namespace grpc_core {
@ -29,26 +30,54 @@ namespace grpc_core {
// Rbac
//
Rbac::Rbac(Rbac::Action action, std::map<std::string, Policy> policies)
: action(action), policies(std::move(policies)) {}
Rbac::Rbac(std::string name, Rbac::Action action,
std::map<std::string, Policy> policies)
: name(std::move(name)), action(action), policies(std::move(policies)) {}
Rbac::Rbac(Rbac&& other) noexcept
: action(other.action), policies(std::move(other.policies)) {}
: name(std::move(other.name)),
action(other.action),
policies(std::move(other.policies)),
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;
policies = std::move(other.policies);
audit_condition = other.audit_condition;
logger_configs = std::move(other.logger_configs);
return *this;
}
std::string Rbac::ToString() const {
std::vector<std::string> contents;
absl::string_view condition_str;
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,
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()));
}
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");
}

@ -26,18 +26,27 @@
#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 {
kNone,
kOnDeny,
kOnAllow,
kOnDenyAndAllow,
};
struct CidrRange {
CidrRange() = default;
CidrRange(std::string address_prefix, uint32_t prefix_len);
@ -162,15 +171,23 @@ struct Rbac {
};
Rbac() = default;
Rbac(Rbac::Action action, std::map<std::string, Policy> policies);
Rbac(std::string name, Rbac::Action action,
std::map<std::string, Policy> policies);
Rbac(Rbac&& other) noexcept;
Rbac& operator=(Rbac&& other) noexcept;
std::string ToString() const;
// 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;
};
} // namespace grpc_core

@ -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 == "*") {
@ -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.");
}
@ -328,24 +336,159 @@ 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(std::string(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, 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(std::string(name), Rbac::Action::kAllow,
std::move(policies_or.value()));
}
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));
}
for (const auto& object : json.object()) {
if (object.first != "name" && object.first != "is_optional" &&
object.first != "config") {
return absl::InvalidArgumentError(
absl::StrFormat("policy contains unknown field \"%s\" in "
"\"audit_logging_options.audit_loggers[%d]\".",
object.first, pos));
}
}
bool is_optional = false;
auto it = json.object().find("is_optional");
if (it != json.object().end()) {
switch (it->second.type()) {
case Json::Type::kBoolean:
is_optional = it->second.boolean();
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();
// The config defaults to an empty object.
Json config = Json::FromObject({});
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()));
}
return result;
}
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) {
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.has_value()) {
rbacs->deny_policy->audit_condition = deny_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.");
}
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.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(
std::move(result.value()));
}
}
}
} else {
return absl::InvalidArgumentError(absl::StrFormat(
"policy contains unknown field \"%s\" in \"audit_logging_options\".",
it->first));
}
}
return absl::OkStatus();
}
} // namespace
@ -398,11 +541,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(it->second, &rbacs);
if (!status.ok()) {
return status;
}
}
if (!has_allow_rbac) {
return absl::InvalidArgumentError("\"allow_rules\" is not present.");
}

@ -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"
@ -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());
@ -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);
@ -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);
@ -176,6 +201,7 @@ TEST(RbacServiceConfigParsingTest, BadActionAndPolicyType) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":{},\n"
" \"policies\":123\n"
@ -203,6 +229,7 @@ TEST(RbacServiceConfigParsingTest, MissingPermissionAndPrincipals) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -233,6 +260,7 @@ TEST(RbacServiceConfigParsingTest, EmptyPrincipalAndPermission) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -265,6 +293,7 @@ TEST(RbacServiceConfigParsingTest, VariousPermissionsAndPrincipalsTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -322,6 +351,7 @@ TEST(RbacServiceConfigParsingTest, VariousPermissionsAndPrincipalsBadTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -416,6 +446,7 @@ TEST(RbacServiceConfigParsingTest, HeaderMatcherVariousTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -460,6 +491,7 @@ TEST(RbacServiceConfigParsingTest, HeaderMatcherBadTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -516,6 +548,7 @@ TEST(RbacServiceConfigParsingTest, StringMatcherVariousTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"
@ -557,6 +590,7 @@ TEST(RbacServiceConfigParsingTest, StringMatcherBadTypes) {
" {}\n"
" ],\n"
" \"rbacPolicy\": [{\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"policies\":{\n"

@ -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));
Rbac rbac("authz", Rbac::Action::kAllow, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
@ -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));
Rbac rbac("authz", Rbac::Action::kAllow, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
@ -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));
Rbac rbac("authz", Rbac::Action::kDeny, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));
@ -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));
Rbac rbac("authz", Rbac::Action::kDeny, std::move(policies));
GrpcAuthorizationEngine engine(std::move(rbac));
AuthorizationEngine::Decision decision =
engine.Evaluate(EvaluateArgs(nullptr, nullptr));

@ -14,15 +14,33 @@
#include "src/core/lib/security/authorization/rbac_translator.h"
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include <grpc/grpc_audit_logging.h>
#include "src/core/lib/gprpp/crash.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/json/json_writer.h"
#include "src/core/lib/security/authorization/audit_logging.h"
#include "test/core/util/test_config.h"
namespace grpc_core {
namespace {
constexpr absl::string_view kLoggerName = "test_logger";
using experimental::AuditLogger;
using experimental::AuditLoggerFactory;
using experimental::AuditLoggerRegistry;
using experimental::RegisterAuditLoggerFactory;
MATCHER_P3(EqualsPrincipalName, expected_matcher_type, expected_matcher_value,
is_regex, "") {
return arg->type == Rbac::Principal::RuleType::kPrincipalName &&
@ -53,9 +71,47 @@ MATCHER_P4(EqualsHeader, expected_name, expected_matcher_type,
: arg->header_matcher.string_matcher() == expected_matcher_value;
}
class TestAuditLoggerFactory : public AuditLoggerFactory {
public:
class TestAuditLoggerConfig : public AuditLoggerFactory::Config {
public:
explicit TestAuditLoggerConfig(std::string config_dump)
: config_dump_(std::move(config_dump)) {}
absl::string_view name() const override { return kLoggerName; }
std::string ToString() const override { return config_dump_; }
private:
std::string config_dump_;
};
absl::string_view name() const override { return kLoggerName; }
absl::StatusOr<std::unique_ptr<AuditLoggerFactory::Config>>
ParseAuditLoggerConfig(const Json& json) override {
// Config with a field "bad" will be considered invalid.
if (json.object().find("bad") != json.object().end()) {
return absl::InvalidArgumentError("bad logger config.");
}
return std::make_unique<TestAuditLoggerConfig>(JsonDump(json));
}
std::unique_ptr<AuditLogger> CreateAuditLogger(
std::unique_ptr<AuditLoggerFactory::Config>) override {
// This test target should never need to create a logger.
Crash("unreachable");
return nullptr;
}
};
class GenerateRbacPoliciesTest : public ::testing::Test {
protected:
void SetUp() override {
RegisterAuditLoggerFactory(std::make_unique<TestAuditLoggerFactory>());
}
void TearDown() override { AuditLoggerRegistry::TestOnlyResetRegistry(); }
};
} // namespace
TEST(GenerateRbacPoliciesTest, InvalidPolicy) {
TEST_F(GenerateRbacPoliciesTest, InvalidPolicy) {
const char* authz_policy =
"{"
" \"name\": \"authz-policy\",,"
@ -67,14 +123,14 @@ TEST(GenerateRbacPoliciesTest, InvalidPolicy) {
::testing::StartsWith("Failed to parse gRPC authorization policy."));
}
TEST(GenerateRbacPoliciesTest, MissingAuthorizationPolicyName) {
TEST_F(GenerateRbacPoliciesTest, MissingAuthorizationPolicyName) {
const char* authz_policy = "{}";
auto rbac_policies = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbac_policies.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(rbac_policies.status().message(), "\"name\" field is not present.");
}
TEST(GenerateRbacPoliciesTest, IncorrectAuthorizationPolicyNameType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectAuthorizationPolicyNameType) {
const char* authz_policy =
"{"
" \"name\": [\"authz_policy\"]"
@ -84,7 +140,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectAuthorizationPolicyNameType) {
EXPECT_EQ(rbac_policies.status().message(), "\"name\" is not a string.");
}
TEST(GenerateRbacPoliciesTest, MissingAllowRules) {
TEST_F(GenerateRbacPoliciesTest, MissingAllowRules) {
const char* authz_policy =
"{"
" \"name\": \"authz_policy\""
@ -95,7 +151,7 @@ TEST(GenerateRbacPoliciesTest, MissingAllowRules) {
"\"allow_rules\" is not present.");
}
TEST(GenerateRbacPoliciesTest, MissingDenyRules) {
TEST_F(GenerateRbacPoliciesTest, MissingDenyRules) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -107,10 +163,11 @@ TEST(GenerateRbacPoliciesTest, MissingDenyRules) {
"}";
auto rbac_policies = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbac_policies.ok());
EXPECT_EQ(rbac_policies->allow_policy.name, "authz");
EXPECT_FALSE(rbac_policies->deny_policy.has_value());
}
TEST(GenerateRbacPoliciesTest, IncorrectAllowRulesType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectAllowRulesType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -122,7 +179,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectAllowRulesType) {
"\"allow_rules\" is not an array.");
}
TEST(GenerateRbacPoliciesTest, IncorrectDenyRulesType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectDenyRulesType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -134,7 +191,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectDenyRulesType) {
"\"deny_rules\" is not an array.");
}
TEST(GenerateRbacPoliciesTest, IncorrectRuleType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectRuleType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -146,7 +203,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectRuleType) {
"allow_rules 0: is not an object.");
}
TEST(GenerateRbacPoliciesTest, EmptyRuleArray) {
TEST_F(GenerateRbacPoliciesTest, EmptyRuleArray) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -157,7 +214,7 @@ TEST(GenerateRbacPoliciesTest, EmptyRuleArray) {
EXPECT_EQ(rbac_policies.status().message(), "allow_rules is empty.");
}
TEST(GenerateRbacPoliciesTest, MissingRuleNameField) {
TEST_F(GenerateRbacPoliciesTest, MissingRuleNameField) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -169,7 +226,7 @@ TEST(GenerateRbacPoliciesTest, MissingRuleNameField) {
"allow_rules 0: \"name\" is not present.");
}
TEST(GenerateRbacPoliciesTest, IncorrectRuleNameType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectRuleNameType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -185,7 +242,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectRuleNameType) {
"allow_rules 0: \"name\" is not a string.");
}
TEST(GenerateRbacPoliciesTest, MissingSourceAndRequest) {
TEST_F(GenerateRbacPoliciesTest, MissingSourceAndRequest) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -197,11 +254,12 @@ TEST(GenerateRbacPoliciesTest, MissingSourceAndRequest) {
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->allow_policy.action, Rbac::Action::kAllow);
EXPECT_THAT(
rbacs->allow_policy.policies,
::testing::ElementsAre(::testing::Pair(
"authz_allow_policy",
"allow_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::permissions,
@ -213,7 +271,7 @@ TEST(GenerateRbacPoliciesTest, MissingSourceAndRequest) {
Rbac::Principal::RuleType::kAny))))));
}
TEST(GenerateRbacPoliciesTest, EmptySourceAndRequest) {
TEST_F(GenerateRbacPoliciesTest, EmptySourceAndRequest) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -227,11 +285,12 @@ TEST(GenerateRbacPoliciesTest, EmptySourceAndRequest) {
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->allow_policy.action, Rbac::Action::kAllow);
EXPECT_THAT(
rbacs->allow_policy.policies,
::testing::ElementsAre(::testing::Pair(
"authz_allow_policy",
"allow_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::permissions,
@ -243,7 +302,7 @@ TEST(GenerateRbacPoliciesTest, EmptySourceAndRequest) {
Rbac::Principal::RuleType::kAny))))));
}
TEST(GenerateRbacPoliciesTest, IncorrectSourceType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectSourceType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -260,7 +319,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectSourceType) {
"allow_rules 0: \"source\" is not an object.");
}
TEST(GenerateRbacPoliciesTest, IncorrectPrincipalsType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectPrincipalsType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -282,7 +341,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectPrincipalsType) {
"allow_rules 0: \"principals\" 1: is not a string.");
}
TEST(GenerateRbacPoliciesTest, ParseSourceSuccess) {
TEST_F(GenerateRbacPoliciesTest, ParseSourceSuccess) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -312,10 +371,12 @@ TEST(GenerateRbacPoliciesTest, ParseSourceSuccess) {
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->deny_policy->name, "authz");
EXPECT_EQ(rbacs->allow_policy.action, Rbac::Action::kAllow);
EXPECT_THAT(rbacs->allow_policy.policies,
::testing::ElementsAre(::testing::Pair(
"authz_allow_policy",
"allow_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::permissions,
@ -353,7 +414,7 @@ TEST(GenerateRbacPoliciesTest, ParseSourceSuccess) {
EXPECT_THAT(
rbacs->deny_policy->policies,
::testing::ElementsAre(::testing::Pair(
"authz_deny_policy",
"deny_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::permissions,
@ -377,7 +438,7 @@ TEST(GenerateRbacPoliciesTest, ParseSourceSuccess) {
true)))))))))))));
}
TEST(GenerateRbacPoliciesTest, IncorrectRequestType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectRequestType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -394,7 +455,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectRequestType) {
"deny_rules 0: \"request\" is not an object.");
}
TEST(GenerateRbacPoliciesTest, IncorrectPathType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectPathType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -416,7 +477,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectPathType) {
"deny_rules 0: \"paths\" 1: is not a string.");
}
TEST(GenerateRbacPoliciesTest, ParseRequestPathsSuccess) {
TEST_F(GenerateRbacPoliciesTest, ParseRequestPathsSuccess) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -445,12 +506,14 @@ TEST(GenerateRbacPoliciesTest, ParseRequestPathsSuccess) {
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->deny_policy->name, "authz");
ASSERT_TRUE(rbacs->deny_policy.has_value());
EXPECT_EQ(rbacs->deny_policy->action, Rbac::Action::kDeny);
EXPECT_THAT(
rbacs->deny_policy->policies,
::testing::ElementsAre(::testing::Pair(
"authz_deny_policy",
"deny_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::principals,
@ -480,7 +543,7 @@ TEST(GenerateRbacPoliciesTest, ParseRequestPathsSuccess) {
EXPECT_THAT(
rbacs->allow_policy.policies,
::testing::ElementsAre(::testing::Pair(
"authz_allow_policy",
"allow_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::principals,
@ -504,7 +567,7 @@ TEST(GenerateRbacPoliciesTest, ParseRequestPathsSuccess) {
true)))))))))))));
}
TEST(GenerateRbacPoliciesTest, IncorrectHeaderType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectHeaderType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -525,7 +588,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectHeaderType) {
"deny_rules 0: \"headers\" 0: is not an object.");
}
TEST(GenerateRbacPoliciesTest, MissingHeaderKey) {
TEST_F(GenerateRbacPoliciesTest, MissingHeaderKey) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -546,7 +609,7 @@ TEST(GenerateRbacPoliciesTest, MissingHeaderKey) {
"allow_rules 0: \"headers\" 0: \"key\" is not present.");
}
TEST(GenerateRbacPoliciesTest, MissingHeaderValues) {
TEST_F(GenerateRbacPoliciesTest, MissingHeaderValues) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -569,7 +632,7 @@ TEST(GenerateRbacPoliciesTest, MissingHeaderValues) {
"allow_rules 0: \"headers\" 0: \"values\" is not present.");
}
TEST(GenerateRbacPoliciesTest, IncorrectHeaderKeyType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectHeaderKeyType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -593,7 +656,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectHeaderKeyType) {
"allow_rules 0: \"headers\" 0: \"key\" is not a string.");
}
TEST(GenerateRbacPoliciesTest, IncorrectHeaderValuesType) {
TEST_F(GenerateRbacPoliciesTest, IncorrectHeaderValuesType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -617,7 +680,7 @@ TEST(GenerateRbacPoliciesTest, IncorrectHeaderValuesType) {
"allow_rules 0: \"headers\" 0: \"values\" is not an array.");
}
TEST(GenerateRbacPoliciesTest, UnsupportedGrpcHeaders) {
TEST_F(GenerateRbacPoliciesTest, UnsupportedGrpcHeaders) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -643,7 +706,7 @@ TEST(GenerateRbacPoliciesTest, UnsupportedGrpcHeaders) {
"deny_rules 0: \"headers\" 0: Unsupported \"key\" grpc-xxx.");
}
TEST(GenerateRbacPoliciesTest, UnsupportedPseudoHeaders) {
TEST_F(GenerateRbacPoliciesTest, UnsupportedPseudoHeaders) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -669,7 +732,7 @@ TEST(GenerateRbacPoliciesTest, UnsupportedPseudoHeaders) {
"allow_rules 0: \"headers\" 0: Unsupported \"key\" :method.");
}
TEST(GenerateRbacPoliciesTest, UnsupportedHostHeader) {
TEST_F(GenerateRbacPoliciesTest, UnsupportedHostHeader) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -695,7 +758,7 @@ TEST(GenerateRbacPoliciesTest, UnsupportedHostHeader) {
"allow_rules 0: \"headers\" 0: Unsupported \"key\" Host.");
}
TEST(GenerateRbacPoliciesTest, EmptyHeaderValuesList) {
TEST_F(GenerateRbacPoliciesTest, EmptyHeaderValuesList) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -720,7 +783,7 @@ TEST(GenerateRbacPoliciesTest, EmptyHeaderValuesList) {
"allow_rules 0: \"headers\" 0: \"values\" list is empty.");
}
TEST(GenerateRbacPoliciesTest, ParseRequestHeadersSuccess) {
TEST_F(GenerateRbacPoliciesTest, ParseRequestHeadersSuccess) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -750,11 +813,12 @@ TEST(GenerateRbacPoliciesTest, ParseRequestHeadersSuccess) {
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->allow_policy.action, Rbac::Action::kAllow);
EXPECT_THAT(
rbacs->allow_policy.policies,
::testing::ElementsAre(::testing::Pair(
"authz_allow_policy",
"allow_policy",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::principals,
@ -810,7 +874,7 @@ TEST(GenerateRbacPoliciesTest, ParseRequestHeadersSuccess) {
false)))))))))))))))));
}
TEST(GenerateRbacPoliciesTest, ParseRulesArraySuccess) {
TEST_F(GenerateRbacPoliciesTest, ParseRulesArraySuccess) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -835,12 +899,13 @@ TEST(GenerateRbacPoliciesTest, ParseRulesArraySuccess) {
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->allow_policy.action, Rbac::Action::kAllow);
EXPECT_THAT(
rbacs->allow_policy.policies,
::testing::ElementsAre(
::testing::Pair(
"authz_allow_policy_1",
"allow_policy_1",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::permissions,
@ -877,7 +942,7 @@ TEST(GenerateRbacPoliciesTest, ParseRulesArraySuccess) {
"spiffe://foo.abc",
false))))))))))),
::testing::Pair(
"authz_allow_policy_2",
"allow_policy_2",
::testing::AllOf(
::testing::Field(
&Rbac::Policy::permissions,
@ -889,7 +954,7 @@ TEST(GenerateRbacPoliciesTest, ParseRulesArraySuccess) {
Rbac::Principal::RuleType::kAny))))));
}
TEST(GenerateRbacPoliciesTest, UnknownFieldInTopLayer) {
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInTopLayer) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -906,7 +971,7 @@ TEST(GenerateRbacPoliciesTest, UnknownFieldInTopLayer) {
"policy contains unknown field \"foo\".");
}
TEST(GenerateRbacPoliciesTest, UnknownFieldInRule) {
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInRule) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -924,7 +989,7 @@ TEST(GenerateRbacPoliciesTest, UnknownFieldInRule) {
"allow_rules 0: policy contains unknown field \"foo\" in \"rule\".");
}
TEST(GenerateRbacPoliciesTest, UnknownFieldInSource) {
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInSource) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -946,7 +1011,7 @@ TEST(GenerateRbacPoliciesTest, UnknownFieldInSource) {
"allow_rules 0: policy contains unknown field \"foo\" in \"source\".");
}
TEST(GenerateRbacPoliciesTest, UnknownFieldInRequest) {
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInRequest) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -964,7 +1029,7 @@ TEST(GenerateRbacPoliciesTest, UnknownFieldInRequest) {
"allow_rules 0: policy contains unknown field \"foo\" in \"request\".");
}
TEST(GenerateRbacPoliciesTest, UnknownFieldInHeaders) {
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInHeaders) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
@ -984,6 +1049,452 @@ TEST(GenerateRbacPoliciesTest, UnknownFieldInHeaders) {
"\"foo\".");
}
TEST_F(GenerateRbacPoliciesTest, EmptyAuditLoggingOptions) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {}"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
}
TEST_F(GenerateRbacPoliciesTest, AuditConditionNone) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": \"NONE\""
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->allow_policy.audit_condition, Rbac::AuditCondition::kNone);
EXPECT_TRUE(
absl::StartsWith(rbacs->allow_policy.ToString(),
"Rbac name=authz action=Allow audit_condition=None"));
}
TEST_F(GenerateRbacPoliciesTest, AuditConditionOnDeny) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"deny_rules\": ["
" {"
" \"name\": \"deny_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": \"ON_DENY\""
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->deny_policy->name, "authz");
EXPECT_EQ(rbacs->allow_policy.audit_condition, Rbac::AuditCondition::kOnDeny);
EXPECT_EQ(rbacs->deny_policy->audit_condition, Rbac::AuditCondition::kOnDeny);
}
TEST_F(GenerateRbacPoliciesTest, AuditConditionOnAllowWithAuditLoggers) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"deny_rules\": ["
" {"
" \"name\": \"deny_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": \"ON_ALLOW\","
" \"audit_loggers\": ["
" {"
" \"name\": \"test_logger\","
" \"config\": {"
" \"foo\": true"
" }"
" },"
" {"
" \"name\": \"test_logger\","
" \"config\": {"
" \"bar\": true"
" }"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->deny_policy->name, "authz");
EXPECT_EQ(rbacs->allow_policy.audit_condition,
Rbac::AuditCondition::kOnAllow);
EXPECT_EQ(rbacs->deny_policy->audit_condition, Rbac::AuditCondition::kNone);
ASSERT_EQ(rbacs->allow_policy.logger_configs.size(), 2);
EXPECT_EQ(rbacs->deny_policy->logger_configs.size(), 0);
EXPECT_EQ(rbacs->allow_policy.logger_configs.at(0)->name(), kLoggerName);
EXPECT_EQ(rbacs->allow_policy.logger_configs.at(1)->name(), kLoggerName);
EXPECT_EQ(rbacs->allow_policy.logger_configs.at(0)->ToString(),
"{\"foo\":true}");
EXPECT_EQ(rbacs->allow_policy.logger_configs.at(1)->ToString(),
"{\"bar\":true}");
EXPECT_EQ(rbacs->allow_policy.ToString(),
"Rbac name=authz action=Allow audit_condition=OnAllow{\n{\n "
"policy_name=allow_policy\n Policy {\n Permissions{any}\n "
"Principals{any}\n }\n}\n{\n "
"audit_logger=test_logger\n{\"foo\":true}\n}\n{\n "
"audit_logger=test_logger\n{\"bar\":true}\n}\n}");
}
TEST_F(GenerateRbacPoliciesTest,
AuditConditionOnDenyAndAllowWithUnsupportedButOptionalLogger) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"deny_rules\": ["
" {"
" \"name\": \"deny_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": \"ON_DENY_AND_ALLOW\","
" \"audit_loggers\": ["
" {"
" \"name\": \"unknown_logger\","
" \"is_optional\": true,"
" \"config\": {}"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->deny_policy->name, "authz");
EXPECT_EQ(rbacs->allow_policy.audit_condition,
Rbac::AuditCondition::kOnDenyAndAllow);
EXPECT_EQ(rbacs->deny_policy->audit_condition, Rbac::AuditCondition::kOnDeny);
EXPECT_EQ(rbacs->allow_policy.logger_configs.size(), 0);
EXPECT_EQ(rbacs->deny_policy->logger_configs.size(), 0);
}
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInAuditLoggingOptions) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"foo\": 123"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(
rbacs.status().message(),
"policy contains unknown field \"foo\" in \"audit_logging_options\".");
}
TEST_F(GenerateRbacPoliciesTest, AuditConditionIsNotString) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": 123"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(), "\"audit_condition\" is not a string.");
}
TEST_F(GenerateRbacPoliciesTest, IncorrectAuditConditionValue) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": \"UNKNOWN\""
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"Unsupported \"audit_condition\" value UNKNOWN.");
}
TEST_F(GenerateRbacPoliciesTest, IncorrectAuditLoggersType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": 123"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(), "\"audit_loggers\" is not an array.");
}
TEST_F(GenerateRbacPoliciesTest, IncorrectAuditLoggerType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": [123]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0]\" is not an object.");
}
TEST_F(GenerateRbacPoliciesTest, UnknownFieldInAuditLoggers) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"foo\": 123"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"policy contains unknown field \"foo\" in "
"\"audit_logging_options.audit_loggers[0]\".");
}
TEST_F(GenerateRbacPoliciesTest, IncorrectAuditLoggerConfigType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"name\": \"unknown_logger\","
" \"config\": 123"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0].config\" is not an object.");
}
TEST_F(GenerateRbacPoliciesTest, BadAuditLoggerConfig) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"name\": \"test_logger\","
" \"config\": {\"bad\": true}"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0]\" bad logger config.");
}
TEST_F(GenerateRbacPoliciesTest, IncorrectAuditLoggerNameType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"name\": 123,"
" \"config\": {}"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0].name\" is not a string.");
}
TEST_F(GenerateRbacPoliciesTest, IncorrectAuditLoggerIsOptionalType) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"name\": \"test_logger\","
" \"is_optional\": 123,"
" \"config\": {}"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0].is_optional\" is not a boolean.");
}
TEST_F(GenerateRbacPoliciesTest, MissingAuditLoggerName) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"config\": {}"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0].name\" is required.");
}
TEST_F(GenerateRbacPoliciesTest, MissingAuditLoggerConfig) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_condition\": \"ON_DENY\","
" \"audit_loggers\": ["
" {"
" \"name\": \"test_logger\""
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
ASSERT_TRUE(rbacs.ok());
EXPECT_EQ(rbacs->allow_policy.name, "authz");
EXPECT_EQ(rbacs->allow_policy.audit_condition, Rbac::AuditCondition::kOnDeny);
EXPECT_EQ(rbacs->allow_policy.logger_configs.size(), 1);
EXPECT_EQ(rbacs->allow_policy.logger_configs.at(0)->name(), kLoggerName);
EXPECT_EQ(rbacs->allow_policy.logger_configs.at(0)->ToString(), "{}");
}
TEST_F(GenerateRbacPoliciesTest, UnsupportedAuditLogger) {
const char* authz_policy =
"{"
" \"name\": \"authz\","
" \"allow_rules\": ["
" {"
" \"name\": \"allow_policy\""
" }"
" ],"
" \"audit_logging_options\": {"
" \"audit_loggers\": ["
" {"
" \"name\": \"unknown_logger\","
" \"config\": {}"
" }"
" ]"
" }"
"}";
auto rbacs = GenerateRbacPolicies(authz_policy);
EXPECT_EQ(rbacs.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_THAT(rbacs.status().message(),
"\"audit_loggers[0].name\" unknown_logger is not supported "
"natively or registered.");
}
} // namespace grpc_core
int main(int argc, char** argv) {

Loading…
Cancel
Save