Part of stacklok/stacklok-enterprise-platform#stacklok/stacklok-enterprise-platform#376
Description
Add RoleClaimName to the Cedar ConfigOptions struct so that the enterprise authorization controller can tell the OSS authorizer which JWT claim contains role membership, complementing the existing GroupClaimName (added in upstream commit 5c258a1). When empty (the default), no role extraction occurs -- backward compatible.
The dual-claim model (group_claim_name + role_claim_name) supports IdPs that separate group and role concepts (e.g., Entra ID groups vs roles claims). Both claims produce the same Cedar entity type (THVGroup); values are unioned and deduplicated at extraction time (#4768).
Context
The GroupClaimName field (json: group_claim_name) was added to ConfigOptions in upstream commit 5c258a1 ("Enforce Cedar policies on upstream IDP token claims"). This task adds the complementary RoleClaimName field (json: role_claim_name) to complete the dual-claim model described in the RFC's OSS Changes table (Change #1, status: Partial).
The enterprise authorization controller writes a ConfigMap containing both claim names alongside policies and entities_json. The OSS authorizer reads them at startup to know where in the JWT to look for group and role membership.
This change is standalone with no code dependencies on other items. It is one of the first three parallelizable tasks (along with #4764 and #4765) in the IdP Group Claim Extraction story.
Dependencies: None
Blocks: #4768 (group/role extraction and Client parents)
Acceptance Criteria
Technical Approach
Recommended Implementation
Add one new string field RoleClaimName to ConfigOptions using omitempty JSON/YAML tags, following the same pattern as the existing GroupClaimName. Store the value on the Authorizer struct (as roleClaim) during construction in NewCedarAuthorizer. The value will be consumed later by #4768 (group extraction), but this change only adds the config plumbing.
Patterns & Frameworks
- Follow the existing
GroupClaimName field pattern added in 5c258a1: exported Go field name, json and yaml struct tags with omitempty
- The
omitempty tag ensures backward compatibility -- missing fields unmarshal to empty strings
- Cedar authorizer uses
log/slog for logging; no logging needed for this change
- Test style: table-driven with
testify/assert and testify/require, t.Parallel() on independent subtests
Code Pointers
pkg/authz/authorizers/cedar/core.go -- ConfigOptions struct: has existing GroupClaimName field; add RoleClaimName alongside it
pkg/authz/authorizers/cedar/core.go -- Authorizer struct: has existing groupClaimName field (from 5c258a1); add roleClaim field
pkg/authz/authorizers/cedar/core.go -- NewCedarAuthorizer function: already stores options.GroupClaimName; add roleClaim: options.RoleClaimName
pkg/authz/authorizers/cedar/core_test.go -- Existing tests that construct ConfigOptions; these remain valid since the new field is optional
Component Interfaces
// ConfigOptions represents the Cedar-specific authorization configuration options.
type ConfigOptions struct {
// ... existing fields (Policies, EntitiesJSON, GroupClaimName) ...
// RoleClaimName is the JWT claim name containing role membership.
// Both group and role claims produce the same Cedar entity type (THVGroup).
// When empty, no role extraction is performed (backward compatible).
RoleClaimName string `json:"role_claim_name,omitempty" yaml:"role_claim_name,omitempty"`
}
Testing Strategy
Unit Tests
Edge Cases
Out of Scope
References
Part of stacklok/stacklok-enterprise-platform#stacklok/stacklok-enterprise-platform#376
Description
Add
RoleClaimNameto the CedarConfigOptionsstruct so that the enterprise authorization controller can tell the OSS authorizer which JWT claim contains role membership, complementing the existingGroupClaimName(added in upstream commit 5c258a1). When empty (the default), no role extraction occurs -- backward compatible.The dual-claim model (
group_claim_name+role_claim_name) supports IdPs that separate group and role concepts (e.g., Entra IDgroupsvsrolesclaims). Both claims produce the same Cedar entity type (THVGroup); values are unioned and deduplicated at extraction time (#4768).Context
The
GroupClaimNamefield (json:group_claim_name) was added toConfigOptionsin upstream commit 5c258a1 ("Enforce Cedar policies on upstream IDP token claims"). This task adds the complementaryRoleClaimNamefield (json:role_claim_name) to complete the dual-claim model described in the RFC's OSS Changes table (Change #1, status: Partial).The enterprise authorization controller writes a ConfigMap containing both claim names alongside
policiesandentities_json. The OSS authorizer reads them at startup to know where in the JWT to look for group and role membership.This change is standalone with no code dependencies on other items. It is one of the first three parallelizable tasks (along with #4764 and #4765) in the IdP Group Claim Extraction story.
Dependencies: None
Blocks: #4768 (group/role extraction and Client parents)
Acceptance Criteria
ConfigOptionsstruct hasRoleClaimName stringfield with JSON tag"role_claim_name,omitempty"and YAML tag"role_claim_name,omitempty"GroupClaimNamefield is unchanged (already present from 5c258a1)role_claim_name) produces empty string -- no behavioral changerole_claim_namecorrectly populates the structNewCedarAuthorizerfunction stores the new field value on theAuthorizerstruct for later use during request authorizationAuthorizerstruct has aroleClaim stringfield (populated fromConfigOptions.RoleClaimName); verify that the existinggroupClaimfield (from 5c258a1) is also correctly wiredTechnical Approach
Recommended Implementation
Add one new string field
RoleClaimNametoConfigOptionsusingomitemptyJSON/YAML tags, following the same pattern as the existingGroupClaimName. Store the value on theAuthorizerstruct (asroleClaim) during construction inNewCedarAuthorizer. The value will be consumed later by #4768 (group extraction), but this change only adds the config plumbing.Patterns & Frameworks
GroupClaimNamefield pattern added in 5c258a1: exported Go field name,jsonandyamlstruct tags withomitemptyomitemptytag ensures backward compatibility -- missing fields unmarshal to empty stringslog/slogfor logging; no logging needed for this changetestify/assertandtestify/require,t.Parallel()on independent subtestsCode Pointers
pkg/authz/authorizers/cedar/core.go--ConfigOptionsstruct: has existingGroupClaimNamefield; addRoleClaimNamealongside itpkg/authz/authorizers/cedar/core.go--Authorizerstruct: has existinggroupClaimNamefield (from 5c258a1); addroleClaimfieldpkg/authz/authorizers/cedar/core.go--NewCedarAuthorizerfunction: already storesoptions.GroupClaimName; addroleClaim: options.RoleClaimNamepkg/authz/authorizers/cedar/core_test.go-- Existing tests that constructConfigOptions; these remain valid since the new field is optionalComponent Interfaces
Testing Strategy
Unit Tests
TestConfigOptionsUnmarshalWithRoleClaim: JSON unmarshal of config withrole_claim_name: "roles"populates the field correctlyTestConfigOptionsUnmarshalWithoutRoleClaim: JSON unmarshal of config withoutrole_claim_nameproduces empty string (backward compat)TestConfigOptionsMarshalOmitsEmptyRoleClaim: JSON marshal ofConfigOptionswith emptyRoleClaimNameomits the fieldTestNewCedarAuthorizerStoresRoleClaim: Construct authorizer withRoleClaimName: "roles"and verify the field is storedTestNewCedarAuthorizerEmptyRoleClaim: Construct authorizer withoutRoleClaimNameand verify empty string (backward compat)Edge Cases
Out of Scope
serverNameon the Authorizer (Store serverName on Authorizer and update NewCedarAuthorizer #4764)NewCedarAuthorizerfunction signature (that is part of Store serverName on Authorizer and update NewCedarAuthorizer #4764 which adds theserverNameparameter)AuthorizeWithJWTClaimsor theauthorize*methodsGroupClaimNamefield -- it is already correctReferences
GroupClaimName+RoleClaimName)GroupClaimName