-
Notifications
You must be signed in to change notification settings - Fork 2
Distinguish role assignment from unassignment in the privilege event filter #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -264,9 +264,35 @@ var ( | |
| }, | ||
| } | ||
| RoleMembershipFilter = EventFilter{ | ||
| // Okta reports both assignment and unassignment of standard admin roles under | ||
| // this one event type, distinguishing them with an extra ROLE_ASSIGNED or | ||
| // ROLE_UNASSIGNED target, so an unassignment has to emit a revoke. Custom role | ||
| // bindings (CUSTOM_ROLE_BINDING_ADDED / _REMOVED) reuse the event type as well | ||
| // but carry no ROLE target and belong to the custom-role resource type, so they | ||
| // are skipped here. | ||
| // | ||
| // user.account.privilege.revoke covers only the removal of a user's last role; | ||
| // RoleMembershipRevokeFilter handles that case. | ||
| EventTypes: mapset.NewSet("user.account.privilege.grant"), | ||
| TargetTypes: mapset.NewSet("ROLE", oktaLogTargetTypeUser), | ||
| EventHandler: func(_ *zap.Logger, event *oktaSDK.LogEvent, targetMap map[string][]*oktaSDK.LogTarget, rv *v2.Event) error { | ||
| EventHandler: func(l *zap.Logger, event *oktaSDK.LogEvent, targetMap map[string][]*oktaSDK.LogTarget, rv *v2.Event) error { | ||
| assigned := len(targetMap[oktaLogTargetRoleAssigned]) > 0 | ||
| unassigned := len(targetMap[oktaLogTargetRoleUnassigned]) > 0 | ||
|
|
||
| switch { | ||
| case assigned && unassigned: | ||
| return fmt.Errorf("okta-connectorv2: event has both %s and %s targets", oktaLogTargetRoleAssigned, oktaLogTargetRoleUnassigned) | ||
| case !assigned && !unassigned: | ||
| // Custom role bindings land here, as would any discriminator Okta adds | ||
| // later. Defaulting to a grant would reaffirm access that may have just | ||
| // been removed, so skip rather than guess. | ||
| l.Debug("okta-event-feed: RoleMembershipFilter: no role assignment discriminator, skipping", | ||
| zap.String("event_type", event.EventType), | ||
| zap.String("event_id", event.Uuid), | ||
| ) | ||
| return nil | ||
| } | ||
|
|
||
| if len(targetMap["ROLE"]) != 1 { | ||
| return fmt.Errorf("okta-connectorv2: expected 1 ROLE target, got %d", len(targetMap["ROLE"])) | ||
| } | ||
|
|
@@ -277,11 +303,18 @@ var ( | |
| } | ||
| user := targetMap[oktaLogTargetTypeUser][0] | ||
|
|
||
| // for some reason we don't get the role ID (or type) formatted properly. | ||
| // hack to look it up via DisplayName | ||
| // The ROLE target's id is a camelCase name ("UserAdmin") and its alternateId | ||
| // is an assignment identifier, so neither is the role type the sync uses as a | ||
| // resource ID. Look it up by label instead. | ||
| roleType := StandardRoleTypeFromLabel(role.DisplayName) | ||
| if roleType == nil { | ||
| return fmt.Errorf("okta-connectorv2: error getting role from label: %s", role.DisplayName) | ||
| // Expected for custom roles and for any label missing from | ||
| // standardRoleTypes; there is no resource to point an event at. | ||
| l.Debug("okta-event-feed: RoleMembershipFilter: no standard role for label, skipping", | ||
| zap.String("role_display_name", role.DisplayName), | ||
| zap.String("event_id", event.Uuid), | ||
| ) | ||
| return nil | ||
| } | ||
|
Comment on lines
310
to
318
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: |
||
|
|
||
| roleResource, err := sdkResource.NewResource(role.DisplayName, resourceTypeRole, roleType.Type) | ||
|
|
@@ -300,12 +333,31 @@ var ( | |
| } | ||
| principal.Annotations = annotations.New(userTrait) | ||
|
|
||
| rv.Event = &v2.Event_CreateGrantEvent{ | ||
| CreateGrantEvent: &v2.CreateGrantEvent{ | ||
| Entitlement: sdkEntitlement.NewAssignmentEntitlement(roleResource, "assigned"), | ||
| Principal: principal, | ||
| }, | ||
| entitlement := sdkEntitlement.NewAssignmentEntitlement(roleResource, "assigned") | ||
| if assigned { | ||
| rv.Event = &v2.Event_CreateGrantEvent{ | ||
| CreateGrantEvent: &v2.CreateGrantEvent{ | ||
| Entitlement: entitlement, | ||
| Principal: principal, | ||
| }, | ||
| } | ||
| } else { | ||
| rv.Event = &v2.Event_CreateRevokeEvent{ | ||
| CreateRevokeEvent: &v2.CreateRevokeEvent{ | ||
| Entitlement: entitlement, | ||
| Principal: principal, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| l.Debug("okta-event-feed: RoleMembershipFilter", | ||
| zap.String("event_type", event.EventType), | ||
| zap.Bool("assigned", assigned), | ||
| zap.String("resource_type", resourceTypeRole.Id), | ||
| zap.String("resource_id", roleType.Type), | ||
| zap.String("role_display_name", role.DisplayName), | ||
| zap.String("user_id", user.Id), | ||
| ) | ||
| return nil | ||
| }, | ||
| } | ||
|
|
@@ -327,7 +379,11 @@ var ( | |
| // role ID or type, so resolve the standard role by its label. | ||
| roleType := StandardRoleTypeFromLabel(role.DisplayName) | ||
| if roleType == nil { | ||
| return fmt.Errorf("okta-connectorv2: error getting role from label: %s", role.DisplayName) | ||
| l.Debug("okta-event-feed: RoleMembershipRevokeFilter: no standard role for label, skipping", | ||
| zap.String("role_display_name", role.DisplayName), | ||
| zap.String("event_id", event.Uuid), | ||
| ) | ||
| return nil | ||
| } | ||
|
|
||
| roleResource, err := sdkResource.NewResource(role.DisplayName, resourceTypeRole, roleType.Type) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: This branch conflates two cases the code can already tell apart. A custom-role binding carries no
ROLEtarget and is genuinely expected; an event that does carry aROLEtarget but no discriminator is a standard-role change being silently dropped (the legacy pre-2024 shape the PR description cites, or a future Okta discriminator). Skipping is the right call for both, but branching onlen(targetMap["ROLE"]) > 0and logging the second atWarnwould give an operator a signal if a tenant systematically emits no discriminator and role grants quietly stop flowing.