Skip to content

Commit bc85513

Browse files
Merge pull request #29000 from michael-redpanda/gbac/core-14893/initial-processing
[CORE-14893] Process group claims from OIDC tokens
2 parents ddac63b + 4f71769 commit bc85513

23 files changed

+452
-67
lines changed

src/v/cluster/controller.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,12 @@ ss::future<> controller::wire_up() {
215215
ss::sharded_parameter([] {
216216
return config::shard_local_cfg()
217217
.oidc_keys_refresh_interval.bind();
218+
}),
219+
ss::sharded_parameter([] {
220+
return config::shard_local_cfg().oidc_group_claim_path.bind();
221+
}),
222+
ss::sharded_parameter([] {
223+
return config::shard_local_cfg().nested_group_behavior.bind();
218224
}));
219225
})
220226
.then([this] {

src/v/config/configuration.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,6 +3887,25 @@ configuration::configuration()
38873887
"access tokens.",
38883888
{.needs_restart = needs_restart::no, .visibility = visibility::user},
38893889
1h)
3890+
, oidc_group_claim_path(
3891+
*this,
3892+
"oidc_group_claim_path",
3893+
"JSON path to extract groups from the JWT payload.",
3894+
{.needs_restart = needs_restart::no, .visibility = visibility::user},
3895+
"$.groups",
3896+
security::oidc::validate_group_claim_path)
3897+
, nested_group_behavior(
3898+
*this,
3899+
"nested_group_behavior",
3900+
"Behavior for handling nested groups when extracting groups from "
3901+
"authentication tokens. Two options are available - none and suffix. "
3902+
"With none, the group is left alone (e.g. '/group/child/grandchild'). "
3903+
"Suffix will extract the final component from the nested group (e.g. "
3904+
"'/group' -> 'group' and '/group/child/grandchild' -> 'grandchild').",
3905+
{.needs_restart = needs_restart::no, .visibility = visibility::user},
3906+
security::oidc::nested_group_behavior::none,
3907+
{security::oidc::nested_group_behavior::none,
3908+
security::oidc::nested_group_behavior::suffix})
38903909
, http_authentication(
38913910
*this,
38923911
"OIDC",

src/v/config/configuration.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "model/metadata.h"
2929
#include "model/timestamp.h"
3030
#include "pandaproxy/schema_registry/schema_id_validation.h"
31+
#include "security/config.h"
3132
#include "utils/unresolved_address.h"
3233

3334
#include <seastar/core/sstring.hh>
@@ -722,6 +723,9 @@ struct configuration final : public config_store {
722723
property<std::chrono::seconds> oidc_clock_skew_tolerance;
723724
property<ss::sstring> oidc_principal_mapping;
724725
property<std::chrono::seconds> oidc_keys_refresh_interval;
726+
property<ss::sstring> oidc_group_claim_path;
727+
728+
enum_property<security::oidc::nested_group_behavior> nested_group_behavior;
725729

726730
// HTTP Authentication
727731
enterprise<property<std::vector<ss::sstring>>> http_authentication;

src/v/config/convert.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "model/metadata.h"
1919
#include "model/timestamp.h"
2020
#include "pandaproxy/schema_registry/schema_id_validation.h"
21+
#include "security/config.h"
2122
#include "strings/string_switch.h"
2223

2324
#include <boost/lexical_cast.hpp>
@@ -762,4 +763,24 @@ struct convert<model::kafka_batch_validation_mode> {
762763
}
763764
};
764765

766+
template<>
767+
struct convert<security::oidc::nested_group_behavior> {
768+
static Node encode(const security::oidc::nested_group_behavior& rhs) {
769+
return Node(fmt::format("{}", rhs));
770+
}
771+
772+
static bool
773+
decode(const Node& node, security::oidc::nested_group_behavior& rhs) {
774+
static constexpr auto acceptable_values
775+
= security::oidc::acceptable_nested_group_behavior_values();
776+
auto value = node.as<std::string>();
777+
if (!std::ranges::contains(acceptable_values, value)) {
778+
return false;
779+
}
780+
std::istringstream iss(value);
781+
iss >> rhs;
782+
return true;
783+
}
784+
};
785+
765786
} // namespace YAML

src/v/config/property.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,10 @@ consteval std::string_view property_type_name() {
713713
type,
714714
model::kafka_batch_validation_mode>) {
715715
return "string";
716+
} else if constexpr (std::is_same_v<
717+
type,
718+
security::oidc::nested_group_behavior>) {
719+
return "string";
716720
} else {
717721
static_assert(
718722
base::unsupported_type<T>::value, "Type name not defined");

src/v/config/rjson_serialization.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,10 @@ void rjson_serialize(
315315
stringize(w, m);
316316
}
317317

318+
void rjson_serialize(
319+
json::Writer<json::StringBuffer>& w,
320+
security::oidc::nested_group_behavior ngb) {
321+
stringize(w, ngb);
322+
}
323+
318324
} // namespace json

src/v/config/rjson_serialization.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,7 @@ void rjson_serialize(
154154
void rjson_serialize(
155155
json::Writer<json::StringBuffer>&, const model::kafka_batch_validation_mode&);
156156

157+
void rjson_serialize(
158+
json::Writer<json::StringBuffer>&, security::oidc::nested_group_behavior);
159+
157160
} // namespace json

src/v/config/types.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,5 +256,4 @@ static constexpr auto acceptable_audit_log_failure_policy_values() {
256256

257257
std::ostream& operator<<(std::ostream&, audit_failure_policy);
258258
std::istream& operator>>(std::istream&, audit_failure_policy&);
259-
260259
} // namespace config

src/v/security/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ redpanda_cc_library(
1818
"oidc_principal_mapping.h",
1919
"oidc_url_parser.h",
2020
],
21+
implementation_deps = [
22+
"//src/v/strings:string_switch",
23+
],
2124
visibility = ["//visibility:public"],
2225
deps = [
2326
"//src/v/base",

src/v/security/config.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,39 @@ validate_rules(const std::optional<std::vector<ss::sstring>>& r) noexcept;
3333

3434
namespace security::oidc {
3535

36+
/// \brief Defines behavior for nested groups in group claim
37+
enum class nested_group_behavior : uint8_t {
38+
/// No special handling for nested groups
39+
none,
40+
/// Uses suffix of group claim, so `/a/b/c` becomes `c`
41+
suffix
42+
};
43+
44+
constexpr std::string_view to_string_view(nested_group_behavior b) {
45+
switch (b) {
46+
case nested_group_behavior::none:
47+
return "none";
48+
case nested_group_behavior::suffix:
49+
return "suffix";
50+
}
51+
return "unknown";
52+
}
53+
54+
static constexpr auto acceptable_nested_group_behavior_values() {
55+
return std::to_array(
56+
{to_string_view(nested_group_behavior::none),
57+
to_string_view(nested_group_behavior::suffix)});
58+
}
59+
60+
std::ostream& operator<<(std::ostream& os, nested_group_behavior b);
61+
std::istream& operator>>(std::istream& is, nested_group_behavior& b);
62+
3663
std::optional<ss::sstring>
3764
validate_principal_mapping_rule(const ss::sstring& rule);
3865

39-
}
66+
/// \brief Validates the path to the group claim in the OIDC JWT.
67+
///
68+
/// \returns std::nullopt on no error, ss::sstring with error message
69+
std::optional<ss::sstring> validate_group_claim_path(const ss::sstring& path);
70+
71+
} // namespace security::oidc

0 commit comments

Comments
 (0)