Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestAuthorizeWithToolAnnotations(t *testing.T) {
authzr, err := NewCedarAuthorizer(ConfigOptions{
Policies: tc.policies,
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Build context with identity
Expand Down
2 changes: 1 addition & 1 deletion pkg/authz/authorizers/cedar/annotations_override_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func TestAnnotationAttributesCannotOverrideStandardAttributes(t *testing.T) {
authzr, err := NewCedarAuthorizer(ConfigOptions{
Policies: tc.policies,
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

ctx := t.Context()
Expand Down
17 changes: 14 additions & 3 deletions pkg/authz/authorizers/cedar/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (*Factory) ValidateConfig(rawConfig json.RawMessage) error {

// CreateAuthorizer creates a Cedar Authorizer from the configuration.
// It receives the full raw config and extracts the Cedar-specific portion.
func (*Factory) CreateAuthorizer(rawConfig json.RawMessage, _ string) (authorizers.Authorizer, error) {
func (*Factory) CreateAuthorizer(rawConfig json.RawMessage, serverName string) (authorizers.Authorizer, error) {
var config Config
if err := json.Unmarshal(rawConfig, &config); err != nil {
return nil, fmt.Errorf("failed to parse configuration: %w", err)
Expand All @@ -129,7 +129,7 @@ func (*Factory) CreateAuthorizer(rawConfig json.RawMessage, _ string) (authorize
return nil, fmt.Errorf("cedar configuration is required (missing 'cedar' field)")
}

return NewCedarAuthorizer(*config.Options)
return NewCedarAuthorizer(*config.Options, serverName)
}

// Common errors for Cedar authorization
Expand Down Expand Up @@ -167,6 +167,12 @@ type Authorizer struct {
// roleClaimName is the JWT claim key that contains role membership.
// When empty, no role extraction is performed (backward compatible).
roleClaimName string
// serverName is the identity of the MCP server this authorizer is scoped to.
// Used by downstream enterprise features for server-scoped Cedar policies
// (e.g. resource in MCP::"<server>"). When empty (standalone Cedar usage
// with no enterprise controller), the authorizer behaves identically to
// the unscoped case.
serverName string
// claimKeyLog rate-limits the diagnostic log of resolved JWT claim keys
// so it emits at most once per 30 seconds instead of once per authorization check.
claimKeyLog *syncutil.AtMost
Expand Down Expand Up @@ -201,14 +207,19 @@ type ConfigOptions struct {
}

// NewCedarAuthorizer creates a new Cedar authorizer.
func NewCedarAuthorizer(options ConfigOptions) (authorizers.Authorizer, error) {
// serverName is a runtime-injected value (not user-authored config) that
// identifies which MCP server this authorizer is scoped to.
// If a second runtime-injected value is needed, bundle both into a
// RuntimeContext struct to keep the factory interface stable.
func NewCedarAuthorizer(options ConfigOptions, serverName string) (authorizers.Authorizer, error) {
authorizer := &Authorizer{
policySet: cedar.NewPolicySet(),
entities: cedar.EntityMap{},
entityFactory: NewEntityFactory(),
primaryUpstreamProvider: options.PrimaryUpstreamProvider,
groupClaimName: options.GroupClaimName,
roleClaimName: options.RoleClaimName,
serverName: serverName,
claimKeyLog: syncutil.NewAtMost(30 * time.Second),
}

Expand Down
43 changes: 29 additions & 14 deletions pkg/authz/authorizers/cedar/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ func TestNewCedarAuthorizer(t *testing.T) {
policies []string
entitiesJSON string
roleClaimName string
serverName string
expectError bool
errorType error
wantRoleClaimName string
wantServerName string
}{
{
name: "Valid policy and empty entities",
Expand Down Expand Up @@ -94,6 +96,14 @@ func TestNewCedarAuthorizer(t *testing.T) {
expectError: false,
wantRoleClaimName: "https://example.com/roles",
},
{
name: "Stores server name",
policies: []string{`permit(principal, action, resource);`},
entitiesJSON: `[]`,
serverName: "my-mcp-server",
expectError: false,
wantServerName: "my-mcp-server",
},
}

// Run test cases
Expand All @@ -105,7 +115,7 @@ func TestNewCedarAuthorizer(t *testing.T) {
Policies: tc.policies,
EntitiesJSON: tc.entitiesJSON,
RoleClaimName: tc.roleClaimName,
})
}, tc.serverName)

// Check error expectations
if tc.expectError {
Expand All @@ -121,6 +131,7 @@ func TestNewCedarAuthorizer(t *testing.T) {
cedarAuthz, ok := authorizer.(*Authorizer)
require.True(t, ok)
assert.Equal(t, tc.wantRoleClaimName, cedarAuthz.roleClaimName)
assert.Equal(t, tc.wantServerName, cedarAuthz.serverName)
}
})
}
Expand Down Expand Up @@ -351,7 +362,7 @@ func TestAuthorizeWithJWTClaims(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{tc.policy},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Create a context with JWT claims
Expand All @@ -376,7 +387,7 @@ func TestAuthorizeWithJWTClaimsErrors(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{`permit(principal, action, resource);`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Test cases
Expand Down Expand Up @@ -675,6 +686,10 @@ func TestFactoryCreateAuthorizer(t *testing.T) {

require.NoError(t, err)
require.NotNil(t, authorizer)

cedarAuthz, ok := authorizer.(*Authorizer)
require.True(t, ok)
assert.Equal(t, "testServer", cedarAuthz.serverName)
})
}
}
Expand All @@ -687,7 +702,7 @@ func TestUpdatePolicies(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{`permit(principal, action, resource);`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

// Cast to concrete type to access UpdatePolicies
Expand Down Expand Up @@ -748,7 +763,7 @@ func TestUpdateEntities(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{`permit(principal, action, resource);`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

// Cast to concrete type to access UpdateEntities
Expand Down Expand Up @@ -799,7 +814,7 @@ func TestEntityOperations(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{`permit(principal, action, resource);`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

// Cast to concrete type to access entity methods
Expand Down Expand Up @@ -839,7 +854,7 @@ func TestGetEntityNotFound(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{`permit(principal, action, resource);`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

// Cast to concrete type
Expand All @@ -863,7 +878,7 @@ func TestIsAuthorizedErrors(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{`permit(principal, action, resource);`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

// Cast to concrete type
Expand Down Expand Up @@ -958,7 +973,7 @@ func TestIsAuthorizedWithEntities(t *testing.T) {
);
`},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

// Cast to concrete type
Expand Down Expand Up @@ -1097,7 +1112,7 @@ func TestAuthorizeWithJWTClaims_UpstreamProvider(t *testing.T) {
Policies: []string{policy},
EntitiesJSON: `[]`,
PrimaryUpstreamProvider: providerName,
})
}, "")
require.NoError(t, err)

upstreamToken := makeUnsignedJWT(jwt.MapClaims{
Expand Down Expand Up @@ -1242,7 +1257,7 @@ func TestAuthorizeWithJWTClaims_GroupMembership(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{policy},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

tests := []struct {
Expand Down Expand Up @@ -1313,7 +1328,7 @@ func TestAuthorizeWithJWTClaims_DoesNotMutateIdentity(t *testing.T) {
authorizer, err := NewCedarAuthorizer(ConfigOptions{
Policies: []string{policy},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

identity := &auth.Identity{
Expand Down Expand Up @@ -1361,7 +1376,7 @@ func TestAuthorizeWithJWTClaims_CustomGroupClaimName(t *testing.T) {
Policies: []string{policy},
EntitiesJSON: `[]`,
GroupClaimName: "https://example.com/groups",
})
}, "")
require.NoError(t, err)

// The custom claim holds "platform"; the well-known "groups" key holds other groups.
Expand Down Expand Up @@ -1410,7 +1425,7 @@ func TestAuthorizeWithJWTClaims_UpstreamProviderWithGroups(t *testing.T) {
Policies: []string{policy},
EntitiesJSON: `[]`,
PrimaryUpstreamProvider: providerName,
})
}, "")
require.NoError(t, err)

tests := []struct {
Expand Down
4 changes: 2 additions & 2 deletions pkg/authz/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestIntegrationListFiltering(t *testing.T) {
`permit(principal, action == Action::"read_resource", resource) when { principal.claim_role == "admin" };`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

testCases := []struct {
Expand Down Expand Up @@ -335,7 +335,7 @@ func TestIntegrationNonListOperations(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource) when { principal.claim_role == "admin" };`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

testCases := []struct {
Expand Down
12 changes: 6 additions & 6 deletions pkg/authz/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestMiddleware(t *testing.T) {
`permit(principal, action == Action::"read_resource", resource == Resource::"data");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Test cases
Expand Down Expand Up @@ -443,7 +443,7 @@ func TestMiddlewareWithGETRequest(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Create a handler that records if it was called
Expand Down Expand Up @@ -808,7 +808,7 @@ func TestMiddlewareToolsListTestkit(t *testing.T) {
cedar.ConfigOptions{
Policies: tc.policies,
EntitiesJSON: `[]`,
},
}, "",
)
require.NoError(t, err, "Failed to create Cedar authorizer")

Expand Down Expand Up @@ -978,7 +978,7 @@ func TestMiddlewareToolsCallTestkit(t *testing.T) {
cedar.ConfigOptions{
Policies: tc.policies,
EntitiesJSON: `[]`,
},
}, "",
)
require.NoError(t, err, "Failed to create Cedar authorizer")

Expand Down Expand Up @@ -1049,7 +1049,7 @@ func TestMiddlewareOptimizerMetaTools(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"allowed_backend");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

passThroughTools := map[string]struct{}{
Expand Down Expand Up @@ -1150,7 +1150,7 @@ func TestMiddlewareOptimizerCallToolJSONRoundTrip(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"backend_fetch");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

passThroughTools := map[string]struct{}{
Expand Down
12 changes: 6 additions & 6 deletions pkg/authz/response_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestFindToolResponseFilter(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

identity := &auth.Identity{PrincipalInfo: auth.PrincipalInfo{
Expand Down Expand Up @@ -239,7 +239,7 @@ func TestResponseFilteringWriter(t *testing.T) {
`permit(principal, action == Action::"read_resource", resource == Resource::"data");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

testCases := []struct {
Expand Down Expand Up @@ -432,7 +432,7 @@ func TestResponseFilteringWriter_NonListOperations(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Test that non-list operations pass through unchanged
Expand Down Expand Up @@ -481,7 +481,7 @@ func TestResponseFilteringWriter_ErrorResponse(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Create an error response
Expand Down Expand Up @@ -534,7 +534,7 @@ func TestResponseFilteringWriter_ContentLengthMismatch(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err, "Failed to create Cedar authorizer")

// Build the backend response: a tools/list result with 3 tools.
Expand Down Expand Up @@ -727,7 +727,7 @@ func TestOptimizerPassThroughToolsInResponseFilter(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: "[]",
})
}, "")
require.NoError(t, err)

// Build a tools/list response as the optimizer would produce it:
Expand Down
6 changes: 3 additions & 3 deletions pkg/authz/tool_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func TestFilterToolsByPolicy_WithCedarAuthorizer(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

t.Run("keeps only permitted tool", func(t *testing.T) {
Expand Down Expand Up @@ -222,7 +222,7 @@ func TestAuthorizeToolCall_WithCedarAuthorizer(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"weather");`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

t.Run("permits authorized tool", func(t *testing.T) {
Expand Down Expand Up @@ -252,7 +252,7 @@ func TestAuthorizeToolCall_WithArguments(t *testing.T) {
`permit(principal, action == Action::"call_tool", resource == Tool::"deploy") when { context.arg_mode == "safe" };`,
},
EntitiesJSON: `[]`,
})
}, "")
require.NoError(t, err)

t.Run("permits when arguments satisfy policy", func(t *testing.T) {
Expand Down
Loading