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] Audit logging config translation by rbac service config parser #33145

Merged
merged 23 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7ac6a4d
audit logging in service config parser
rockspore May 5, 2023
72487d0
remove unused using decl and hdrs
rockspore May 5, 2023
d2f15ec
Automated change: Fix sanity tests
rockspore May 5, 2023
bbe16bf
Merge pull request #20 from rockspore/create-pull-request/patch-72487d0
rockspore May 5, 2023
16bd834
stdout audit logger impl
rockspore May 5, 2023
f8f5ee1
generate projects
rockspore May 5, 2023
195f90f
Automated change: Fix sanity tests
rockspore May 5, 2023
50d7ad7
Merge branch 'stdout-logger' of github.com:rockspore/grpc into servic…
rockspore May 5, 2023
1c224f3
Merge pull request #21 from rockspore/create-pull-request/patch-f8f5ee1
rockspore May 5, 2023
296ac6c
Merge branch 'stdout-logger' of github.com:rockspore/grpc into servic…
rockspore May 5, 2023
0e598e9
remove fixture by using stdout logger
rockspore May 5, 2023
361798a
merge from origin
rockspore May 5, 2023
67e5571
Automated change: Fix sanity tests
rockspore May 5, 2023
769dbc5
Merge pull request #22 from rockspore/create-pull-request/patch-361798a
rockspore May 5, 2023
9864edf
Merge branch 'master' of github.com:grpc/grpc into service-config-parser
rockspore May 16, 2023
6adbc3f
public json hdr
rockspore May 16, 2023
7a556e7
Automated change: Fix sanity tests
rockspore May 16, 2023
0aa5024
Merge pull request #25 from rockspore/create-pull-request/patch-6adbc3f
rockspore May 16, 2023
a438bbb
address PR comments
rockspore May 17, 2023
a3a3a64
Merge branch 'service-config-parser' of github.com:rockspore/grpc int…
rockspore May 17, 2023
935af66
address PR comments
rockspore May 17, 2023
ccc6633
iwyu, clang_format and PR comments
rockspore May 17, 2023
0577455
fix test with json bug fixed
rockspore May 17, 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 include/grpc/grpc_audit_logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class AuditContext {
class AuditLogger {
public:
virtual ~AuditLogger() = default;
virtual absl::string_view name() const = 0;
virtual void Log(const AuditContext& audit_context) = 0;
};

Expand Down
39 changes: 16 additions & 23 deletions src/core/ext/filters/rbac/rbac_service_config_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ struct RbacConfig {
int action;
std::map<std::string, Policy> policies;
// Defaults to 0 since its json field is optional.
Copy link
Member

Choose a reason for hiding this comment

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

s/0/kNone/

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.

Rbac::AuditCondition audit_condition;
Rbac::AuditCondition audit_condition = Rbac::AuditCondition::kNone;
std::vector<std::unique_ptr<AuditLoggerFactory::Config>> logger_configs;

Rules() = default;
Expand Down Expand Up @@ -800,32 +800,25 @@ void RbacConfig::RbacPolicy::Rules::JsonPostLoad(const Json& json,
errors->AddError("unknown action");
}
// Parse and validate audit_condition field.
auto it = json.object().find("audit_condition");
int condition = static_cast<int>(Rbac::AuditCondition::kNone);
if (it != json.object().end()) {
if (it->second.type() != Json::Type::kNumber) {
ValidationErrors::ScopedField field(errors, ".audit_condition");
errors->AddError("is not a number");
} else {
GPR_ASSERT(absl::SimpleAtoi(it->second.string(), &condition));
}
}
switch (condition) {
case static_cast<int>(Rbac::AuditCondition::kNone):
case static_cast<int>(Rbac::AuditCondition::kOnAllow):
case static_cast<int>(Rbac::AuditCondition::kOnDeny):
case static_cast<int>(Rbac::AuditCondition::kOnDenyAndAllow):
break;
default: {
ValidationErrors::ScopedField field(errors, ".audit_condition");
errors->AddError("unknown audit condition");
auto condition = LoadJsonObjectField<int>(json.object(), args,
"audit_condition", errors, false);
if (condition.has_value()) {
switch (*condition) {
case static_cast<int>(Rbac::AuditCondition::kNone):
case static_cast<int>(Rbac::AuditCondition::kOnAllow):
case static_cast<int>(Rbac::AuditCondition::kOnDeny):
case static_cast<int>(Rbac::AuditCondition::kOnDenyAndAllow):
audit_condition = static_cast<Rbac::AuditCondition>(*condition);
break;
default: {
ValidationErrors::ScopedField field(errors, ".audit_condition");
errors->AddError("unknown audit condition");
}
}
}
audit_condition = static_cast<Rbac::AuditCondition>(condition);
if (json.object().find("audit_loggers") == json.object().end()) return;
// Parse and validate audit logger configs.
markdroth marked this conversation as resolved.
Show resolved Hide resolved
auto configs = LoadJsonObjectField<std::vector<AuditLogger>>(
json.object(), args, "audit_loggers", errors);
json.object(), args, "audit_loggers", errors, false);
if (configs.has_value()) {
for (size_t i = 0; i < configs->size(); ++i) {
auto& logger = (*configs)[i];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ class GrpcAuthorizationEngine : public AuthorizationEngine {
Rbac::AuditCondition audit_condition() const { return audit_condition_; }

// Required only for testing purpose.
size_t num_audit_loggers() const { return audit_loggers_.size(); }
const std::vector<std::unique_ptr<AuditLogger>>& audit_loggers() const {
return audit_loggers_;
}

// Evaluates incoming request against RBAC policy and makes a decision to
// whether allow/deny this request.
Expand Down
1 change: 1 addition & 0 deletions src/core/lib/security/authorization/stdout_logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace experimental {
class StdoutAuditLogger : public AuditLogger {
public:
StdoutAuditLogger() = default;
absl::string_view name() const override { return "stdout_logger"; }
void Log(const AuditContext&) override;
};

Expand Down
50 changes: 44 additions & 6 deletions test/core/ext/filters/rbac/rbac_service_config_parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include <grpc/grpc.h>
Expand All @@ -44,9 +45,12 @@ using experimental::AuditLoggerFactory;
using experimental::AuditLoggerRegistry;
using experimental::RegisterAuditLoggerFactory;

constexpr absl::string_view kLoggerName = "test_logger";

class TestAuditLogger : public AuditLogger {
public:
TestAuditLogger() = default;
absl::string_view name() const override { return kLoggerName; }
void Log(const AuditContext&) override {}
};

Expand All @@ -55,7 +59,7 @@ class TestAuditLoggerFactory : public AuditLoggerFactory {
class Config : public AuditLoggerFactory::Config {
public:
Config(const Json& json) : config_(JsonDump(json)) {}
absl::string_view name() const override { return "test_logger"; }
absl::string_view name() const override { return kLoggerName; }
std::string ToString() const override { return config_; }

private:
Expand All @@ -64,7 +68,7 @@ class TestAuditLoggerFactory : public AuditLoggerFactory {

TestAuditLoggerFactory(std::map<absl::string_view, std::string>* configs)
: configs_(configs) {}
absl::string_view name() const override { return "test_logger"; }
absl::string_view name() const override { return kLoggerName; }
absl::StatusOr<std::unique_ptr<AuditLoggerFactory::Config>>
ParseAuditLoggerConfig(const Json& json) override {
// Invalidate configs with "bad" field in it.
Expand Down Expand Up @@ -738,10 +742,13 @@ TEST_F(RbacServiceConfigParsingTest, AuditConditionOnDenyWithMultipleLoggers) {
ASSERT_NE(parsed_rbac_config->authorization_engine(0), nullptr);
EXPECT_EQ(parsed_rbac_config->authorization_engine(0)->audit_condition(),
Rbac::AuditCondition::kOnDeny);
EXPECT_EQ(parsed_rbac_config->authorization_engine(0)->num_audit_loggers(),
2);
ASSERT_NE(logger_configs_.find("test_logger"), logger_configs_.end());
EXPECT_EQ(logger_configs_.find("test_logger")->second, "{\"foo\":\"bar\"}");
const auto& loggers =
parsed_rbac_config->authorization_engine(0)->audit_loggers();
ASSERT_EQ(loggers.size(), 2);
Copy link
Member

Choose a reason for hiding this comment

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

Suggest writing this as:

EXPECT_THAT(
    loggers
    ::testing::ElementsAre(
        ::testing::Property(&AuditLogger::name, "stdout_logger"),
        ::testing::Property(&AuditLogger::name, kLoggerName)));

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. I had to use

  EXPECT_THAT(parsed_rbac_config->authorization_engine(0)->audit_loggers(),
              ::testing::ElementsAre(::testing::Pointee(::testing::Property(
                                         &AuditLogger::name, "stdout_logger")),
                                     ::testing::Pointee(::testing::Property(
                                         &AuditLogger::name, kLoggerName))));

because the vector stores unique pointers.

EXPECT_EQ(loggers[0]->name(), "stdout_logger");
EXPECT_EQ(loggers[1]->name(), kLoggerName);
EXPECT_THAT(logger_configs_, ::testing::ElementsAre(::testing::Pair(
"test_logger", "{\"foo\":\"bar\"}")));
}

TEST_F(RbacServiceConfigParsingTest, BadAuditLoggerConfig) {
Expand Down Expand Up @@ -775,6 +782,37 @@ TEST_F(RbacServiceConfigParsingTest, BadAuditLoggerConfig) {
<< service_config.status();
}

TEST_F(RbacServiceConfigParsingTest, UnknownAuditLoggerConfig) {
const char* test_json =
"{\n"
" \"methodConfig\": [ {\n"
" \"name\": [\n"
" {}\n"
" ],\n"
" \"rbacPolicy\": [ {\n"
" \"filter_name\": \"rbac\",\n"
" \"rules\":{\n"
" \"action\":1,\n"
" \"audit_condition\":1,\n"
" \"audit_loggers\":[ \n"
" {\n"
" \"unknown_logger\": {}\n"
" }\n"
" ]\n"
" }\n"
" } ]\n"
" } ]\n"
"}";
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].rules.audit_loggers[0] "
"error:audit logger factory for unknown_logger does not exist]")
<< service_config.status();
}

TEST_F(RbacServiceConfigParsingTest, BadAuditConditionAndLoggersTypes) {
const char* test_json =
"{\n"
Expand Down
1 change: 1 addition & 0 deletions test/core/security/grpc_audit_logging_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace {

class TestAuditLogger : public AuditLogger {
public:
absl::string_view name() const override { return "test_logger"; }
void Log(const AuditContext&) override {}
};

Expand Down
1 change: 1 addition & 0 deletions test/core/security/grpc_authorization_engine_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class TestAuditLogger : public AuditLogger {
std::vector<std::unique_ptr<TestAuditContext>>* contexts)
: contexts_(contexts) {}

absl::string_view name() const override { return kLoggerName; }
void Log(const AuditContext& context) override {
contexts_->push_back(std::make_unique<TestAuditContext>(context));
}
Expand Down