Skip to content

Commit

Permalink
second attempt: rbac service config parsing with audit logging
Browse files Browse the repository at this point in the history
  • Loading branch information
rockspore committed May 18, 2023
1 parent b589c9b commit 54b535a
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 25 deletions.
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
1 change: 1 addition & 0 deletions src/core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3697,6 +3697,7 @@ grpc_cc_library(
"channel_fwd",
"closure",
"error",
"grpc_audit_logging",
"grpc_authorization_base",
"grpc_matchers",
"grpc_rbac_engine",
Expand Down
108 changes: 99 additions & 9 deletions src/core/ext/filters/rbac/rbac_service_config_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,29 @@

#include <cstdint>
#include <map>
#include <memory>
#include <string>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/types/optional.h"

#include <grpc/grpc_audit_logging.h>

#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/json/json_args.h"
#include "src/core/lib/json/json_object_loader.h"
#include "src/core/lib/matchers/matchers.h"
#include "src/core/lib/security/authorization/audit_logging.h"

namespace grpc_core {

namespace {

using experimental::AuditLoggerFactory;
using experimental::AuditLoggerRegistry;

// RbacConfig: one or more RbacPolicy structs
struct RbacConfig {
// RbacPolicy: optional Rules
Expand Down Expand Up @@ -179,16 +187,37 @@ struct RbacConfig {
static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
};

// AuditLogger: the name of logger and its config in json
struct AuditLogger {
std::string name;
Json::Object config;

AuditLogger() = default;
AuditLogger(const AuditLogger&) = delete;
AuditLogger& operator=(const AuditLogger&) = delete;
AuditLogger(AuditLogger&&) = default;
AuditLogger& operator=(AuditLogger&&) = default;

static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
void JsonPostLoad(const Json&, const JsonArgs&,
ValidationErrors* errors);
};

int action;
std::map<std::string, Policy> policies;
Rbac::AuditCondition audit_condition;
std::vector<std::unique_ptr<AuditLoggerFactory::Config>> logger_configs;

Rules() = default;
Rules() {
// Defaults to kNone since its json field is optional.
audit_condition = Rbac::AuditCondition::kNone;
};
Rules(const Rules&) = delete;
Rules& operator=(const Rules&) = delete;
Rules(Rules&&) = default;
Rules& operator=(Rules&&) = default;

Rbac TakeAsRbac();
Rbac TakeAsRbac(std::string name);
static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
void JsonPostLoad(const Json&, const JsonArgs&, ValidationErrors* errors);
};
Expand Down Expand Up @@ -716,32 +745,60 @@ const JsonLoaderInterface* RbacConfig::RbacPolicy::Rules::Policy::JsonLoader(
return loader;
}

//
// RbacConfig::RbacPolicy::Rules::AuditLogger
//

const JsonLoaderInterface*
RbacConfig::RbacPolicy::Rules::AuditLogger::JsonLoader(const JsonArgs&) {
// All fields handled in JsonPostLoad().
static const auto* loader = JsonObjectLoader<AuditLogger>().Finish();
return loader;
}

void RbacConfig::RbacPolicy::Rules::AuditLogger::JsonPostLoad(
const Json& json, const JsonArgs& args, ValidationErrors* errors) {
// Should have exactly one field as the logger name.
if (json.object().size() != 1) {
errors->AddError("audit logger should have exactly one field");
return;
}
name = json.object().begin()->first;
auto config_or =
LoadJsonObjectField<Json::Object>(json.object(), args, name, errors);
if (config_or.has_value()) {
config = std::move(*config_or);
}
}

//
// RbacConfig::RbacPolicy::Rules
//

Rbac RbacConfig::RbacPolicy::Rules::TakeAsRbac() {
Rbac RbacConfig::RbacPolicy::Rules::TakeAsRbac(std::string name) {
Rbac rbac;
// TODO(lwge): This is to fix msan failure for now. Add proper conversion once
// audit logging support is added.
rbac.audit_condition = Rbac::AuditCondition::kNone;
rbac.name = std::move(name);
rbac.action = static_cast<Rbac::Action>(action);
rbac.audit_condition = audit_condition;
for (auto& p : policies) {
rbac.policies.emplace(p.first, p.second.TakeAsRbacPolicy());
}
rbac.logger_configs = std::move(logger_configs);
return rbac;
}

const JsonLoaderInterface* RbacConfig::RbacPolicy::Rules::JsonLoader(
const JsonArgs&) {
// Audit logger configs handled in post load.
static const auto* loader = JsonObjectLoader<Rules>()
.Field("action", &Rules::action)
.OptionalField("policies", &Rules::policies)
.Finish();
return loader;
}

void RbacConfig::RbacPolicy::Rules::JsonPostLoad(const Json&, const JsonArgs&,
void RbacConfig::RbacPolicy::Rules::JsonPostLoad(const Json& json,
const JsonArgs& args,
ValidationErrors* errors) {
// Validate action field.
auto rbac_action = static_cast<Rbac::Action>(action);
Expand All @@ -750,6 +807,40 @@ void RbacConfig::RbacPolicy::Rules::JsonPostLoad(const Json&, const JsonArgs&,
ValidationErrors::ScopedField field(errors, ".action");
errors->AddError("unknown action");
}
// Parse and validate audit_condition field.
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");
}
}
}
// Parse and validate audit logger configs.
auto configs = LoadJsonObjectField<std::vector<AuditLogger>>(
json.object(), args, "audit_loggers", errors, false);
if (configs.has_value()) {
for (size_t i = 0; i < configs->size(); ++i) {
auto& logger = (*configs)[i];
auto config = AuditLoggerRegistry::ParseConfig(
logger.name, Json::FromObject(std::move(logger.config)));
if (!config.ok()) {
ValidationErrors::ScopedField field(
errors, absl::StrCat(".audit_loggers[", i, "]"));
errors->AddError(config.status().message());
continue;
}
logger_configs.push_back(std::move(*config));
}
}
}

//
Expand All @@ -762,8 +853,7 @@ Rbac RbacConfig::RbacPolicy::TakeAsRbac() {
// is equivalent to no enforcing.
return Rbac(std::move(name), Rbac::Action::kDeny, {});
}
// TODO(lwge): This also needs to take the name.
return rules->TakeAsRbac();
return rules->TakeAsRbac(std::move(name));
}

const JsonLoaderInterface* RbacConfig::RbacPolicy::JsonLoader(const JsonArgs&) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ class GrpcAuthorizationEngine : public AuthorizationEngine {
// Required only for testing purpose.
size_t num_policies() const { return policies_.size(); }

// Required only for testing purpose.
Rbac::AuditCondition audit_condition() const { return audit_condition_; }

// Required only for testing purpose.
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.
Decision Evaluate(const EvaluateArgs& args) const override;
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

0 comments on commit 54b535a

Please sign in to comment.