Skip to content

Store serverName on Authorizer and update NewCedarAuthorizer #4764

@jhrozek

Description

@jhrozek

Part of stacklok/stacklok-enterprise-platform#stacklok/stacklok-enterprise-platform#376

Description

Store the serverName parameter on the Cedar Authorizer struct and update the NewCedarAuthorizer constructor to accept it. The AuthorizerFactory.CreateAuthorizer interface already passes serverName to the Cedar factory, but the Cedar implementation discards it with _ string. This task makes the authorizer retain the value so that downstream changes (server-scoped resource entities, MCP-based list operations, group extraction) can use it.

This is a prerequisite for server-scoped authorization in the enterprise platform. Without serverName stored on the authorizer, compiled Cedar policies that use resource in MCP::"<server>" cannot be evaluated correctly.

Context

The Platform Authorization design extends the existing cedarv1 authorizer with optional enterprise capabilities. Several downstream OSS changes (#4768, #4769, #4770) depend on serverName being available on the Authorizer struct. Currently the factory discards it, so the struct has no way to reference the server identity at authorization time.

The change is backward-compatible: when serverName is empty (standalone Cedar usage with no enterprise controller), the authorizer behaves identically to today.

Dependencies: None (can start immediately)
Blocks: #4768 (group/role extraction), #4769 (MCP parent on resources), #4770 (FeatureType to MCP for list ops)

Acceptance Criteria

  • Authorizer struct has a serverName string field
  • NewCedarAuthorizer accepts serverName string as a second parameter
  • CreateAuthorizer factory passes serverName through to NewCedarAuthorizer instead of discarding with _
  • All 19 test call sites of NewCedarAuthorizer across 4 test files are updated to pass "" as the serverName argument
  • The CreateAuthorizer factory call on line 98 of core.go passes serverName to NewCedarAuthorizer
  • Existing tests pass with no behavior change (empty serverName preserves identical behavior)
  • New test verifies that serverName is stored and accessible on the authorizer

Technical Approach

Recommended Implementation

Three changes in pkg/authz/authorizers/cedar/core.go:

  1. Add serverName field to Authorizer struct (line 116):
type Authorizer struct {
    policySet     *cedar.PolicySet
    entities      cedar.EntityMap
    entityFactory *EntityFactory
    serverName    string       // NEW: server identity for scoped policies
    mu            sync.RWMutex
}
  1. Update NewCedarAuthorizer signature (line 137):
// Current
func NewCedarAuthorizer(options ConfigOptions) (authorizers.Authorizer, error)

// Proposed
func NewCedarAuthorizer(options ConfigOptions, serverName string) (authorizers.Authorizer, error)

Store serverName in the Authorizer struct during construction:

func NewCedarAuthorizer(options ConfigOptions, serverName string) (authorizers.Authorizer, error) {
    authorizer := &Authorizer{
        policySet:     cedar.NewPolicySet(),
        entities:      cedar.EntityMap{},
        entityFactory: NewEntityFactory(),
        serverName:    serverName,
    }
    // ... rest unchanged ...
}
  1. Update CreateAuthorizer factory (line 88):
// Current
func (*Factory) CreateAuthorizer(rawConfig json.RawMessage, _ string) (authorizers.Authorizer, error) {
    // ...
    return NewCedarAuthorizer(*config.Options)
}

// Proposed
func (*Factory) CreateAuthorizer(rawConfig json.RawMessage, serverName string) (authorizers.Authorizer, error) {
    // ...
    return NewCedarAuthorizer(*config.Options, serverName)
}
  1. Mechanical test updates: Add "" as the second argument to all 19 NewCedarAuthorizer(...) call sites in test files. The empty string preserves identical behavior for every existing test.

Patterns & Frameworks

  • Follow existing constructor pattern in core.go (functional struct initialization)
  • Table-driven tests with testify assertions per project conventions
  • t.Parallel() on tests with no shared mutable state

Code Pointers

  • pkg/authz/authorizers/cedar/core.go lines 86-99 -- CreateAuthorizer factory method; currently discards serverName with _ string; needs to accept and pass through
  • pkg/authz/authorizers/cedar/core.go lines 115-125 -- Authorizer struct definition; needs new serverName field
  • pkg/authz/authorizers/cedar/core.go lines 136-167 -- NewCedarAuthorizer constructor; needs serverName parameter and storage
  • pkg/authz/authorizers/registry.go lines 16-24 -- AuthorizerFactory interface definition; already passes serverName to CreateAuthorizer
  • pkg/authz/authorizers/cedar/core_test.go -- 10 call sites to update (lines 74, 316, 341, 652, 713, 764, 804, 828, 917 use NewCedarAuthorizer(ConfigOptions{...}))
  • pkg/authz/middleware_test.go -- 4 call sites (lines 33, 417, 783, 953)
  • pkg/authz/response_filter_test.go -- 3 call sites (lines 26, 221, 270)
  • pkg/authz/integration_test.go -- 2 call sites (lines 30, 332)

Component Interfaces

The function signature change:

// Before
func NewCedarAuthorizer(options ConfigOptions) (authorizers.Authorizer, error)

// After
func NewCedarAuthorizer(options ConfigOptions, serverName string) (authorizers.Authorizer, error)

The factory signature is unchanged (it already accepts serverName):

// AuthorizerFactory interface (unchanged)
CreateAuthorizer(rawConfig json.RawMessage, serverName string) (Authorizer, error)

Testing Strategy

Unit Tests

  • Test that NewCedarAuthorizer with non-empty serverName stores the value on the struct (cast to *Authorizer and verify serverName field)
  • Test that NewCedarAuthorizer with empty serverName stores empty string (backward compat)
  • Test that CreateAuthorizer factory passes serverName through (create via factory with "my-server", cast result, verify field)

Existing Test Validation

  • All 10 call sites in core_test.go compile and pass with "" added
  • All 4 call sites in middleware_test.go compile and pass with "" added
  • All 3 call sites in response_filter_test.go compile and pass with "" added
  • All 2 call sites in integration_test.go compile and pass with "" added

Edge Cases

  • NewCedarAuthorizer with serverName containing special characters (e.g., "my-server.example.com") stores correctly -- no sanitization expected at this layer

Out of Scope

References

  • pkg/authz/authorizers/registry.go -- AuthorizerFactory interface already defines serverName parameter

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions