Skip to content

Add RoleClaimName to ConfigOptions (complement existing GroupClaimName) #4763

Description

@jhrozek

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

  • ConfigOptions struct has RoleClaimName string field with JSON tag "role_claim_name,omitempty" and YAML tag "role_claim_name,omitempty"
  • Existing GroupClaimName field is unchanged (already present from 5c258a1)
  • JSON unmarshalling of existing configs (without role_claim_name) produces empty string -- no behavioral change
  • JSON unmarshalling of configs with role_claim_name correctly populates the struct
  • The NewCedarAuthorizer function stores the new field value on the Authorizer struct for later use during request authorization
  • The Authorizer struct has a roleClaim string field (populated from ConfigOptions.RoleClaimName); verify that the existing groupClaim field (from 5c258a1) is also correctly wired
  • All existing tests pass without modification
  • New unit tests cover unmarshalling with and without the new field
  • Code reviewed and approved

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

  • TestConfigOptionsUnmarshalWithRoleClaim: JSON unmarshal of config with role_claim_name: "roles" populates the field correctly
  • TestConfigOptionsUnmarshalWithoutRoleClaim: JSON unmarshal of config without role_claim_name produces empty string (backward compat)
  • TestConfigOptionsMarshalOmitsEmptyRoleClaim: JSON marshal of ConfigOptions with empty RoleClaimName omits the field
  • TestNewCedarAuthorizerStoresRoleClaim: Construct authorizer with RoleClaimName: "roles" and verify the field is stored
  • TestNewCedarAuthorizerEmptyRoleClaim: Construct authorizer without RoleClaimName and verify empty string (backward compat)

Edge Cases

  • Empty string role claim name is treated as "no extraction" (not as a valid claim name)

Out of Scope

References

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions