# Get access rules Source: https://docs.opal.dev/api-reference/access-rules/get-access-rules https://app.opal.dev/openapi.yaml get /access-rules Returns a list of access rules for your organization. # Get access rules 1 Source: https://docs.opal.dev/api-reference/access-rules/get-access-rules-1 https://app.opal.dev/openapi.yaml get /access-rules/{access_rule_id} Returns a list of access rule config given the group_id of the access rule. # Post access rules Source: https://docs.opal.dev/api-reference/access-rules/post-access-rules https://app.opal.dev/openapi.yaml post /access-rules Creates a new access rule config for the given group_id. # Put access rules Source: https://docs.opal.dev/api-reference/access-rules/put-access-rules https://app.opal.dev/openapi.yaml put /access-rules/{access_rule_id} Updates the access rule config for the given group_id. # Get app by ID Source: https://docs.opal.dev/api-reference/apps/get-app-by-id https://app.opal.dev/openapi.yaml get /apps/{app_id} Returns an `App` object. # Get apps Source: https://docs.opal.dev/api-reference/apps/get-apps https://app.opal.dev/openapi.yaml get /apps Returns a list of `App` objects. # Get sync errors Source: https://docs.opal.dev/api-reference/apps/get-sync_errors https://app.opal.dev/openapi.yaml get /sync_errors Returns a list of recent sync errors that have occurred since the last successful sync. # Delete bundles Source: https://docs.opal.dev/api-reference/bundles/delete-bundles https://app.opal.dev/openapi.yaml delete /bundles/{bundle_id} Deletes a bundle. # Delete bundles groups Source: https://docs.opal.dev/api-reference/bundles/delete-bundles-groups https://app.opal.dev/openapi.yaml delete /bundles/{bundle_id}/groups/{group_id} Removes a group from a bundle. # Delete bundles resources Source: https://docs.opal.dev/api-reference/bundles/delete-bundles-resources https://app.opal.dev/openapi.yaml delete /bundles/{bundle_id}/resources/{resource_id} Removes a resource from a bundle. # Get bundle by ID Source: https://docs.opal.dev/api-reference/bundles/get-bundle-by-id https://app.opal.dev/openapi.yaml get /bundles/{bundle_id} Returns a `Bundle` object. # Get bundles Source: https://docs.opal.dev/api-reference/bundles/get-bundles https://app.opal.dev/openapi.yaml get /bundles Returns a list of `Bundle` objects. # Get bundles groups Source: https://docs.opal.dev/api-reference/bundles/get-bundles-groups https://app.opal.dev/openapi.yaml get /bundles/{bundle_id}/groups Returns a list of `Group` objects in a given bundle. # Get bundles resources Source: https://docs.opal.dev/api-reference/bundles/get-bundles-resources https://app.opal.dev/openapi.yaml get /bundles/{bundle_id}/resources Returns a list of `Resource` objects in a given bundle. # Get bundles visibility Source: https://docs.opal.dev/api-reference/bundles/get-bundles-visibility https://app.opal.dev/openapi.yaml get /bundles/{bundle_id}/visibility Gets the visibility of the bundle. # Post bundles Source: https://docs.opal.dev/api-reference/bundles/post-bundles https://app.opal.dev/openapi.yaml post /bundles Creates a bundle. # Post bundles groups Source: https://docs.opal.dev/api-reference/bundles/post-bundles-groups https://app.opal.dev/openapi.yaml post /bundles/{bundle_id}/groups Adds a group to a bundle. # Post bundles resources Source: https://docs.opal.dev/api-reference/bundles/post-bundles-resources https://app.opal.dev/openapi.yaml post /bundles/{bundle_id}/resources Adds a resource to a bundle. # Put bundles Source: https://docs.opal.dev/api-reference/bundles/put-bundles https://app.opal.dev/openapi.yaml put /bundles/{bundle_id} Updates a bundle. # Put bundles visibility Source: https://docs.opal.dev/api-reference/bundles/put-bundles-visibility https://app.opal.dev/openapi.yaml put /bundles/{bundle_id}/visibility Sets the visibility of the bundle. # End campaign Source: https://docs.opal.dev/api-reference/campaigns/end-campaign https://app.opal.dev/openapi.yaml post /campaigns/{campaign_id}/end Ends a stopped campaign, setting `ended_at` and `ended_by_user_id`, applying pending access changes, and queuing report generation. Returns 400 unless the campaign is started and stopped and not already ended. # Get campaign by ID Source: https://docs.opal.dev/api-reference/campaigns/get-campaign-by-id https://app.opal.dev/openapi.yaml get /campaigns/{campaign_id} Returns a `Campaign` object. # Get campaigns Source: https://docs.opal.dev/api-reference/campaigns/get-campaigns https://app.opal.dev/openapi.yaml get /campaigns Returns a list of `Campaign` objects. # List campaign items Source: https://docs.opal.dev/api-reference/campaigns/list-campaign-items https://app.opal.dev/openapi.yaml get /campaigns/{campaign_id}/items Returns a paginated list of review items for a campaign — the same rows shown on the admin Reviews tab. Status is derived using the same rules as the UI Status column. Soft-deleted role assignments are still returned with `is_target_deleted: true`. # List viewer campaign items Source: https://docs.opal.dev/api-reference/campaigns/list-viewer-campaign-items https://app.opal.dev/openapi.yaml get /campaigns/{campaign_id}/viewer-items Returns a paginated list of campaign review items assigned to the current authenticated viewer. Matches GraphQL `Campaign.viewerItems`. Campaign access matches `GET /campaigns/{campaign_id}` and GraphQL `campaign(id)`: the caller must have access-review read permission or be a reviewer on the campaign. Otherwise the request fails with 403 (same as get-by-id). Item rows are still scoped to the caller's own reviews (empty page when the caller is an admin but not a reviewer). # Post campaigns Source: https://docs.opal.dev/api-reference/campaigns/post-campaigns https://app.opal.dev/openapi.yaml post /campaigns Creates a campaign. Campaign scope only supports direct access edges: `configuration.query.edgeFilter.directOnly` defaults to `true`, is always stored as `true`, and passing `false` returns 400. # Start campaign Source: https://docs.opal.dev/api-reference/campaigns/start-campaign https://app.opal.dev/openapi.yaml post /campaigns/{campaign_id}/start Starts a draft campaign immediately, setting `started_at` and `started_by_user_id`. Returns 400 if the campaign is not in draft state, or if it is a recurring template (`is_template: true`) — templates spawn draft campaigns on their schedule and cannot be started directly. # Stop campaign Source: https://docs.opal.dev/api-reference/campaigns/stop-campaign https://app.opal.dev/openapi.yaml post /campaigns/{campaign_id}/stop Stops an ongoing campaign immediately, setting `stopped_at` and `stopped_by_user_id`. Returns 400 if the campaign has not started or has already stopped. # Update campaign Source: https://docs.opal.dev/api-reference/campaigns/update-campaign https://app.opal.dev/openapi.yaml put /campaigns/{campaign_id} Partially updates a campaign. Omitted fields are left unchanged. `configuration.query` and `configuration.reviewer_assignment_policy` cannot be updated after create; including either field returns 400. `configuration.cron_expression` and `configuration.recurring_duration_days` may only be set on template campaigns; setting them on a one-off campaign returns 400. `configuration.is_template` is immutable and not accepted on update. Configuration updates on a stopped or ended (non-template) campaign return 400. Name-only updates are still allowed. # Update campaign item reviewers Source: https://docs.opal.dev/api-reference/campaigns/update-campaign-item-reviewers https://app.opal.dev/openapi.yaml post /campaigns/{campaign_id}/item-reviewers Atomically assigns and unassigns reviewers on campaign items in one request. All `campaign_item_id`s must belong to `{campaign_id}`. Existing assign pairs are ignored; missing unassign pairs are skipped. Completed reviews (`decided_at` set) are never removed. Requires settings-edit permission. Returns 400 if the campaign has ended. # Delete configuration templates Source: https://docs.opal.dev/api-reference/configuration-templates/delete-configuration-templates https://app.opal.dev/openapi.yaml delete /configuration-templates/{configuration_template_id} Deletes a configuration template. # Get configuration templates Source: https://docs.opal.dev/api-reference/configuration-templates/get-configuration-templates https://app.opal.dev/openapi.yaml get /configuration-templates Returns a list of `ConfigurationTemplate` objects. # Post configuration templates Source: https://docs.opal.dev/api-reference/configuration-templates/post-configuration-templates https://app.opal.dev/openapi.yaml post /configuration-templates Creates a configuration template. # Put configuration templates Source: https://docs.opal.dev/api-reference/configuration-templates/put-configuration-templates https://app.opal.dev/openapi.yaml put /configuration-templates Update a configuration template. # Delete delegations Source: https://docs.opal.dev/api-reference/delegations/delete-delegations https://app.opal.dev/openapi.yaml delete /delegations/{delegation_id} Deletes a delegation by its ID. # Get delegation by ID Source: https://docs.opal.dev/api-reference/delegations/get-delegation-by-id https://app.opal.dev/openapi.yaml get /delegations/{delegation_id} Returns a specific delegation by its ID. # Get delegations Source: https://docs.opal.dev/api-reference/delegations/get-delegations https://app.opal.dev/openapi.yaml get /delegations Returns a list of request reviewer delegations configured for your organization. # Post delegations Source: https://docs.opal.dev/api-reference/delegations/post-delegations https://app.opal.dev/openapi.yaml post /delegations Creates a new request reviewer delegation to delegate access review requests from one user to another. # Create event stream Source: https://docs.opal.dev/api-reference/event-streams/create-event-stream https://app.opal.dev/openapi.yaml post /event-streams Creates a new event streaming connection. # Delete event stream Source: https://docs.opal.dev/api-reference/event-streams/delete-event-stream https://app.opal.dev/openapi.yaml delete /event-streams/{event_stream_id} Deletes an event streaming connection. # Get event streams Source: https://docs.opal.dev/api-reference/event-streams/get-event-streams https://app.opal.dev/openapi.yaml get /event-streams Returns a list of configured event streaming connections for your organization. # Update event stream Source: https://docs.opal.dev/api-reference/event-streams/update-event-stream https://app.opal.dev/openapi.yaml put /event-streams/{event_stream_id} Updates an existing event streaming connection. # Get event by ID Source: https://docs.opal.dev/api-reference/events/get-event-by-id https://app.opal.dev/openapi.yaml get /events/{event_id} Returns an `Event` object. # Get events Source: https://docs.opal.dev/api-reference/events/get-events https://app.opal.dev/openapi.yaml get /events Returns a list of `Event` objects. # Delete group bindings Source: https://docs.opal.dev/api-reference/group-bindings/delete-group-bindings https://app.opal.dev/openapi.yaml delete /group-bindings/{group_binding_id} Deletes a group binding. # Get group binding by ID Source: https://docs.opal.dev/api-reference/group-bindings/get-group-binding-by-id https://app.opal.dev/openapi.yaml get /group-bindings/{group_binding_id} Returns a `GroupBinding` object. # Get group bindings Source: https://docs.opal.dev/api-reference/group-bindings/get-group-bindings https://app.opal.dev/openapi.yaml get /group-bindings Returns a list of `GroupBinding` objects. # Post group bindings Source: https://docs.opal.dev/api-reference/group-bindings/post-group-bindings https://app.opal.dev/openapi.yaml post /group-bindings Creates a group binding. # Put group bindings Source: https://docs.opal.dev/api-reference/group-bindings/put-group-bindings https://app.opal.dev/openapi.yaml put /group-bindings Bulk updates a list of group bindings. # Delete groups Source: https://docs.opal.dev/api-reference/groups/delete-groups https://app.opal.dev/openapi.yaml delete /groups/{group_id} Deletes a group. # Delete groups containing groups Source: https://docs.opal.dev/api-reference/groups/delete-groups-containing-groups https://app.opal.dev/openapi.yaml delete /groups/{group_id}/containing-groups/{containing_group_id} Removes a containing group from a group. # Delete groups users Source: https://docs.opal.dev/api-reference/groups/delete-groups-users https://app.opal.dev/openapi.yaml delete /groups/{group_id}/users/{user_id} Removes a user's access from this group. # Get group access levels Source: https://docs.opal.dev/api-reference/groups/get-group-access-levels https://app.opal.dev/openapi.yaml get /groups/{group_id}/access_levels Returns the list of access levels defined for the group. Groups that only offer default (unnamed) access return an empty list. # Get group by ID Source: https://docs.opal.dev/api-reference/groups/get-group-by-id https://app.opal.dev/openapi.yaml get /groups/{group_id} Returns a `Group` object. # Get groups Source: https://docs.opal.dev/api-reference/groups/get-groups https://app.opal.dev/openapi.yaml get /groups Returns a list of groups for your organization. # Get groups message channels Source: https://docs.opal.dev/api-reference/groups/get-groups-message-channels https://app.opal.dev/openapi.yaml get /groups/{group_id}/message-channels Gets the list of audit and reviewer message channels attached to a group. # Get groups on call schedules Source: https://docs.opal.dev/api-reference/groups/get-groups-on-call-schedules https://app.opal.dev/openapi.yaml get /groups/{group_id}/on-call-schedules Gets the list of on call schedules attached to a group. # Get groups resources Source: https://docs.opal.dev/api-reference/groups/get-groups-resources https://app.opal.dev/openapi.yaml get /groups/{group_id}/resources Gets the list of resources that the group gives access to. # Get groups reviewer stages Source: https://docs.opal.dev/api-reference/groups/get-groups-reviewer-stages https://app.opal.dev/openapi.yaml get /groups/{group_id}/reviewer-stages Gets the list of reviewer stages for a group. # Get groups reviewers Source: https://docs.opal.dev/api-reference/groups/get-groups-reviewers https://app.opal.dev/openapi.yaml get /groups/{group_id}/reviewers Gets the list of owner IDs of the reviewers for a group. # Get groups tags Source: https://docs.opal.dev/api-reference/groups/get-groups-tags https://app.opal.dev/openapi.yaml get /groups/{group_id}/tags Returns all tags applied to the group. # Get groups users Source: https://docs.opal.dev/api-reference/groups/get-groups-users https://app.opal.dev/openapi.yaml get /groups/{group_id}/users Gets the list of users for this group. # Get groups visibility Source: https://docs.opal.dev/api-reference/groups/get-groups-visibility https://app.opal.dev/openapi.yaml get /groups/{group_id}/visibility Gets the visibility of this group. # Get groupsusers Source: https://docs.opal.dev/api-reference/groups/get-groupsusers https://app.opal.dev/openapi.yaml get /groups/users/{user_id} Returns all groups that the user is a member of. # Get nested group by ID Source: https://docs.opal.dev/api-reference/groups/get-nested-group-by-id https://app.opal.dev/openapi.yaml get /groups/{group_id}/containing-groups/{containing_group_id} Gets a specific containing group for a group. # Get nested groups Source: https://docs.opal.dev/api-reference/groups/get-nested-groups https://app.opal.dev/openapi.yaml get /groups/{group_id}/containing-groups Gets the list of groups that the group gives access to. # Post groups Source: https://docs.opal.dev/api-reference/groups/post-groups https://app.opal.dev/openapi.yaml post /groups Creates an Opal group or [imports a remote group](https://docs.opal.dev/reference/end-system-objects). # Post groups containing groups Source: https://docs.opal.dev/api-reference/groups/post-groups-containing-groups https://app.opal.dev/openapi.yaml post /groups/{group_id}/containing-groups Creates a new containing group. # Post groups resources Source: https://docs.opal.dev/api-reference/groups/post-groups-resources https://app.opal.dev/openapi.yaml post /groups/{group_id}/resources/{resource_id} Adds a resource to a group. # Post groups users Source: https://docs.opal.dev/api-reference/groups/post-groups-users https://app.opal.dev/openapi.yaml post /groups/{group_id}/users/{user_id} Adds a user to this group. # Put groups Source: https://docs.opal.dev/api-reference/groups/put-groups https://app.opal.dev/openapi.yaml put /groups Bulk updates a list of groups. # Put groups message channels Source: https://docs.opal.dev/api-reference/groups/put-groups-message-channels https://app.opal.dev/openapi.yaml put /groups/{group_id}/message-channels Sets the list of audit message channels attached to a group. # Put groups on call schedules Source: https://docs.opal.dev/api-reference/groups/put-groups-on-call-schedules https://app.opal.dev/openapi.yaml put /groups/{group_id}/on-call-schedules Sets the list of on call schedules attached to a group. # Put groups resources Source: https://docs.opal.dev/api-reference/groups/put-groups-resources https://app.opal.dev/openapi.yaml put /groups/{group_id}/resources Sets the list of resources that the group gives access to. # Put groups reviewer stages Source: https://docs.opal.dev/api-reference/groups/put-groups-reviewer-stages https://app.opal.dev/openapi.yaml put /groups/{group_id}/reviewer-stages Sets the list of reviewer stages for a group. # Put groups reviewers Source: https://docs.opal.dev/api-reference/groups/put-groups-reviewers https://app.opal.dev/openapi.yaml put /groups/{group_id}/reviewers Sets the list of reviewers for a group. # Put groups users Source: https://docs.opal.dev/api-reference/groups/put-groups-users https://app.opal.dev/openapi.yaml put /groups/{group_id}/users/{user_id} Updates a user's access level or duration in this group. # Put groups visibility Source: https://docs.opal.dev/api-reference/groups/put-groups-visibility https://app.opal.dev/openapi.yaml put /groups/{group_id}/visibility Sets the visibility of this group. # Delete idp group mappings groups Source: https://docs.opal.dev/api-reference/idp-group-mappings/delete-idp-group-mappings-groups https://app.opal.dev/openapi.yaml delete /idp-group-mappings/{app_resource_id}/groups/{group_id} Deletes an `IdpGroupMapping` object. # Get idp group mappings Source: https://docs.opal.dev/api-reference/idp-group-mappings/get-idp-group-mappings https://app.opal.dev/openapi.yaml get /idp-group-mappings/{app_resource_id} Returns the configured set of available `IdpGroupMapping` objects for an Okta app. # Get idp group mappings groups Source: https://docs.opal.dev/api-reference/idp-group-mappings/get-idp-group-mappings-groups https://app.opal.dev/openapi.yaml get /idp-group-mappings/{app_resource_id}/groups/{group_id} Gets an `IdpGroupMapping` object for an Okta app and group. # Post idp group mappings groups Source: https://docs.opal.dev/api-reference/idp-group-mappings/post-idp-group-mappings-groups https://app.opal.dev/openapi.yaml post /idp-group-mappings/{app_resource_id}/groups/{group_id} Creates or updates an individual `IdpGroupMapping` object (upsert operation). **Behavior:** - If the mapping doesn't exist, it will be created with the provided values - If the mapping exists, only the fields provided in the request will be updated # Put idp group mappings Source: https://docs.opal.dev/api-reference/idp-group-mappings/put-idp-group-mappings https://app.opal.dev/openapi.yaml put /idp-group-mappings/{app_resource_id} Updates the list of available `IdpGroupMapping` objects for an Okta app. # Get message channel by ID Source: https://docs.opal.dev/api-reference/message-channels/get-message-channel-by-id https://app.opal.dev/openapi.yaml get /message-channels/{message_channel_id} Gets a `MessageChannel` object. # Get message channels Source: https://docs.opal.dev/api-reference/message-channels/get-message-channels https://app.opal.dev/openapi.yaml get /message-channels Returns a list of `MessageChannel` objects. # Post message channels Source: https://docs.opal.dev/api-reference/message-channels/post-message-channels https://app.opal.dev/openapi.yaml post /message-channels Creates a `MessageChannel` objects. # Get non human identities Source: https://docs.opal.dev/api-reference/non-human-identities/get-non-human-identities https://app.opal.dev/openapi.yaml get /non-human-identities Returns a list of non-human identities for your organization. # Get on call schedule by ID Source: https://docs.opal.dev/api-reference/on-call-schedules/get-on-call-schedule-by-id https://app.opal.dev/openapi.yaml get /on-call-schedules/{on_call_schedule_id} Gets a `OnCallSchedule` object. # Get on call schedules Source: https://docs.opal.dev/api-reference/on-call-schedules/get-on-call-schedules https://app.opal.dev/openapi.yaml get /on-call-schedules Returns a list of `OnCallSchedule` objects. # Post on call schedules Source: https://docs.opal.dev/api-reference/on-call-schedules/post-on-call-schedules https://app.opal.dev/openapi.yaml post /on-call-schedules Creates a `OnCallSchedule` objects. # Run an ad-hoc OpalQuery Source: https://docs.opal.dev/api-reference/opal-queries/run-an-ad-hoc-opalquery https://app.opal.dev/openapi.yaml post /queries/run Executes an ad-hoc OpalQuery and returns paginated results. Two query types are supported: a **Node** query filters and returns entities (users, resources, or groups); an **Access** query returns the access grants between principals and their entitlements, one per (principal, entitlement, access level). Set `type` to `NODE` or `ACCESS` in the request body to select the query type. This endpoint is available to OpalQuery beta participants. To request access, contact Opal support. # Delete owners Source: https://docs.opal.dev/api-reference/owners/delete-owners https://app.opal.dev/openapi.yaml delete /owners/{owner_id} Deletes an owner. # Get owner by ID Source: https://docs.opal.dev/api-reference/owners/get-owner-by-id https://app.opal.dev/openapi.yaml get /owners/{owner_id} Returns an `Owner` object. # Get owners Source: https://docs.opal.dev/api-reference/owners/get-owners https://app.opal.dev/openapi.yaml get /owners Returns a list of `Owner` objects. # Get owners users Source: https://docs.opal.dev/api-reference/owners/get-owners-users https://app.opal.dev/openapi.yaml get /owners/{owner_id}/users Gets the list of users for this owner, in escalation priority order if applicable. # Get ownersname Source: https://docs.opal.dev/api-reference/owners/get-ownersname https://app.opal.dev/openapi.yaml get /owners/name/{owner_name} Returns an `Owner` object. Does not support owners with `/` in their name, use /owners?name=... instead. # Post owners Source: https://docs.opal.dev/api-reference/owners/post-owners https://app.opal.dev/openapi.yaml post /owners Creates an owner. # Put owners Source: https://docs.opal.dev/api-reference/owners/put-owners https://app.opal.dev/openapi.yaml put /owners Bulk updates a list of owners. # Put owners users Source: https://docs.opal.dev/api-reference/owners/put-owners-users https://app.opal.dev/openapi.yaml put /owners/{owner_id}/users Sets the list of users for this owner. If escalation is enabled, the order of this list is the escalation priority order of the users. If the owner has a source group, adding or removing users from this list won't be possible. # Add a Paladin context source Source: https://docs.opal.dev/api-reference/paladin/add-a-paladin-context-source https://app.opal.dev/openapi.yaml post /paladin/{paladin_id}/context-sources Configures a context source (a Slack channel or a document) for a Paladin to read. Idempotent, so re-adding an existing source returns it. # Create Paladin Source: https://docs.opal.dev/api-reference/paladin/create-paladin https://app.opal.dev/openapi.yaml post /paladin Creates a new `Paladin`. # Delete Paladin Source: https://docs.opal.dev/api-reference/paladin/delete-paladin https://app.opal.dev/openapi.yaml delete /paladin/{paladin_id} Deletes a Paladin, removing the underlying service user. # Get Paladin by ID Source: https://docs.opal.dev/api-reference/paladin/get-paladin-by-id https://app.opal.dev/openapi.yaml get /paladin/{paladin_id} Returns a `Paladin` object. # Get Paladins by name Source: https://docs.opal.dev/api-reference/paladin/get-paladins-by-name https://app.opal.dev/openapi.yaml get /paladin/name/{paladin_name} Returns all Paladins whose name exactly matches the given name. Names are not unique, so the result is a list and may be empty. # List Paladin context sources Source: https://docs.opal.dev/api-reference/paladin/list-paladin-context-sources https://app.opal.dev/openapi.yaml get /paladin/{paladin_id}/context-sources Returns the context sources (Slack channels and documents) configured for a Paladin. # Remove a Paladin context source Source: https://docs.opal.dev/api-reference/paladin/remove-a-paladin-context-source https://app.opal.dev/openapi.yaml delete /paladin/{paladin_id}/context-sources/{context_source_id} Removes a context source from a Paladin. # Update Paladin Source: https://docs.opal.dev/api-reference/paladin/update-paladin https://app.opal.dev/openapi.yaml put /paladin/{paladin_id} Updates a `Paladin` object. # Delete request templates Source: https://docs.opal.dev/api-reference/request-templates/delete-request-templates https://app.opal.dev/openapi.yaml delete /request-templates/{request_template_id} Deletes a request template. # Get request templates Source: https://docs.opal.dev/api-reference/request-templates/get-request-templates https://app.opal.dev/openapi.yaml get /request-templates Returns a list of `RequestTemplate` objects. # Get request templates 1 Source: https://docs.opal.dev/api-reference/request-templates/get-request-templates-1 https://app.opal.dev/openapi.yaml get /request-templates/{request_template_id} Returns a `RequestTemplate` object. # Post request templates Source: https://docs.opal.dev/api-reference/request-templates/post-request-templates https://app.opal.dev/openapi.yaml post /request-templates Creates a request template. # Put request templates Source: https://docs.opal.dev/api-reference/request-templates/put-request-templates https://app.opal.dev/openapi.yaml put /request-templates Updates a request template. # Cancel request Source: https://docs.opal.dev/api-reference/requests/cancel-request https://app.opal.dev/openapi.yaml post /requests/{id}/cancel Cancels a pending access request. # Get request by ID Source: https://docs.opal.dev/api-reference/requests/get-request-by-id https://app.opal.dev/openapi.yaml get /requests/{id} Returns a request by ID. # Get requests Source: https://docs.opal.dev/api-reference/requests/get-requests https://app.opal.dev/openapi.yaml get /requests Returns a list of requests for your organization that is visible by the admin. # Get requests comments Source: https://docs.opal.dev/api-reference/requests/get-requests-comments https://app.opal.dev/openapi.yaml get /requests/{id}/comments Returns a list of comments for a specific request. # Get requests via Relay Source: https://docs.opal.dev/api-reference/requests/get-requests-via-relay https://app.opal.dev/openapi.yaml get /requests/relay Returns a paginated list of requests using Relay-style cursor pagination. # Post requests Source: https://docs.opal.dev/api-reference/requests/post-requests https://app.opal.dev/openapi.yaml post /requests Create an access request # Post requests approve Source: https://docs.opal.dev/api-reference/requests/post-requests-approve https://app.opal.dev/openapi.yaml post /requests/{id}/approve Approve an access request # Post requests comments Source: https://docs.opal.dev/api-reference/requests/post-requests-comments https://app.opal.dev/openapi.yaml post /requests/{id}/comments Comment on an access request # Post requests deny Source: https://docs.opal.dev/api-reference/requests/post-requests-deny https://app.opal.dev/openapi.yaml post /requests/{id}/deny Deny an access request # Send reminder to a reviewer Source: https://docs.opal.dev/api-reference/requests/send-reminder-to-a-reviewer https://app.opal.dev/openapi.yaml post /requests/{id}/reviewers/{reviewer_id}/remind Sends a reminder to one pending reviewer of the request. # Send request reminder Source: https://docs.opal.dev/api-reference/requests/send-request-reminder https://app.opal.dev/openapi.yaml post /requests/{id}/remind Sends a reminder to all pending reviewers of the request. # Delete resources Source: https://docs.opal.dev/api-reference/resources/delete-resources https://app.opal.dev/openapi.yaml delete /resources/{resource_id} Deletes a resource. # Delete resources custom access levels Source: https://docs.opal.dev/api-reference/resources/delete-resources-custom-access-levels https://app.opal.dev/openapi.yaml delete /resources/{resource_id}/custom-access-levels/{access_level_remote_id} Deletes a custom access level identified by its remote ID. If the resource is a parent type, the deletion fans out to all child resources. # Delete resources non human identities Source: https://docs.opal.dev/api-reference/resources/delete-resources-non-human-identities https://app.opal.dev/openapi.yaml delete /resources/{resource_id}/non-human-identities/{non_human_identity_id} Removes a non-human identity's direct access from this resource. # Delete resources users Source: https://docs.opal.dev/api-reference/resources/delete-resources-users https://app.opal.dev/openapi.yaml delete /resources/{resource_id}/users/{user_id} Removes a user's direct access from this resource. # Get resource access levels Source: https://docs.opal.dev/api-reference/resources/get-resource-access-levels https://app.opal.dev/openapi.yaml get /resources/{resource_id}/access_levels Returns the list of access levels defined for the resource. Resources that only offer default (unnamed) access return an empty list. # Get resource by ID Source: https://docs.opal.dev/api-reference/resources/get-resource-by-id https://app.opal.dev/openapi.yaml get /resources/{resource_id} Retrieves a resource. # Get resource user Source: https://docs.opal.dev/api-reference/resources/get-resource-user https://app.opal.dev/openapi.yaml get /resources/{resource_id}/users/{user_id} Returns information about a specific user's access to a resource. # Get resource user access status Source: https://docs.opal.dev/api-reference/resources/get-resource-user-access-status- https://app.opal.dev/openapi.yaml get /resource-user-access-status/{resource_id}/{user_id} Get user's access status to a resource. # Get resource users Source: https://docs.opal.dev/api-reference/resources/get-resource-users https://app.opal.dev/openapi.yaml get /resources/{resource_id}/users Gets the list of users for this resource. # Get resources Source: https://docs.opal.dev/api-reference/resources/get-resources https://app.opal.dev/openapi.yaml get /resources Returns a list of resources for your organization. # Get resources custom access levels Source: https://docs.opal.dev/api-reference/resources/get-resources-custom-access-levels https://app.opal.dev/openapi.yaml get /resources/{resource_id}/custom-access-levels Returns all custom access levels for a resource. If the resource is a parent type (e.g. GitHubOrg), returns aggregated roles across child resources. # Get resources groups Source: https://docs.opal.dev/api-reference/resources/get-resources-groups https://app.opal.dev/openapi.yaml get /resources/{resource_id}/groups Returns a list of groups that grant access to the resource # Get resources message channels Source: https://docs.opal.dev/api-reference/resources/get-resources-message-channels https://app.opal.dev/openapi.yaml get /resources/{resource_id}/message-channels Gets the list of audit message channels attached to a resource. # Get resources non human identities Source: https://docs.opal.dev/api-reference/resources/get-resources-non-human-identities https://app.opal.dev/openapi.yaml get /resources/{resource_id}/non-human-identities Gets the list of non-human identities with access to this resource. # Get resources reviewer stages Source: https://docs.opal.dev/api-reference/resources/get-resources-reviewer-stages https://app.opal.dev/openapi.yaml get /resources/{resource_id}/reviewer-stages Gets the list reviewer stages for a resource. # Get resources reviewers Source: https://docs.opal.dev/api-reference/resources/get-resources-reviewers https://app.opal.dev/openapi.yaml get /resources/{resource_id}/reviewers Gets the list of owner IDs of the reviewers for a resource. # Get resources scoped role permissions Source: https://docs.opal.dev/api-reference/resources/get-resources-scoped-role-permissions https://app.opal.dev/openapi.yaml get /resources/{resource_id}/scoped-role-permissions Returns all the scoped role permissions that apply to the given resource. Only OPAL_SCOPED_ROLE resource type supports this field. # Get resources tags Source: https://docs.opal.dev/api-reference/resources/get-resources-tags https://app.opal.dev/openapi.yaml get /resources/{resource_id}/tags Returns all tags applied to the resource. # Get resources visibility Source: https://docs.opal.dev/api-reference/resources/get-resources-visibility https://app.opal.dev/openapi.yaml get /resources/{resource_id}/visibility Gets the visibility of this resource. # Get resourcesusers Source: https://docs.opal.dev/api-reference/resources/get-resourcesusers https://app.opal.dev/openapi.yaml get /resources/users/{user_id} Gets the list of resources for this user. # Patch resources custom access levels Source: https://docs.opal.dev/api-reference/resources/patch-resources-custom-access-levels https://app.opal.dev/openapi.yaml patch /resources/{resource_id}/custom-access-levels/{access_level_remote_id} Updates a custom access level identified by its remote ID. If the resource is a parent type, the update fans out to all child resources. # Post resources Source: https://docs.opal.dev/api-reference/resources/post-resources https://app.opal.dev/openapi.yaml post /resources Creates a resource. See [here](https://docs.opal.dev/reference/end-system-objects) for details about importing resources. # Post resources custom access levels Source: https://docs.opal.dev/api-reference/resources/post-resources-custom-access-levels https://app.opal.dev/openapi.yaml post /resources/{resource_id}/custom-access-levels Creates a custom access level on a resource. If the resource is a parent type, the role is created on all child resources. # Post resources non human identities Source: https://docs.opal.dev/api-reference/resources/post-resources-non-human-identities https://app.opal.dev/openapi.yaml post /resources/{resource_id}/non-human-identities/{non_human_identity_id} Gives a non-human identity access to this resource. # Post resources users Source: https://docs.opal.dev/api-reference/resources/post-resources-users https://app.opal.dev/openapi.yaml post /resources/{resource_id}/users/{user_id} Adds a user to this resource. # Put resources Source: https://docs.opal.dev/api-reference/resources/put-resources https://app.opal.dev/openapi.yaml put /resources Bulk updates a list of resources. # Put resources message channels Source: https://docs.opal.dev/api-reference/resources/put-resources-message-channels https://app.opal.dev/openapi.yaml put /resources/{resource_id}/message-channels Sets the list of audit message channels attached to a resource. # Put resources reviewer stages Source: https://docs.opal.dev/api-reference/resources/put-resources-reviewer-stages https://app.opal.dev/openapi.yaml put /resources/{resource_id}/reviewer-stages Sets the list of reviewer stages for a resource. # Put resources reviewers Source: https://docs.opal.dev/api-reference/resources/put-resources-reviewers https://app.opal.dev/openapi.yaml put /resources/{resource_id}/reviewers Sets the list of reviewers for a resource. # Put resources scoped role permissions Source: https://docs.opal.dev/api-reference/resources/put-resources-scoped-role-permissions https://app.opal.dev/openapi.yaml put /resources/{resource_id}/scoped-role-permissions Sets all the scoped role permissions on an OPAL_SCOPED_ROLE resource. # Put resources users Source: https://docs.opal.dev/api-reference/resources/put-resources-users https://app.opal.dev/openapi.yaml put /resources/{resource_id}/users/{user_id} Updates a user's access level or duration on this resource. # Put resources visibility Source: https://docs.opal.dev/api-reference/resources/put-resources-visibility https://app.opal.dev/openapi.yaml put /resources/{resource_id}/visibility Sets the visibility of this resource. # Get sessions Source: https://docs.opal.dev/api-reference/sessions/get-sessions https://app.opal.dev/openapi.yaml get /sessions Returns a list of `Session` objects. # Delete tag Source: https://docs.opal.dev/api-reference/tags/delete-tag https://app.opal.dev/openapi.yaml delete /tag/{tag_id} UNSTABLE. May be removed at any time. Deletes a tag with the given id. # Delete tags groups Source: https://docs.opal.dev/api-reference/tags/delete-tags-groups https://app.opal.dev/openapi.yaml delete /tags/{tag_id}/groups/{group_id} Removes a tag from a group. # Delete tags resources Source: https://docs.opal.dev/api-reference/tags/delete-tags-resources https://app.opal.dev/openapi.yaml delete /tags/{tag_id}/resources/{resource_id} Removes a tag from a resource. # Delete tags users Source: https://docs.opal.dev/api-reference/tags/delete-tags-users https://app.opal.dev/openapi.yaml delete /tags/{tag_id}/users/{user_id} Removes a tag from a user. # Get tag Source: https://docs.opal.dev/api-reference/tags/get-tag https://app.opal.dev/openapi.yaml get /tag Gets a tag with the given key and value. # Get tag by ID Source: https://docs.opal.dev/api-reference/tags/get-tag-by-id https://app.opal.dev/openapi.yaml get /tag/{tag_id} UNSTABLE. May be removed at any time. Gets a tag with the given id. # Get tags Source: https://docs.opal.dev/api-reference/tags/get-tags https://app.opal.dev/openapi.yaml get /tags Returns a list of tags created by your organization. # Post tag Source: https://docs.opal.dev/api-reference/tags/post-tag https://app.opal.dev/openapi.yaml post /tag Creates a tag with the given key and value. # Post tags groups Source: https://docs.opal.dev/api-reference/tags/post-tags-groups https://app.opal.dev/openapi.yaml post /tags/{tag_id}/groups/{group_id} Applies a tag to a group. # Post tags resources Source: https://docs.opal.dev/api-reference/tags/post-tags-resources https://app.opal.dev/openapi.yaml post /tags/{tag_id}/resources/{resource_id} Applies a tag to a resource. # Post tags users Source: https://docs.opal.dev/api-reference/tags/post-tags-users https://app.opal.dev/openapi.yaml post /tags/{tag_id}/users/{user_id} Applies a tag to a user. # Delete token Source: https://docs.opal.dev/api-reference/tokens/delete-token https://app.opal.dev/openapi.yaml delete /tokens/{token_id} Deletes a first-party API token. Admins can delete any token. Non-admins can only delete their own tokens when the organization allows all users to create API tokens. # Get tokens Source: https://docs.opal.dev/api-reference/tokens/get-tokens https://app.opal.dev/openapi.yaml get /tokens Returns a list of first-party API tokens for your organization. Requires admin access. # Get uar Source: https://docs.opal.dev/api-reference/uars/get-uar https://app.opal.dev/openapi.yaml get /uar/{uar_id} Retrieves a specific UAR. Deprecated in favor of `GET /campaigns/{campaign_id}`. # Get uars Source: https://docs.opal.dev/api-reference/uars/get-uars https://app.opal.dev/openapi.yaml get /uars Returns a list of `UAR` objects. Deprecated in favor of `GET /campaigns`. # Post uar Source: https://docs.opal.dev/api-reference/uars/post-uar https://app.opal.dev/openapi.yaml post /uar Starts a User Access Review. Deprecated in favor of `POST /campaigns`. # Delete users Source: https://docs.opal.dev/api-reference/users/delete-users https://app.opal.dev/openapi.yaml delete /users/{user_id} Deletes a user from your organization. # Get user Source: https://docs.opal.dev/api-reference/users/get-user https://app.opal.dev/openapi.yaml get /user Returns a `User` object. # Get users Source: https://docs.opal.dev/api-reference/users/get-users https://app.opal.dev/openapi.yaml get /users Returns a list of users for your organization. # Get users tags Source: https://docs.opal.dev/api-reference/users/get-users-tags https://app.opal.dev/openapi.yaml get /users/{user_id}/tags Returns all tags applied to the user. # Get usersremote users Source: https://docs.opal.dev/api-reference/users/get-usersremote_users https://app.opal.dev/openapi.yaml get /users/remote_users Returns a list of remote users for your organization. # Get userswhoami Source: https://docs.opal.dev/api-reference/users/get-userswhoami https://app.opal.dev/openapi.yaml get /users/whoami Returns the user that the provided API token authenticates as. # Patch users Source: https://docs.opal.dev/api-reference/users/patch-users https://app.opal.dev/openapi.yaml patch /users/{user_id} Updates a user's position or manager. # Post users Source: https://docs.opal.dev/api-reference/users/post-users https://app.opal.dev/openapi.yaml post /users Invites a user to your organization. # Changelog Source: https://docs.opal.dev/changelog/changelog Product updates and announcements ### Added * Added the ability to kick off an Access Campaign directly from the OpalQuery UI * Added `GET /campaigns/{campaign_id}/items` REST API endpoint to list review items under a given campaign, with filtering by status, reviewer, and search ### Improved * Invalid callback URLs are now included in the HTTP error response when connecting an MCP server, making misconfigured OAuth redirect URIs easier to diagnose ### Added * Admins can now suppress access-loss notifications per resource or group, preventing users from being notified when they lose access due to expiry or an access review decision ### Improved * Productivity Integrations now show connection details for Opsgenie, Shortcut, and Amazon Bedrock (account name or verification error, workspace/teams, and Bedrock region) * Productivity Integrations now show a clear error when an API key is stale, revoked, or unreachable ### Bug Fixes * Fixed a bug where submitting an access-review decision that revoked a user's access to a resource could return a server error if that resource had been deleted since the review started * Fixed a bug where reviewing an Access Campaign item could return a server error if the reviewed user's access had already been removed (e.g. the user was offboarded) while the campaign was in progress; the review now completes normally * Fixed a bug where removing a user from a Google Group could fail if their account's cached email was stale ### Added * Added the ability to filter the Apps inventory by Native Apps * Added REST API endpoints for `InviteUser`, `UpdateUser`, and `DeleteUser` * Added filtering of OpalScript runs by script; the runs table now shows the linked request's items directly instead of a separate summary * Campaigns now show a deep link back to their parent schedule ### Improved * MCP server OAuth no longer requires the `resource` parameter, making it compatible with more external integrations such as Glean and Databricks * Usage data is now collected for managed GitHub repositories on enterprise connections, where previously no usage was recorded * On GitHub enterprise connections, usage data sync continues for remaining organizations when one organization's audit log access is rejected * The **View events** permission can now be granted in Custom Roles, giving non-admins access to Events pages and exports * Resource and group admin owners can now open individual event detail pages for the assets they own, instead of hitting a not-found page from the entity's Events tab * Users who can only edit a bundle's resources and groups can now see that bundle's Events tab * Reviewers approving an access request can now see the extension duration, max number of extensions, and total potential access duration for resources and groups with access extensions enabled * Access Campaign review-results exports now have separate "Reviewer" and "Decision" columns instead of one combined column * Failed access changes no longer store or email unredacted secrets echoed back by remote systems ### Bug Fixes * Fixed a bug where a User Access Review reviewer inbox showed "Page not found" when a reviewer's item pointed at a resource or group that had since been deleted or unmanaged * Fixed an issue where a brief Google API error during Google Workspace connector permission checks could incorrectly fail an entire sync * Fixed access-expiry reminders in Slack showing a "Request" button that started a new approval instead of extending existing access * Fixed leaked LDAP/AD TCP connections during provision, push, and sync operations that could accumulate stale connections against customer directory servers ### Self-hosted only * Added opt-in support for automatic rolling-restarts of backend and task-worker Deployments when the Stakater Reloader controller detects a rotated secret — see [Install Stakater Reloader (optional)](https://docs.opal.dev/docs/install-opal-using-helm#install-stakater-reloader-optional) ### Added * Added purpose tag filtering to the Agents tab — you can now filter agents by any keyword- or AI-derived purpose tag assigned to agents in your organization * Added a Source filter to the Agents tab, letting you narrow the agent list by connector origin (Opal, Okta, Anthropic, Bedrock AgentCore, SPIFFE, Detected, or Re-labeled from user) ### Improved * OpalScript's `resource` value now exposes remote ID and name, parent resource ID, connection ID, and created/updated timestamps; the `request` value now exposes support ticket ID and request ticket ID — making these available for use in approval and delegation logic * OpalScript's `group` value now exposes additional fields — remote ID and name, connection ID, and created/updated timestamps — for use in approval and delegation logic ### Added * Added the ability to create Slack user groups from Inventory using the **+ Groups** button; you can also deactivate a user group by revoking all its members * Added REST API support for creating Slack user groups via the [`POST /groups`](https://docs.opal.dev/reference/creategroup) endpoint, including an initial member list, handle, and workspace * Added REST API endpoints to cancel a pending request (`POST /requests/:id/cancel`) and remind reviewers about a pending request (`POST /requests/:id/remind`) * Configuration Templates now display how many resources and groups use each template in the table and on each template's detail page — click through to see exactly which ones ### Improved * Agent update events now show which specific field changed (name, owner, accountable sponsor, tier, purpose, or harness) instead of only recording that an update happened * OpalScript's `user` value now exposes additional fields: first and last name, secondary emails, HR/IDP status, system-user flag, and created, updated, and last-login timestamps * When a request is approved for access the requester already has, the request page now shows that the propagation step needed no change instead of showing an empty or failed step * NetSuite and Coupa access changes now retry automatically when the remote system rate-limits Opal or returns a transient error, instead of failing immediately ### Bug Fixes * Fixed Azure AD / Entra ID enterprise-app logos not displaying in Opal's catalog and request views * Fixed an issue where removing a group's last on-call schedule did not immediately remove the members that schedule had granted access to — those members remained until the next scheduled sync * Fixed incorrect help text on the scheduled Access Campaign Timezone setting, which said schedules fire at midnight when they actually fire at 8:00 AM * Fixed the **+ Group** button on the group Assets tab creating a member-group relationship instead of adding the group to the selected groups * Fixed AzureAD role grant failures to report the actual error reason (such as an authorization failure) instead of an unknown error * Fixed forfeiting access to a group or resource you can no longer view under your org's visibility settings — it is now blocked instead of silently going through * Fixed a bug where the Activity feed on a request's detail page could show a support ticket belonging to an unrelated request * Fixed an LDAP connection leak during connection validation and directory sync that could accumulate stale connections against a customer's LDAP server over time ### Added * Admins can now sort Access Campaigns by creation date, review item count, or pending review items * Productivity integrations now show workspace, organization, or account subdomain details on the Settings page * The [`GET /groups`](https://docs.opal.dev/reference/getgroups) API now supports a `requestable` filter to return only groups that allow access requests, and the custom role group picker hides non-requestable groups by default ### Improved * Google Workspace directories now suggest custom schema attributes when setting imported user attributes, instead of requiring you to type the schema name from memory * GitHub syncs no longer stall when the hourly API quota runs low near its reset — remaining requests are paced across the time left in the window * Faster GitHub syncs: team details are now read from a single organization-wide team listing instead of one API request per team ### Bug Fixes * Retrying a failed access push now updates the access request it belongs to — previously a retry could succeed while the request page still showed the original failed ticket, and the requester was never told their access had been granted * Linking a service account to an agent from the NHIs inventory no longer removes it from that list ### Added * Access rule failsafe thresholds are now configurable per organization, so access rules no longer pause on minor membership changes ### Bug Fixes * Fixed an error that could prevent identity provider details from loading for users who don't have visibility into all of an organization's IdP connections * On a request made on someone's behalf, the person receiving the access now sees the same tailored Paladin recommendation the requester sees, instead of the reviewer wording ### Added * Added group leaders to the read-only Group Details tab, listed immediately below "Break-glass users" as user pills (or "None" when the group has no leaders) * GitLab projects now show "Last Used" in Risk Center, Recommendations, and access reviews based on push activity ### Self-Hosted Only * Fixed an issue where disabling the Slack integration or restarting the service could flood logs with spurious Slack timeout errors and leave the integrations page showing Slack as connected after the connection had ended ### Added * Admins can now select multiple ongoing access reviews from the list and stop them all at once ### Improved * Required fields on the "Add App" setup forms are now marked with an asterisk (`*`), making it clearer which fields must be filled in before creating a connection * Propagation failures caused by the end system are now reported with the actual cause — throttling, permission denied, or a remote conflict — instead of "Opal internal error" ### Bug Fixes * Fixed a regression where creating an access request on behalf of another user through the API could incorrectly fail with a 400 error * Fixed a bug where syncing an individual resource could clear the parent of that resource and its descendants, which could silently remove access for non-admin users who still had a valid grant at a parent scope ### Added * Added support for GCP billing accounts: you can now import billing accounts into Opal and request access to them, including roles such as Billing Account Administrator and Billing Account Viewer * Added support for custom billing roles on GCP billing accounts, in addition to the predefined ones * Admins can now configure allowed callback URLs for MCP OAuth in org settings (searchable as "MCP OAuth callback URLs") * The audit log now records which reviewer denied a request, matching what is already recorded for approvals ### Improved * The Opal MCP server now returns legible error messages to MCP clients (such as Claude Code) instead of opaque errors * Pausing the Error Notifications category in Settings → Notifications now also suppresses admin notifications about failed propagations * API tokens are now subject to the org IP allowlist on LLM HTTP endpoints, matching GraphQL and REST * Campaign PDF export reports now show a cleaner metrics summary without duplicate columns ### Bug Fixes * Fixed an issue where a sync could remove a group membership and then immediately try to re-add it, producing spurious "failed to propagate" errors for users who no longer have an account in the connected app * Fixed a bug that could cause the Slack access request modal to fail when requesting a bundle * Fixed the "Granted at" date in access review campaigns showing when the entitlement was first seen rather than when access was actually granted; applies to access granted going forward * Fixed saving a new Anthropic Analytics API key sometimes failing with a spurious "Latest available data" error even when the key was valid ### Added * Custom roles can now limit requests made on behalf of users or groups to specific resources or apps * Added a multi-select question type for access request templates; admins can configure a list of options, and requesters can choose multiple values when submitting a request on the web and in Slack * Added a callout custom field type for request templates: display-only banners with configurable severity levels and custom messages that appear on the request form * Added API endpoints to list access levels for a resource (`GET /resources/{resource_id}/access_levels`) and a group (`GET /groups/{group_id}/access_levels`) * Added API endpoints for managing request templates * Service accounts can now be added as owner members directly from the owner's Add users screen; they appear alongside people and are marked with a "Service account" label * Many new Audit Log events added ### Improved * Access campaign event timelines now distinguish a reviewer's decision to revoke from the revocation Opal actually performed, instead of showing both as "Access revoked" * Improved performance of the Inventory > Apps > Accounts tab * Tag details page now also lists access rules that reference the tag * On resource or group types Opal cannot provision, the Add Users button is now disabled with an explanation ### Bug Fixes * Fixed an error that prevented creating group bindings when the source group was included in the member group list * Fixed an error that could prevent viewing certain access requests when the person who originally requested access on behalf of a group had since been deleted * Fixed a rare crash that could cause a connection sync to fail when an organization had groups without a remote identifier * Fixed an issue where a Google group imported through the API using its email address instead of its Google group ID would be duplicated internally and stop receiving membership updates; affected groups are corrected automatically on the next sync * Fixed a bug where re-requesting access from Slack would auto-submit the request and reuse the reason from your previous request, even in organizations that require a reason; those re-requests now open the request modal so you can enter a new reason * Fixed the audit log mislabeling a group's config template change as a resource change; added missing audit events for "require manager approval" and access-request escalation period changes * Fixed an issue where GitHub connector syncs taking longer than one hour failed with authentication errors; the GitHub App installation token is now refreshed before it expires ### Improved * Improved performance of the Groups and Resources tabs on the owner detail page * Improved public API error messages for API-token requests to indicate when the token's owner has lost the required role, helping callers quickly identify and resolve authorization failures ### Bug Fixes * Fixed a bug where submitting an access request that requires MFA from Slack could fail with an error instead of completing or prompting to finish the MFA challenge * Fixed a bug where deleting an OpalScript left orphaned access records for its provisioned service account; deletion now fully cleans up the account's role assignments, memberships, and related records ### Improved * Improved global search precision: short queries no longer match the middle of unrelated words (for example, searching "twin" no longer returns items named "aldwin"), while typo-tolerant matching of whole words is unchanged * Updated OpalScript type badges to labels for clearer visual distinction ### Bug Fixes * Fixed a bug where access-extension options could fail to load on resource and group pages that included an unmanaged item ### Bug Fixes * Fixed risk sensitivity showing as "unknown" for resources on Vault, ClickHouse, Okta CIAM, Datadog, and Zendesk connections; they now report the correct default risk level * Fixed an issue that prevented Opal from provisioning new users in NetSuite * Fixed an issue where app owners with sync permission could not trigger a sync from the app's import assets page; the sync button was previously shown only to admins, unlike the app detail page * Fixed an issue where app owners received a 403 error when opening the import assets page for apps they own * Fixed an issue where campaign admins did not receive email or Slack notifications when a reviewer was reassigned ### Added * Added "Request on behalf" as a custom role permission for groups, letting a role (or a service user holding one) create access requests on behalf of a team without being an Opal admin or group leader of that team. Requests still go through the normal approval chain, and the role grants no ability to edit group membership * Added a "Created" column to the access rules list with support for sorting by creation date * Added daily and weekly options for scheduling access campaign reminders to reviewers with incomplete reviews ### Bug Fixes * Fixed a crash when requesting all items in a bundle that included a resource with no role selected * Fixed a bug where adding certain resources (such as Databricks service principals, Grafana folders and dashboards, or Azure SQL databases) to a bundle asked you to select a role without showing a role picker * Fixed a bug where a linked Reviewer Slack channel could not be removed from an owner in the owner edit dialog (clicking the X had no effect) * Fixed an issue where browsing a specific resource type in the app catalog (and in the admin app resources table) stopped loading items after the first page, even though the total count was correct * Fixed an issue where the Inventory > Owners page showed an empty table instead of an error state when the owners list failed to load; the page now shows a "Failed to load" alert with a Retry button ### Bug Fixes * Fixed an issue where the Configuration > Service Users page showed an empty list with a count of 0 for service accounts created manually or imported instead of via automation * Fixed an issue where automatic user merging could report a false configuration error, or produce different results across repeat syncs, when the same email address was listed more than once * Fixed a sync failure that could stop a connection from syncing when it contained a very large number of groups * Fixed an issue where the request detail page could show an unexpected error when the request had been reviewed or commented on by a Paladin agent * Fixed an issue where completed access reviews could reopen for reviewers assigned as owning-team admins (all-admins reviewer assignment): the background reviewer sync no longer re-creates a pending review for an item the reviewer has already decided ### Added * Added read-only token scope support for MCP Server OAuth tokens * Added customizable TTL configuration for MCP Server OAuth tokens, replacing the previous hardcoded 1-hour expiry; configurable per-org by admins from the OAuth settings page * Resources backed by Okta apps, GCP resources, Azure resources, Github repos and 27 other apps now display a [button that directly links users to the app from the catalog](/docs/curate-catalog#links-to-apps-in-catalog). * Admins can insert custom links at the app level in the catalog to deep link end users to resources. ### Improved * Improved bulk user deletion to complete reliably for large selections: users are removed and lose all access immediately, with credential and relationship cleanup finishing in the background (progressive rollout) ### Bug Fixes * Fixed a sync failure that could stop a connection from syncing when it contained a very large number of resources ### Improved * On-call schedule changes on a group now record which schedules were added or removed, instead of a detail-less "on-call schedules updated" audit entry * Resource audit events now record when a resource's description is edited, instead of showing a detail-less "resource updated" entry * Toggling a resource's "sync name/description from end system" settings is now recorded as a specific audit event instead of a detail-less "resource updated" entry ### Bug Fixes * Fixed an error that could occur when filtering group bindings or group binding suggestions by a large number of group or connection IDs * Fixed an issue where an Okta group's membership could stop syncing shortly after an access change, until the group was synced manually * Fixed an issue where read-only admins and auditors saw an empty Tags inventory page ### Bug Fixes * Allow Opal Auditor role to access OpalQuery and manage access-review campaigns ### Added * The PagerDuty connector can now import the account-level Owner role and its assignment as an import-only resource (behind a feature flag), making it visible in user access review campaigns. Because PagerDuty provides no API to modify the Owner role, grant and revoke attempts on it report a clear "operation unsupported" status instead of an internal error * Added "Wait Time" column to the "Admin" tab of the "Requests" page ### Bug Fixes * Fixed resource deletion silently failing to delete anything on large syncs ### Added * Added Jira and Confluence integrations; both support users and groups using OAuth 2.0 and an Admin API key for full user discovery * Added the option to restrict reviewer reassignment to managers, asset owners, and/or specific users in campaign settings * Added OAuth App (API Services) authentication support for Okta connections; configure a client ID and private key from the connection settings page — existing connections are unaffected * Access queries can now filter entities by name using a regular expression, for naming conventions that "starts with" and "contains" can't express ### Improved * Reviewer information is now visible in group and resource details modals, showing reviewer avatars with name and email on hover * "Ask Opal" responses in the Risk Center now stream in real time instead of waiting for the full response before displaying * OpalScript validation errors when a save is blocked now appear with line numbers in the Debug panel; successful saves show a confirmation toast * Clicking "Sync now" on an individual Okta group now always re-fetches and reconciles the group's members, even when the membership-freshness optimization would otherwise skip it * The reviewer delegation button is now labeled "Reviewer Delegation" (drawer: "Create Reviewer Delegation"), clarifying that creating a delegation does not require approval ### Bug Fixes * Fixed a bug where campaigns created with an advanced access filter ("users that also have access to asset X") would include more items than the scope preview indicated * Fixed an issue where a user added to a newly created group could be removed from that group in Opal by a sync that read stale membership data from the end system ### Added * Added support for importing AI agents from Okta — by user type, as OAuth/API-service apps, or via automatic detection of agent-like accounts; read-only admins and auditors can now view the connector import panels * Added a "Browse Templates" button to the OpalQuery interface, previously only reachable when no private queries existed ### Improved * OpalScript saves are now blocked when syntax or other static errors are detected, with a clear error message identifying the issue; warnings still save normally * Read-only admins and auditors can now open all three agent-inventory panels — Detect agents, Sync from connectors, and Create agent — to review what each panel does; making changes still requires full admin access * Renamed the Agent form's "Owner" field to "Owning Group"; added an optional "Accountable Sponsor" field to the Agent create/edit form and detail panel; added a read-only "Discovered by" field to the Agent detail panel showing which admin surfaced the agent * Agents inventory now shows each agent's source connector; imported Okta agents now carry richer metadata including creation time, creator, detected harness, group memberships, and granted OAuth scopes * Campaign review decisions (approve, revoke, and change-role) now appear individually in the campaign events tab, showing the reviewer, principal, resource, access level, and any note for each decision * OpalQuery filter menu in Queries and Access Campaign scoping now includes descriptions and search capabilities ### Bug Fixes * Fixed the "Detect agents" convert flow so the error toast no longer incorrectly claims selected identities are "already claimed by another source" when that isn't the case * Fixed an issue where a timed-out Slack request modal or double-clicked re-request button could create duplicate access requests; while an identical request is still pending, a resubmission now returns the existing request * Fixed GCP sync failing to pull BigQuery tables for projects with legacy domain-scoped project IDs (e.g., `example.com:my-project`) * Fixed an issue where saving AWS IAM Identity Center import settings from the app edit page failed with "Failed to update connection" * Fixed a race condition where a slow connection sync could remove a connection user that had just been re-added or modified * Fixed an issue where valid GCP roles couldn't be requested on projects, folders, and organizations when the role's service API wasn't enabled on the resource; role sync now uses the full IAM role catalog for these resource types * Fixed an issue where extending access could still trigger an "access expiring soon" notification; expiration reminders now only fire as the new end date approaches ### Bug Fixes * Fixed an issue where updating the membership of a Slack user group could fail with "failed to enable Slack user group: already\_enabled" when the group was already enabled on Slack * Fixed an error that could prevent user sync from completing for connections where a large number of users were removed at once * Fixed an issue where updating a resource via `PUT /v1/resources` could time out when the resource has many descendants ### Added * Added `GET /v1/users/self` to the public API, allowing API token holders to verify which user their token belongs to * Added `match_remote_name` and `match_remote_description` fields to the `PUT /resources` and `POST /resources` APIs, enabling "Sync name from end system" and "Sync description from end system" to be read and set via the public API and Terraform for groups and resources ### Improved * Admins can now save and apply event filters directly from the filter bar; saved filters apply in place without navigation, the save dialog shows a preview of active filter criteria including resolved entity names, and the manage filters dialog lists each saved filter with its criteria and creator inline * Access request timelines now show the actual error message when an audit ticket fails to sync (for example, when the configured Jira project no longer exists or is missing a supported issue type), instead of a generic "Failed to Load Ticket"; audit ticket creation now also returns a clear, actionable error when the audit ticket project is not configured ### Bug Fixes * Fixed owner group and resource counts timing out on the Owners page for organizations with very large numbers of reviewer assignments * Fixed an error that could prevent owner group counts from loading for organizations with very large numbers of reviewer assignments * Fixed an issue where the service user detail page failed to load for service users with automations (including OpalScript service users and Paladin agents) * Fixed an issue where a single pending access request whose approver owner had no eligible reviewers caused the incoming-requests list and sidebar pending-review count to fail for all users; such requests are now excluded from those views, and the error message now identifies the owner with no reviewers * Fixed an error ("Failed to update resource") when removing a configuration template from a resource or group and editing its request configurations in the same save * Fixed adding a nested group to now show a descriptive error naming the groups involved when the add would create a circular membership, instead of a generic failure message * Fixed membership updates for owners linked to source groups to include indirect group access changes ### Added * Added tag management permissions (read, create, delete, and edit user tags) to scoped admin roles * Added a GCP connection setting to select which leaf resource types (service accounts, buckets, compute instances, Cloud SQL, BigQuery) to discover and import; organizations, folders, and projects are always discovered, and unchecking a type stops discovery and removes previously imported resources of that type on the next sync, replacing the previous "Exclude service accounts from import" toggle * Added launch URLs for resources in Vault, Grafana, GitLab, Salesforce, Okta, Coupa, Oracle Fusion, Workday, Twingate, Datadog, Zendesk, NetSuite, Databricks, Snowflake, Anthropic, OpenAI, and Google Workspace connections, enabling direct navigation to the provider console from resource pages ### Improved * Read-Only Admins can now use Access Comparison; users without admin read permissions see a permission message instead of an empty "No access data found" state * Access Campaigns (beta): users without edit-settings permission now see campaign settings as view-only instead of experiencing silent save failures; save and reviewer-assignment errors now surface the actual error message * Improved access request discoverability: for users who already have access, the access button menu item is now labeled "Request More Access" with an updated tooltip pointing to the menu * Hovering a user, resource, or group ID in the OpalScript editor now shows the full Opal hover card with counts, ownership details, and quick links * Script names in the OpalScript Runs table now link directly to that script in the editor * OpalScript editor now preserves unsaved changes in the browser, so closing the tab or navigating away no longer loses in-progress edits * OpalScript AI button is now always visible in the editor * Bundle create and edit experience now opens in a compact modal instead of a full-screen overlay * Slack User Groups now sync reliably under Slack's API rate limits with automatic retry and backoff; imported groups keep their description in sync with Slack, tags save correctly alongside a configuration template, clearing a group's configuration template no longer errors, and Slack User Groups now show the Slack logo * Transient database connection failures during API authentication now return HTTP 503 with a `Retry-After` header instead of an incorrect 401, 400, or 500 ### Bug Fixes * Fixed an issue where saving SAML settings (including SSO certificate rotation) failed for organizations whose Auth0 connection had more than 50 enabled clients * Fixed an issue where Google Groups (and GKE) connections with import setting "tagged" did not correctly mark groups tagged via membership in the Opal parent group as managed ### Self-hosted only * Fixed an issue where LDAP connections using TLS (TLS Mode enabled or port 636) failed with a base64 decoding error due to unfilled mTLS placeholder values in the Helm chart; LDAPS now works out of the box ### Added * Delegation condition scripts can now read the full request via `context.get_request()`, enabling rules based on the target user, requested resources, reason, and other request details * The [OpalQuery REST API](/docs/opal-query-api) access-path endpoint now supports advanced access filters, letting callers scope results to principals or entitlements that also have access to a specified resource or group ### Improved * Campaign admins can now hide resource, group, and user tags from reviewers via a toggle in the campaign Settings tab * When Paladin is in monitor mode, the request detail page now shows the full agent trace and highlights Paladin's recommended action with a pulsing indicator on the Approve or Deny button * Updating settings on a stopped or ended campaign via the API now returns 400; renaming the campaign is still allowed * Starting a scheduled campaign template via the API now returns an error; templates only launch draft campaigns on their configured schedule * The legacy UAR REST endpoints (`/uars`, `/uar`, `/uar/{uar_id}`) are marked deprecated; use the campaigns API instead, existing integrations continue to work * `POST /v1/campaigns` now requires `configuration.query`; requests without it return 400 ### Bug Fixes * Fixed an error where granting or revoking membership in groups synced from connections that do not support provisioning (incident.io, Rootly) could fail with an internal error * Paladin reviews that fail to start now show as failed and prompt manual review instead of appearing stuck in progress indefinitely * Fixed the global Events page jumping back to the top of the list when opening an event * Fixed an issue where propagation history and status could sometimes be misattributed ### Self-hosted only * Sync CronJobs (`sync`, `sync-daily`, `sync-highfrequency`) now set `activeDeadlineSeconds`, so a hung sync job is automatically failed and cleaned up instead of blocking future scheduled syncs ### Added * Added API endpoint `POST /v1/campaigns/{campaign_id}/start` to start a draft access review campaign * Added API endpoint `POST /v1/campaigns/{campaign_id}/stop` to stop an ongoing campaign, with an option to immediately revoke unreviewed access grants * Added API endpoint `POST /v1/campaigns/{campaign_id}/end` to end a stopped access review campaign ### Added * The public API's ad-hoc OpalQuery endpoint now supports filtering entities by IDP lifecycle status (`hrIdpStatus`), and their admin owner (`entityAdminOwner`). ### Improved * Improved access campaign event history: event descriptions now show which settings changed and include a summary of reviewer assignment changes; the Events tab also refreshes automatically without a page reload * The events viewer now shows the campaign name and a summary of reviewer changes for campaign events * Campaign names in the events viewer now link directly to the campaign * Resource options in entity selector dropdowns now show their app's name, so resources with the same name across different apps can be told apart * The resource and group pickers in the OpalScript "Linked" modal now show entity icons and type labels, matching the test run modal ### Bug Fixes * Fixed a bug that may prevent users from connecting to resources they have access to through non-visible groups * OpalQuery no longer supports filtering users by an "unset"/inactive IDP status; the IDP-status filter matches the synced HR/IDP statuses (Active, Suspended, Deprovisioned, Deleted, Not Found). * Fixed an issue that may cause long syncs on AWS accounts to fail due to expiring session credentials. ### Added * Added REST API endpoint `POST /v1/campaigns` to create access review campaigns; one-off campaigns reject schedule-only fields (`cron_expression`, `recurring_duration_days`) * Added REST API endpoint `PUT /v1/campaigns/{campaign_id}` to update an existing access review campaign, including schedule fields on recurring templates * Added `type: ACCESS_PATH` support to [`POST /v1/queries/run`](/docs/opal-query-api), enabling principal→entitlement path results in addition to NODE queries * Added OpalQuery filter to filter groups and resources by their admin owner ### Improved * Access Campaign scoping now supports advanced access filters ("that also have access to", "that also are accessible by") on Principal or Asset filters, allowing for queries such as "Scope direct access of all users in group X" * OpalQuery's natural-language search and AI-generated titles/descriptions now understand the IDP Status and Entity Admin filters * Improved Okta sync resilience and error reporting: transient Okta API failures during user syncs are now retried automatically, and sync errors include the actual HTTP status and Okta request ID instead of "the API returned an unknown error" ### Bug Fixes * Fixed access-expiration reminders firing up to an hour late (and occasionally after access had already expired) when the access duration was close to a configured reminder window; reminders for accesses granted for approximately a reminder window's length are now suppressed rather than sent late * Fixed the access campaign review table to fill the available page height instead of leaving empty space below the list ### Improved * Improved connection sync error messages to show the underlying remote error instead of "Opal internal error"; the public API now returns specific error codes like `ERR_NOT_AUTHORIZED_TO_QUERY_RESOURCE` where it previously returned `ERR_OPAL_INTERNAL_ERROR` ### Bug Fixes * Fixed resource and group counts on the access review admin page shrinking during a review as access was revoked — they now reflect the full scope being reviewed for the lifetime of the campaign * Fixed two cases in access campaigns where the UI showed stale data until a page refresh: newly created campaigns now appear in the campaign list right away, and the Reviews tab warning icon clears as soon as every item has a reviewer assigned * Fixed email notifications mangling names that contain underscores or other markdown characters. * Fixed a bug in the Entity Select dropdown in OpalQuery that caused text filtering to break * Fixed the public API `GET /access-rules/{access_rule_id}` and `GET /access-rules` endpoints omitting `attribute_selectors` from access rule clauses; Terraform users no longer see a perpetual plan diff on `opal_access_rule` resources that use `attribute_selectors` * Fixed campaign review submission incorrectly showing a success toast on failure — reviewers now see the actual error message * Fixed UI issues around campaign review reassignment: staged choices now stick after a page refresh, and reassigning a review correctly disables further interaction with the item. ### Added * Added new API `GET /v1/campaigns/{campaign_id}` endpoint to fetch a single access review campaign by ID * Added query filters to `GET /v1/campaigns` supporting filtering by name, status, and created/started/ended/stopped time ranges ### Improved * Read-only Opal API tokens can no longer invoke state-changing tools through the MCP endpoints; such calls are now rejected, matching the read-only enforcement already applied to the REST and GraphQL APIs * Admins can now see which delegations are gated by an OpalScript delegation condition directly from the script console * The admin access-campaign page now loads quickly for large campaigns; reviewer progress and scope details fill in a moment later instead of blocking the whole page ### Bug Fixes * Fixed a bug where a Google Groups pull sync would fail with an internal error when a user's only Opal access to a group was via a nested group while the remote reported them as a flattened nested member ### Added * Access Comparison is now generally available. Admins and read-only admins can compare access between two users from the Access Comparison page without enabling a beta flag. * Added new OpalQuery filter in web to filter users by HR IDP Status (Active, Deactivated, etc.). * Added a `GET /v1/campaigns` endpoint for listing Campaigns. ### Improved * Moved OpalScript Secrets and Allowed Hosts settings from the general Settings page to the dedicated OpalScript Resources page. ### Bug Fixes * Fixed Asset Filters in campaign scoping to no longer show User types as options — only resource and group types appear. * Fixed a bug where signing back in after your session expired on a deep link (e.g. a link to a specific group) would drop you on the search page instead of returning you to the page you originally opened. ### Bug Fixes * Tags that have a key but no value now display and save correctly in the query builder's tag filter (previously they appeared as `key:null` and could save an incorrect value). * Fixed several bugs that may block deleting users when the "Remove users from Opal when deprovisioned" setting is turned on. ### Improved * Scrolling and typing in the campaign review inbox is significantly smoother for reviewers with many assigned items. ### Added * Added Tableau integration for syncing users and managing group membership ### Improved * Admins can now select multiple days of the month when configuring a recurring campaign schedule, allowing campaigns to recur more than once per month * Improved access campaign generation speed for organizations with group-based access; campaign progress totals are now exact * Description boxes on asset forms (resources, groups, bundles, owners, access rules, and custom roles) can now be resized vertically, making long descriptions easier to read and edit ### Bug Fixes * Fixed an issue where a feature-flag service outage could cause connection syncs to incorrectly treat feature-flag-gated groups and resources (e.g., PagerDuty on-call schedule groups) as deleted and remove them from Opal; syncing now safely skips these items until the flag is available again * Fixed the access comparison tool to use wider columns for easier reading; the "Add user" dropdown now correctly resets after each selection instead of showing the previously selected user's avatar * Fixed an issue where adding a comment to an access request triggered an unnecessary new Paladin run * Fixed an issue where a newly created Okta user may not get added to groups * Fixed an issue where loading toasts could spin indefinitely. Saving connection settings/credentials on the app Setup page, post-save "now syncing" status, and manual sync errors now resolve with success or the error message ### Added * Added a GCP connection setting, "Exclude service accounts from import" — when enabled, GCP service accounts are no longer imported (including those referenced by IAM policy bindings), and previously imported service accounts are removed from Opal on the next sync, keeping the resource catalog focused on orgs, folders, and projects * Campaign admins can now hide Paladin Insights (AI suggestions) from reviewers via a toggle in the campaign's Settings tab; off by default, so existing campaigns are unaffected ### Improved * Okta group attributes that hold multiple values (string arrays) are now imported as group tags — one tag per value — instead of being silently skipped * OpalScript policies can now read all values of a multi-value group tag: `entity.tags[key]` returns a list when a tag key has more than one value, and a scalar for single-value keys * OpalScript request objects now expose `created_at` and `updated_at` as Unix timestamps, enabling time-based policy logic such as limiting how many requests a user can make in a 24-hour window * Access requests now display the exact requested duration (e.g. "3 hours, 30 minutes") in the Requests Inbox, request detail page, and admin requests table, instead of a value rounded to the nearest hour * Editing an access campaign's settings no longer fires a toast notification for every change — saves are confirmed with a quiet inline "Saved" indicator, consistent with other campaign pages ### Bug Fixes * Fixed an issue where removing certain auto-imported GCP org/folder/project resources from Opal errored with "failed to delete resources" * Fixed incorrect counts shown in the Active and Scheduled tabs on the campaigns homepage for orgs using schedule templates without months configured * NetSuite connection validation now surfaces the underlying error when the role-permissions query fails (e.g. due to a Client Credentials mapping role missing the 'REST Web Services' permission), instead of incorrectly reporting that the 'Opal Integration' role does not exist ### Added * Users now receive a confirmation notification (via Slack, email, Google Chat, or Mattermost) when their access is extended * OpalScript request-review scripts can now make allowlisted HTTP calls and use encrypted secrets to enrich or act on access decisions * Admins can control which external hosts OpalScript can call, from Settings → Advanced and the OpalScript editor's Manage panel * Admins can now rename an existing Paladin agent from the agent's detail page * The HubSpot integration, its teams, and its roles now display the HubSpot logo throughout the Inventory ### Improved * Global Paladin is now a standalone setting under AI Features that stays visible regardless of the "Enable All AI Features" toggle, making it easier to find and configure * The "Access Review Insights" AI setting is now discoverable via settings search * Refreshed the sign-in page with a new logo, rounded card, and a subtle arrow animation on the Continue button * HubSpot connection validations now correctly evaluate token scopes for accounts hosted on regional HubSpot infrastructure (e.g. na2, eu1), and report whether the token includes the optional `settings.billing.write` scope required to deprovision users holding paid seats * Deprovisioning a HubSpot user who is a Super Admin now fails immediately with clear guidance, instead of retrying indefinitely * HubSpot connections now default to a low risk level (matching comparable SaaS integrations) instead of unknown * The public API endpoints `POST /v1/tag` and `DELETE /v1/tag/{tag_id}` now return `403 Forbidden` with a descriptive message when called by a non-admin, instead of a generic `500 Internal Server Error` * Access Campaigns (beta): Improved error surfacing when creating a campaign without sufficient permissions ### Bug Fixes * Fixed a display issue where, after importing the first asset on an app, the assets tab could simultaneously show a "No resources" empty state and the imported asset, with a stale "Showing 1 of 0 asset" count until the page was refreshed * Fixed a bug where the Group Resource Visibility setting on an existing access review schedule always displayed "Strict" and could not be changed * Fixed the access campaign Reviews table sometimes needing a page refresh to keep loading rows while scrolling ### Improved * In the OpalScript "Linked" modal, the selected resource or group now shows its app name, making it easy to distinguish entities that share the same name across different apps ### Bug Fixes * Fixed an issue with Slack action user resolution causing case-sensitive lookups to fail ### Added * Added user provisioning support for Salesforce connections * Added public API support for additional app types — `GRAFANA`, `CLICKHOUSE`, `HUBSPOT`, `ZENDESK`, `OKTA_CIAM`, `ORACLE_FUSION`, `POSTGRES`, `DEVIN`, `ROOTLY`, and `VAULT` ### Improved * Improved performance when paging through large group memberships via the `GET /groups/{group_id}/users` API * The OpalScript console version dropdown now shows when each version was saved * Resource descriptions now support multiple lines: you can enter line breaks when editing, and existing multi-line descriptions (including those set via API/Terraform/sync) display with their line breaks preserved * Okta directory syncs now surface a clear not-authorized sync error telling admins to grant System Log read on the token's admin role, while allowing the rest of the sync to commit normally ### Bug Fixes * Fixed a 500 error when listing groups via the API if a reviewer referenced a deleted user * Fixed a bug that prevented showing groups that had deleted member groups * Fixed access request durations displaying as a rounded-up value in activity events and request-configuration text (e.g., a 90-minute request now shows "1 hour, 30 minutes" instead of "2 hours") * Fixed an issue in code editors where the find widget (Cmd+F) could not be closed with the mouse due to a tooltip covering the close button * Fixed the end-user catalog incorrectly showing "Pre-approved for access" for users who are not actually eligible for a resource or group's auto-approval configuration * Fixed issue that caused Slack actions to fail for accounts associated with multiple users * Fixed a 400 error when editing request configurations on resources whose default (non-requestable) configuration had a legacy max duration of 0 ### Improved * Improved performance of the access campaigns page for organizations with very large campaigns * OpalScript modules now use cleaner, more idiomatic names — `entity`, `requests`, `risk`, `access`, `notifications`, `time`, and `tickets` (the `lib` suffix is dropped, and request history is consolidated under `requests`); existing scripts keep working unchanged as the previous names remain as aliases * Removed the status slug from Access Campaign detail page URLs ### Bug Fixes * Fixed a bug where the request propagation stepper showed as incomplete after temporary access expired * Fixed an indefinitely spinning "Propagating access changes" status toast ### Bug Fixes * Fixed an issue causing the "last successful sync" timestamp to go stale for organizations with a large number of groups * Fixed a recurring internal error for read-only Cursor connections; expired group-based access to Cursor org resources now resolves correctly without spurious errors * Fixed inconsistent results when filtering by users on /inventory/owners ### Bug Fixes * Fixed a multi-tenant isolation issue in the agent-request context API so that an organization administrator can only set agent-request context (the on-behalf-of principal and purpose) on requests and users within their own organization ### Added * \[Beta] Added support for connecting Opal to AWS GovCloud * \[Beta] Campaign reviewers can now be set as live pointers to the principal's manager or the asset's owners * Added a campaign lifecycle timeline — clicking a campaign's status badge now shows a history of key lifecycle events (created, started, stopped, and ended) * Added async exports to the Events page ### Improved * Template campaign settings now include all configurable options — notifications, revocation policy, reminders, and reviewer assignment — matching what's available for regular campaigns * Improved performance of the `PUT /v1/configuration-templates` API * Added an unsaved-changes warning in the OpalScript editor to prevent accidentally losing work when navigating away * Improved the Inventory page so that table sort preferences are preserved when navigating away and back ### Bug Fixes * Fixed a bug where Slack request notifications showed a rounded duration instead of the exact requested duration * Fixed a bug where custom request notifications could not be removed from configuration templates via the UI * Fixed a bug in the `PUT /v1/configuration-templates` API where custom request notifications were ignored * Creating a configuration template with an incorrect combination of `Visibility` and `VisibilityGroupIDs` now correctly returns a `400` ### Self-hosted only * Async exports on the Events page require an S3 bucket to be configured; without it, synchronous exports continue to work as before ### Added * \[Beta] Added the ability to filter by no reviewers on the access campaign reviewer assignment page ### Added * \[Beta] Added a dedicated Paladin section to the admin navigation with a full agent management page for creating and configuring Paladin agents, plus a redesigned access request review experience with a clear recommendation, key evidence, and a per-signal scorecard with a full agent trace * \[Beta] Added decision speed configuration to Paladin agents — choose between Fast mode (Haiku + Opus) for quicker reviews or Thinking mode (Opus for all steps) for more thorough analysis * Extended the "Discover SoD Violations" button to requestable group pages, in addition to resource pages * The command bar (⌘K) now shows pending count badges next to Requests and Access Reviews navigation items ### Improved * OpalScript errors during request processing now leave a comment on the request explaining the failure, instead of failing silently * Campaign review filter dropdowns now show the source connection icon and a tooltip for entries that share a name across different integrations * Improved performance of the [PUT /groups](/reference/updategroups) API and Terraform group updates * Improved performance of the `PUT /v1/resources` API, Terraform resource updates, and bulk editing of resource configurations ### Bug Fixes * Fixed a bug where search queries with leading or trailing spaces failed to return correct results * Fixed a bug where deleted groups and resources in campaign review tables showed no tooltip or indicator that the entity had been deleted * Fixed a bug where the [PUT /groups](/reference/updategroups) API emitted incorrect audit subevents * Fixed a bug where resource request configuration updates emitted incorrect audit subevents ### Added * \[Beta] Added ability to delete draft campaigns from the campaign detail page * \[Beta] Added bulk comment support when reviewing access campaigns — apply a single note to all selected items during bulk approve or revoke ### Improved * Improved Okta group sync to remove stale tags when Okta group profile attributes change or are removed ### Bug Fixes * Fixed a bug where editing an OpalScript while a request was pending approval could cause approved actions (such as ticket creation) to run twice when the request resumed * Fixed a bug where previewing reviewers during access review campaign creation could fail to load when using an "Entity Admins" or "Entity Approvers" reviewer policy * Fixed a bug where Owner groups that source their membership from a group were not updated when a user was granted access to that group via an access request ### Added * Added Access Duration Type filter to campaign scoping, allowing campaigns to target Permanent Access Only or Time-Bound Access Only * \[Beta] Added request history section to the campaign item review sidebar ### Improved * The reviewer picker in the campaign overview now highlights on hover and shows a pointer cursor, making it clear the cell is interactive * The GET /v1/groups, GET /v1/groups/, GET /v1/resources, and GET /v1/resources/ endpoints now return service\_user\_ids for each reviewer stage in their request configurations, matching what can already be set via the corresponding PUT endpoints ### Bug Fixes * Fixed a bug where deprovisioned users were still being sent access request notifications ### Added * Added native [Zendesk integration](/integrations/zendesk) to manage access to [Zendesk](https://www.zendesk.com/) groups, organizations, and custom roles (Enterprise only). ### Improved * Grouped campaign review items now load automatically as you scroll, instead of requiring a manual "Load more" click ### Bug Fixes * Fixed a bug where creating requests from Slack after search would sometimes fail with custom resources and Okta apps * Fixed a bug where reviewer assignments in grouped campaign review items would not update immediately after being changed ### Added * Added `approve_request` and `deny_request` tools to Admin User Provisioning MCP Server * \[BETA] Added support for sending reminder notifications to an access campaign's reviewer's manager. * Added a button to the resource details page which surfaces SoD violations using OpalQuery. * Custom connectors now receive a `custom_fields` array in provisioning calls (`POST /resources/{id}/users` and `POST /groups/{id}/users`), allowing connectors to use request context to drive their provisioning logic ### Bug Fixes * Fixed a bug where `POST /v1/owners` returned HTTP 500 instead of 403 when the caller lacked owner-creation permissions * Fixed a bug where duplicate reviewer rows would show up for requests for resources with access levels * Fixed Opal MCP tools (such as search groups) that were incorrectly returning errors after a recent tool filtering change ### Bug Fixes * Fixed Opal MCP tools (such as search groups) that were incorrectly returning errors after a recent tool filtering change. ### Bug Fixes * Fixed an issue where approving requests with duplicate requested items could fail ### Added * Added metrics to the Requests Admin tab for pending requests, closed requests, approval rate and time to approval * \[Beta] Added the ability to filter for requests reviewed by Paladin to the Requests Admin tab * \[Beta] Added timezone support for campaign deadlines — admins can set a timezone when configuring a deadline, and reviewers see the expiration in both the campaign's timezone and their local time * \[Beta] Added the ability to suggest new roles during access campaign reviews * \[Beta] Added additional context for review items when expanding a review row * \[Beta] Added auto-saving for unsubmitted reviews in access campaigns * \[Beta] Added the access campaign's scope to the campaign's generated PDF reports. ### Improved * Improved performance on Requests page * Improved OpalQuery natural language generation to use structured output formatting, eliminating errors that previously occurred when the AI model returned extra text or formatting alongside the query ### Bug Fixes * Fixed a bug where adding or removing users from an Owner without appropriate permissions would no-op instead of returning an error * Fixed a bug where approving a request with multiple access levels for the same resource would sometimes result in the wrong access level being pushed * Fixed a bug in UAR PDF reports where the "Total in Review Scope" count for users was off by one * Fixed a bug where roles created under a push-only app's asset were not visible in the roles table until the page was hard-refreshed * Fixed incorrect singular/plural wording on the My Campaigns stat cards and welcome text ### Added * Added REST API endpoints at `/v1/event-streams` for managing event streaming connections (create, list, update, delete). ### Improved * Improved error messaging for syncs on Workday connections that are missing required permissions. * Read-only admins can now create, edit, share, and delete saved queries in OpalQuery, matching what was previously available only to super admins. ### Self-Hosted Only * Added a setting to enable LaunchDarkly's Relay Proxy in the KOTS admin console. This allows feature flag evaluations to continue working during a LaunchDarkly outage. * Remove the authAudience param from our helm values. This is not necessary for onprem customers to set. ### Bug Fixes * Fixed the user's `Title` field to be read-only when an IDP/HRIS is connected, matching the existing behavior for the Manager field. ### Added * Added Resources and Groups tabs to the configuration template detail page, showing every resource or group the template is applied to ### Bug Fixes * Fixed a PDF generation failure for UAR reports. ### Added * Launching Queries, an admin interface to query your access graph and discover Separation of Duties violations. See [docs](https://docs.opal.dev/docs/opal-query) for more information. ### Improved * Improved Okta attribute mapping: admins can now select from a searchable dropdown of all available Okta profile attributes instead of manually entering attribute keys * Improved \[BETA] OpalQuery nested condition groups: condition nesting is now limited to 3 levels deep * Improved handling of ticket propagation errors by emitting ticket propagation or sync failures to the audit log ### Bug Fixes * Fixed the "X Filters Applied" count in \[BETA] OpalQuery to correctly include negation filters ("is NOT", "does NOT contain", etc.) * Fixed a bug where setting message channels on unmanaged resources or groups via the API would return a silent error instead of a 404 * Fixed login error message to correctly display "access denied" instead of referencing MFA when login is denied by your identity provider (e.g., email domain not allowlisted) * Fixed a blank request banner when requesting access to a non-requestable resource or group — users now see an explanatory message * Fixed a bug where audit tickets sent to Notion would silently fail to create ### Improved * Improved role requestability configuration by clearly displaying which roles are not requestable due to the default request configuration ### Bug Fixes * Fixed a bug where searching for roles when adding access doesn't work * Fixed a bug where certain GCP roles such as `cloudbuilds.Builds.Approver` would display as `cloudbuilds.Builds`. Please allow a full sync cycle for the names to update. Roles displayed in existing access may continue to show the old name until revoked * Fixed a bug with search when filters were applied * Fixed a bug where ingestion from okta could miss users in very rare cases ### Added * \[BETA] Added Role Name filter to OpalQuery in 'Has Access To' blocks ### Improved * Improved the error page shown when navigating to a resource, group, or other entity that doesn't exist or isn't visible — it now displays a clearer message with an icon indicating the item may not exist or you may not have permission to view it ### Bug Fixes * Fixed a bug where request extensions would not be available after access was revoked and granted again * Fixed custom role read permission to return entities with limited visibility restrictions in the API ### Improved * Added search to the "Item Type" filter in \[BETA] OpalQuery — type to filter down the list of resource, group, and user types instead of navigating nested submenus * Improved \[BETA] OpalQuery display for read-only admins: filter values now render at full contrast and non-functional action buttons are hidden ### Bug Fixes * Fixed a bug where historical requests for non-requestable items caused a 500 error * Fixed a bug where syncs triggered by users could sometimes fail ### Added * Added [`POST /queries/run`](/docs/opal-query-api) API endpoint for ad-hoc OpalQuery execution (currently in Beta). Currently supports `NODE`-type queries returning paginated lists of users, resources, and groups matching flexible filter criteria, including entity type, name, tags, IDs, imported app, access levels, and relationship filters (`isAccessibleBy`, `hasAccessTo`) ### Improved * Improved performance on the Catalog page; user counts are now more accurate ### Improved * Improved loading time for the Ongoing tab under Access Reviews * Moved editing user details to the Details tab ### Bug Fixes * Fixed a bug where resources could not be set to auto-approve in some cases * Fixed 500 error when scoping access reviews by entity type ### Bug Fixes * Fixed a bug where a non-functional feature would erroneously display on request review * Fixed text overflow in the Risk Center "Suggestions" cards when resource or group names were long No user-facing changes in this release. ### Added * Added support for matching access rule conditions by HR/IDP status, enabling admins to target users based on their HR or IDP status alongside existing tag-based selectors * Added `userlib.get_requests`, `assetlib.get_requests_for_resource`, and `assetlib.get_requests_for_group` to OpalScript, enabling scripts to query request history for users, resources, and groups * Added `duration_minutes` argument to `actions.approve` and `actions.comment` in OpalScript, enabling scripts to override the access duration when approving or commenting on a request * Added native [Datadog integation](/integrations/datadog.mdx), to provision and manage access to [Datadog](https://www.datadoghq.com/) roles and teams ### Improved * Improved settings search to now include AI feature toggles, notification settings, and danger zone settings (read-only mode, dry-run mode, warning banner, delete organization) ### Bug Fixes * Fixed a bug where the organization accent color picker would not close when clicking outside of it * Fixed group name cells showing as skeleton or emdash in the resources tab of access reviews * Fixed more info modal not rendering for group-group access reviews ### Added * Added request status filter to the Admin Requests table, allowing admins to filter by Pending, Approved, Denied, or Canceled status ### Bug Fixes * Fixed MCP tool responses to include structured content as required by the MCP spec, unblocking strict clients such as Cursor that previously received `-32600` errors * Fixed a bug in the public API where creating access requests for multiple groups or resources incorrectly assigned all items the same ID * Fixed the Admin Token edit box in the GitHub connection UI * Fixed GitHub SAML user email ingestion to use the correct email attribute instead of a non-deterministic username field ### Improved * Made the GitHub Enterprise Admin Token optional; if not provided, team syncing will be skipped ### Bug Fixes * Fixed a bug where requesting access to global impersonation resources would fail with "failed to create request" * Fixed a bug where creating a GCP BigQuery Table resource via the API would return a 500 error * Fixed a bug where connecting the Paladin Slack app would fail when the workspace was already connected to the regular Slack integration ### Added * Added `entitylib.get_user`, `entitylib.get_group`, and `entitylib.get_resource` to OpalScript, enabling scripts to retrieve non-PII information and tags for users, groups, and resources ### Bug Fixes * Fixed a bug where forfeiting access to a single role on a group would forfeit access to all roles on that group ### Added * Added \[BETA] support for negation operators (is NOT, does NOT contain, etc.) in OpalQuery ### Bug Fixes * Fixed a bug that prevent adding groups to access rule groups * Fixed a bug where attempting to add multiple member groups to a group would sometimes fail with GitLab groups * Fixed a bug where the inventory user view would fail to display appropriate roles for indirect access ### Added * Added Admin Access Investigation and Auditing MCP to Opal Web under `/mcp/admin-auditing`, enabling AI-assisted access investigation and audit workflows * Added `ticketslib` to OpalScript, enabling scripts to create and fetch support tickets in connected ticket providers * Added `ticketslib.comment_ticket` to OpalScript, enabling scripts to comment on remote tickets * Added `ticketslib.close_ticket` to OpalScript, enabling scripts to close tickets in a connected ticket provider ### Improved * Updated the Extend Access button in Slack and Opal UI to only appear when the access duration is within the configured extension window, replacing previous behavior where it appeared immediately after provisioning regardless of duration ### Bug Fixes * Fixed a GitHub connection issue where the app name was silently cleared when saving other connection settings * Fixed the GET `/groups` API response example to include all `Group` fields such as `name` * Changed Twingate resources to be non-requestable; access should be requested via Twingate groups instead * Changed Twingate Synced groups (IdP-managed, e.g. Google Workspace) to be correctly imported as read-only; existing connections must be reconnected to pick up the correct group types ### Added * Added time module to OpalScript, enabling time- and duration-based approval decisions (e.g., auto-approve requests for access durations under a threshold) ### Bug Fixes * Fixed a rounding error on the ongoing access review page where completion percentages were rounded incorrectly * Fixed an issue that may prevent propagating access to GCP for users whose GCP accounts do not match their Opal primary email * Fixed an issue that prevented manually triggered sync jobs from executing * Fixed an issue that prevented assigning non-requestable resources to groups ### Improved * Improved API validation to reject request configurations that reference invalid roles or group remote IDs ### Added * Added `actions.pause(durationInMinutes int)` to OpalScript - allowing for OpalScripts to be paused and execution to be resumed from that point safely. * Added `actions.poll(callable int, int)` to OpalScript - allowing for OpalScripts to poll a condition evaluated in a function until it returns true. * Added native [Grafana integration](/integrations/grafana) to manage [Grafana](https://grafana.com/) folders, teams, and dashboards for all environments, and roles for Enterprise/Cloud environments. ### Bug Fixes * Fixed a bug where only having indirect access to a group would result in Opal displaying no access. ### Improved * Added support for creating multiple Databricks connections with the same account login URL. * Update the `PUT /resources` API to support setting `parent_resource_id`, allowing customers to programmatically assign parent resources when managing GCP folder and project hierarchies via the REST API or Terraform provider. ### Bug Fixes * Fixed a pagination issue when pulling groups in Twingate integration. ### Added * Added native [Twingate integration](/integrations/twingate), supporting provisioning & deprovisioning of [Twingate](https://www.twingate.com/) users and managing resources & groups. ### Improved * Renamed "Identity" to "Principal" in the Risk Center for consistency with the rest of the product. ### Bug Fixes * Fixed an issue where users with multiple GitHub connections could be redirected to a 404. Now, users with multiple GitHub orgs have a dropdown to pick which org to connect to. ### Improved * Bulk expiration actions now default to "Revoke Now" instead of "Expire in 7 days". ### Added * Added "Revocation Timestamp" and "Revoked By" columns to the access review resource users CSV export * Added "Entity Type" filter to OpalQuery to filter results by user, resource, or group ### Improved * Improved webhook HMAC signing key generation to use cryptographically secure randomness * Updated default org notification settings to only apply to users who have not explicitly configured their notification preferences ### Bug Fixes * Fixed an issue where requested items and reviewers in the access request view could visually reorder on re-render * Fixed Snowflake sync failing when the role hierarchy includes inaccessible roles (e.g., Snowflake-managed schema roles) ### Bug Fixes * Fixed an issue where requesting access to certain resources would use the wrong app context, causing incorrect navigation in the access request flow. ### Improved * Simplified the admin sidebar navigation — Templates, Linked Groups, and Configuration (formerly Organization Settings) are now top-level items instead of being nested under a collapsible menu * Updated request configuration settings to display the organization's maximum duration default, with an improved layout featuring a more accessible add button, auto-expanding default config, and clearer descriptions * Updated user select dropdowns to show the selected user's avatar instead of a generic icon * Improved the behavior of the "Extend Access" button in Slack expiry notifications. Previously, clicking the button would extend your access but leave the button visible — which could cause confusion or trigger a duplicate request if clicked again. The button is now removed immediately after a successful extension, replaced with a confirmation showing the new expiry time. If a notification includes buttons for multiple resources, only the clicked button is removed; the rest remain available. ### Bug Fixes * Fixed group details modal to correctly display the configured IDP alias name instead of the raw group name when viewing a group in the context of a specific app resource * Fixed new request buttons not properly linking the App and resource being requested ### Added * Added organization setting to suppress expiry notifications for non-requestable assets ### Improved * Batched expired access notifications for groups ### Bug Fixes * Fixed an issue where extending a request displayed the wrong duration * Fix Azure validation to reflect updated virtual machine permission name change to 'microsoft.compute/virtualmachines/read' * Fixed an issue where the Catalog overlay would repeatedly interrupt the forfeit dialog making it difficult to click the forfeit button ### Added * Added an Opal-hosted admin user provisioning MCP at `/mcp/admin-user-provisioning` * Added an Opal-hosted End User MCP at `/mcp/end-user/` * Admins now receive in-app notifications when connection syncs encounter errors, with a direct link to the event details page * Added default sorting by name to all role dropdowns * Added a `Never` option to "Action Reviews When" in access review configuration to allow for audit-only access reviews ### Improved * Added `Retry-After`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `X-RateLimit-Limit` headers to API responses * Custom connector deprovisioning now includes the user's profile attributes, manager attributes, Opal UUID, and tags in the `DELETE /users/{user_id}` request body, giving connector implementations richer context when removing users * Improved notification handling to no longer notify users on loss of indirect access * Increased timeout for org webhooks / push-only apps to 10 seconds ### Bug Fixes * Fixed a bug where GitHub and GitLab connections would show no accounts, despite having accounts connected * Fixed app validations for NetSuite to show the correct required permissions * Fixed a bug where a manager's delegate approving an access request was not immediately recognized as a manager approval, leaving the request stuck in a pending state until a background job processed it ### Improved #### API * Updated the [`GET /v1/requests`](https://docs.opal.dev/api-reference/requests/get-requests) endpoint to support `resource_id` and `group_id` query parameters for filtering requests for a specific resource or group * Updated the [`GET /v1/groups`](https://docs.opal.dev/api-reference/groups/get-groups) endpoint to support filtering by a single `group_id` ### Bug Fixes * Fixed a bug where the custom access request template builder would error silently ### Bug Fixes * Fixed a bug where the NetSuite app could not be created during setup due to a response parsing failure ### Added * Added native [NetSuite](https://docs.opal.dev/integrations/netsuite) integration, enabling organizations to manage access to NetSuite roles directly from Opal ### Improved * Updated object ID filter to automatically query instead of on enter ### Added * Added risk sensitivity lookup functions to OpalScript for request review workflows — invoke via `assetlib.get_resource_risk_sensitivity(id)` and `assetlib.get_group_risk_sensitivity(id)`, returning risk levels (`"UNKNOWN"`, `"NONE"`, `"LOW"`, `"MEDIUM"`, `"HIGH"`, `"CRITICAL"`) * Added organization settings that allow Admins to globally suppress certain notifications by type (e.g., when performing bulk operations) ### Improved * Added `tag_ids` filter parameter to the [`GET /users`](https://docs.opal.dev/api-reference/users/get-users), [`GET /groups`](https://docs.opal.dev/api-reference/groups/get-groups), and [`GET /resources`](https://docs.opal.dev/api-reference/resources/get-resources) endpoints, allowing callers to filter results to only records with any of the specified tags applied (note: `GET /resources` previously accepted a singular `tag_id` parameter) ### Bug Fixes * Fixed a bug where provisioning would not occur when a user was added to a group via access rules * Fixed a UI bug where actioned requests threw an error even when actions were successful * Fixed a bug where the "More Info" panel in access reviews showed "You have never been approved for..." instead of the correct user's name when the reviewed user had been deprovisioned * Fixed a bug where a service user approval may be ignored when multiple service user approvals are present * Fixed a bug where the GitLab connection would fail to create in some cases ### Bug Fixes * Fixed a bug where service user and automations dropdown in  approval stages shows regular opal users ### Improved * Improved Google Chat and Slack notifications to include "Approved by \" in request approval notifications ### Added * Added setting to configure a global maximum on the number of times access can be extended for assets with "Allow extensions" enabled, requiring users to submit a new request once the limit is reached ### Bug Fixes * Fixed a bug that prevented manually invited users from receiving Opal invite emails ### Self-hosted Only * Note that this release has a data backfill that will run on deployment. You may see the upgrade process take a few minutes longer than usual. ### Added * Enabled import and management of GitHub custom org roles. * Added hotkeys to submit requests: Control+Enter and Command+Enter (macos) ### Bug Fixes * Fixed an issue that caused the UI to incorrectly display an error when stopping an Access Review with the "Revoke all Unreviewed Access Grants" setting enabled, despite the operation succeeding. * Fixed confusing behavior with the browser back button in the catalog, inventory, and search pages: pressing back now closes any open modal rather than reopening previously dismissed ones or navigating away unexpectedly ### Self-hosted Only * Fixed handling for the `env.forceAllowCreateOrgs` helm variable to now correctly enable creation of multiple orgs in a self-hosted environment ### Bug Fixes * (Self-Hosted Only) Fixes a bug that may block redis from rolling out correctly in KOTS-based onprem environments. ### Bug Fixes * Fixed a bug on the admin requests page where multiple search filters were not being combined correctly. * When creating a Github Enterprise integration, users will now be redirected to app install after completing configuration. ### Bug Fixes * Fixed rendering issues with Okta and Google Groups icons. ### Added * Added [REST API endpoint](https://docs.opal.dev/reference/deleteFirstPartyAPIToken) to delete first-party API tokens by ID * Added [REST API endpoint](https://docs.opal.dev/reference/listFirstPartyAPITokens) to list first-party API tokens with filtering and pagination * Added ability to assign service users as reviewers on groups via REST API * Added `CUSTOM_CONNECTOR` as an app type filter in the REST API ### Improved * Improved inventory page loading speed ### Bug Fixes * Fixed a bug with multi-select filter * Fixed a bug where removing resource tags would fail * Fixed a bug where provisioning would fail when a group was added to a resource in a different app * Fixed a bug where sync fails to remove highly-nested GCP resources ### Self-hosted only * Updated on-premises deployment process to integrate more tightly with Helm (requires KOTS v1.124.0 or later). Note: There is a [known issue](https://docs.replicated.com/release-notes/rn-app-manager#11291) with KOTS v1.129.1, which can be resolved by upgrading to v1.129.2 or later ### Bug Fixes * Fixed events export to include events that have no sub-events ### Self-hosted only * Fixed an issue that could prevent installing Opal in databases with existing schemas ### Added * Added support for shift-click on Event Streams ### Improved * Added loading state to the request cancel button * Renamed the users table to accounts on the inventory apps page ### Self-Hosted Only * Cleaned up duplicate `user_tags` entries — note this upgrade may be slow if you have more than 500K user\_tags ### Improved * Improved stability when starting access reviews with more than 100k items ### Self-hosted only * Added `latency_ms` field to HTTP logs for instrumentation ### Bug Fixes * Fixed event type selector on events page to allow for multi select ### Added * Added functionality to test which specific request configuration a user falls into for a specific resource or group * Added the ability to search for assets using their remote ID in addition to the asset name * Added search to the owner resources and owner groups tabs * Added custom search placeholder below the input on the search page * Added new expiration details when a user extends their access through Slack ### Improved * Updated Slack and Google Chat notifications about expired or soon-to-expire access to no longer show a request button on items that are non-requestable to the user at the time of send * Added `access_level_remote_id` query parameter to the `DeleteGroupsContainingGroups` REST API endpoint (no UI yet) ### Bug Fixes * Fixed a bug where exports could occasionally fail for non-admin users * Fixed a bug where the asset count displayed did not match the asset count shown in the tab * Fixed a bug where nested group access would be treated as direct access upon sync ### Self-Hosted Only * Improved performance by adding a new database index — note this release may take longer than usual to install due to index creation * Added a new index for the UI notifications table ### Improved * Updated UX for linking and creating audit channels to be clearer ### Added * Added support for setting secrets via secretKeyRef in Helm deployments (Self-hosted only) ### Bug Fixes * Fixed an issue that could block adding users to groups with access to unmanaged resources * Fixed 'Load more' button not loading additional results in group search dropdown under request configurations ### Added * Added role sorting to User Review and Group Review tabs on UARs ### Bug Fixes * Fixes a locking issue that may cause incorrect "Access Added" notifications during a sync for users who've recently been added to a resource. ### Added * Added user\_email field to event streams and CSV exports alongside existing user\_name and user\_id fields for enhanced user identification ### Bug Fixes * Fixed an issue where PagerDuty on-call schedules without escalation policies weren't properly syncing to Opal * Fixed 404 errors occurring for resource-only access reviews ### Added * Added events for access rule failsafes being triggered ### Bug Fixes * Fixed a bug where group-group propagations would erroneously display as successful * Fixed a bug where group-group remediations would always time out * Fixed a bug where removing group-groups after a failed propagation would fail * Fixed a bug where custom connector user provisioning would miss user tag information in the payload ### Bug Fixes * Fixed the email filter on the `/user` endpoint to be case insensitive ### Bug Fixes * Fixed a bug where an OpalInternalError was thrown during sync if a user gained direct access to a group they already had indirect access to * Fixed a bug where users received access expiry notifications when they were actually removed due to an access rule * Fixed bug for Jira Cloud where not all Jira projects were shown for ticket linking ### Added * Added connect button to request page once approved * Added app name to request completed notifications ### Bug Fixes * Fixed a bug where listing sessions for a resource would fail ### Fixed * Fixed a bug where listing sessions for a resource would fail ### Added * Added API endpoint for improved service integration * Added setting under Organization Settings > Authentication to restrict API / token based access to specific IPs or IP Ranges ### Improved * Improved performance on the search page * Improved performance on app page in catalog * Improved API access control checks for non-admins * Adding a service user to a request configuration with pending requests will now execute their automations ### Bug Fixes * Fixed issue where assignment by approvers in UARs was not working * Restricted tag creation and deletions to admins only in API ### Added * Added support for scoped role permissions to all remaining [API endpoints](https://docs.opal.dev/api-reference) * Added name, title, team, and manager sort options to user resource UARs * Added name, title, team, and manager sort options to user group UARs * Users with no roles in the iLevel connection are now automatically deactivated * Added setting to automtatically cancel stale requests under Organization Settings > Access Requests ### Improved * Improved performance when typing search queries in catalog * Newly created Access Reviews now default to not allowing self reviews ### Bug Fixes * Fixed bug in Slack audit channel creation modal when connected directly to a workspace within an enterprise grid * Fixed bug in inventory owners resources and groups tab where owners added as reviewers in multiple stages were double counted * Fixed bug where indirect access assignments were missing the Role field if the access was assigned via a group-group * Fixed bug with access request notifications showing incorrect roles when requesting multiple roles for the same resource ### Added * Added setting under Organization Settings > Advanced to set the Fiscal Year Start Month for scheduled UARs and future reporting features ### Improved * Newly created users now use default organization settings for notifications instead of having all notifications on by default ### Bug Fixes * Fixed bug where template quarters in scheduled access reviews were off by 1 ### Self-Hosted Only * Improved performance on Catalog App Detail page (released to cloud customers in 1.1101.0) ### Bug Fixes * Fixed a bug where Okta apps and roles could fail to sync in some cases * Fixed bug where custom Opal roles with create bundle permissions could not create bundles in the UI * Fixed sporadic issues with frontend page loads after an Opal deploy * Fixed bugs where some access levels for a resource would be unavailable in some cases * Fixed intermittent bug causing request created notifications to not update ### Improved * Access levels that are targeted in a request configuration will now be automatically made available for requests * Improved performance on the request screen ### Added * Added support for additional group types with nil metadata, including Databricks, AWS SSO, Tailscale, Okta, PagerDuty, and Incidentio groups * Added `duration_minutes` and `access_level_remote_id` fields to the API when giving groups access to containing groups ### Improved * Improved workspace count synchronization on Slack Enterprise Grid connections to reflect remote state ### Bug Fixes * Fixed bug where member groups of containing groups still appeared after successful deletion ### Bug Fixes * Fixed an issue where custom connectors could create resources with empty names * Fixed labeling issue on Moderate/Full Visibility UARs * Fixed legacy bundle links ending in /overview * Included last synced text on the sync items button for app import ### Improved * UAR visibility options now apply to reviewer assignment as well as reviewer read ### Bug Fixes * Fixed issue where failed attempts to revoke access granted via requests were retried, and duplicate propagation tickets were created on failed propagation after ticket closure when ticket propagation is used * Fixed rendering race condition where scoped roles sometimes rendered ALL instead of the scope ### Improved * Accounts provisioned outside Opal are now synchronized into Opal within 15 minutes * Improved performance on Catalog App Detail page ### Added * Added batch access expiring soon notifications ### Bug Fixes * Fixed a bug that prevented creating some requests from Slack for resources that require MFA ## Added * Added the ability to customize the permissions that owners have on the entities they are admins of. ### Improved * Added previous request reason to quick re-request flow for improved context ### Bug Fixes * Fixed broken link rendering for button in UAR reminders * Fixed issue preventing updates to group resource expiration dates * Fixed custom owner permissions behavior where granting "Edit Assignments" incorrectly granted the ability to edit group and resource settings. Owners with only "Edit Assignments" permission can now add/remove users from groups and resources as expected, but cannot modify other settings unless explicitly granted "Edit Settings" permission ### Bug Fixes * Fixed broken links in integrations documentation ### Removed * Removed the ability to view unmanaged resources when viewing a user * Removed the ability to view unmanaged groups when viewing a user * Removed the ability to view unmanaged groups when viewing a resource * Removed unmanaged resources from the group overview page ## Improved * Improved load performance on the Catalog → App page ## Bug Fixes * Fixed issue where connected identities were incorrectly showing as Not Connected in user settings screen ### Added * Added support for self-signed certificates for TLS LDAP connections * \[Cloud only] LDAP requests will now contain a certificate signed by Opal's CA, which can be used for mTLS connections to LDAP via a proxy ### Improved * Removed 30 day duration limit for scheduled access reviews * Improved LDAP group sync performance and handling * Improved LDAP sync handling ### Added * Added setting for Gitlab apps to include or exclude forked projects when Auto-Import All is enabled * Added setting under Organization Settings > Advanced that allows all users in an organization to create a PAT for use with Opal APIs * Added email and Slack notifications for Access Rules failsafe triggers (sent to Admins or org error owners as configured in organization settings) * Added organization setting under authentication to enable automatic logins for users ### Improved * Updated the oncall schedules modal under Pagerduty and Opsgenie integrations * Existing Gitlab apps will continue to include already imported forked projects, with the option to exclude them from future auto-imports when the new setting is enabled ### Bug Fixes * Fixed bug where report generation was failing if stopped by user was not in the UAR ### Self-hosted Only * Upgraded Redis to 8.4.0 ### Added * Added the ability to customize owner create/update/delete permissions in custom roles which were previously restricted to admin only * Added org setting under Access Requests that allows delegation to all users in an organization (by default this is off and delegations can only be created for a user's direct reports) ### Bug Fixes * Fixed 400 error when creating Google GKE Groups * Fixed request API endpoints returning status code 500 instead of 404 for bad request errors * Fixed bug where creating a request through the API with an invalid resource ID succeeded (deprecated the resource ID field when creating requests as it will now be autopopulated based on the resource provided) ### Deprecated * Deprecated `access_level_name` field when creating requests via API; this is now autopopulated based on `access_level_remote_id` ### Bug Fixes * Fixed hyperlinks on Linear tickets to Opal requests ### Added * Added ability to create Devin AI (Cognition) app and manage roles and organizations through Opal ### Improved * Improved handling for viewing requests for deleted users in UI and through the API * Improved performance for requests table * Avoided hard refresh for UAR overview stats after a review is submitted ### Bug Fixes * Fixed exporting requests for a single day returning no results * Fixed 500 error when creating Google GKE Group through [API](https://docs.opal.dev/api-reference/groups/create-group) and Terraform * Fixed issue with access review report generation where it would crash for large organizations ### Deprecated * Deprecated group binding suggestions ## Added * Added the ability to customize owner create/update/delete permissions in custom roles which were previously restricted to admin only * Added org setting under Access Requests that allows delegation to all users in an organization (by default this is off and delegations can only be created for a user's direct reports) ## Bug Fixes * Fixed 400 error when creating Google GKE Groups * Fixed request API endpoints returning status code 500 instead of 404 for bad request errors * Fixed bug where creating a request through the API with an invalid resource ID succeeded (deprecated the resource ID field when creating requests as it will now be autopopulated based on the resource provided) ### Improvements * Improved client side caching for requests page ### Bug Fixes * Fixed issue with UAR report generation for large organizations ### Added * Added ability to enable/disable syncing resources for Snowflake apps ### Improved * Improved performance on the Inventory Group Assets table ### Improved * Updated labeling in inventory for custom apps and custom connectors to be more specific * Updated Coupa validations to allow read-only scopes ### Bug Fixes * Fixed a bug that could block users from creating requests via Slack for resources that required MFA, if they had an expired web session ### Added * Added entity types to apps on the App Details page in the inventory * Added a button to duplicate an access rule on the Access Rules details page * Added `slack` to the possible values for the `third_party_provider` filter on the [`/users/remote_users`](/api-reference/users/get-usersremote_users) API endpoint ### Removed * Removed the ability to re-use AWS Organizations' sessions: each connection to a sessionable resource will now create a new session ### Added * Added user name and user id for REQUESTS\_CREATED event data payload in events stream ### Bug Fixes * Fixed a bug causing the UAR Schedules table to error after creating a schedule with the 'All' filter ### Added * Added Google Chat support for 'Access Expiring' notifications ### Improved * Implemented retry with linear backoff and retry on 429 for custom connectors - [see docs here](/docs/api-spec#rate-limiting) ## Added * Added organization setting to make searches default to requestable resources only * Added Google Chat support for access soon to expire notifications ## Bug Fixes * Fixed UAR preview scoping incorrectly matching on any tags instead of all tags ### Bug Fixes * Fixed bug where admin owners were not allowed to edit max users * Fixed not being able to add immutable groups (e.g. access rules) as member groups from group access screen * Fixed missing option to use login as primary field when creating a new Okta connection/Native App * Fixed a bug with permission handling in the resource import flow to correctly respect connection-specific permissions ### Improvements * On Call schedules can now be imported directly as groups from PagerDuty Native App ### Self-hosted only * (Helm) Added support for chart-level node affinity that applies to all pods ### Bug Fixes * Fixed missing owner source group information in Group Details tab. ### Removed * Fixed missing Google Chat notifications when comments are added to access requests * Removed incorrect group resources access review vulnerability information * Fixed an issue where requests could error out if their associated request configuration was deleted after the request was created. * Fixed missing resource names in the get [user resources API Endpoint](https://docs.opal.dev/reference/get_user_resources). ### Removed * Removed vulnerabilities column from Access Review Group Resources view to streamline the interface ### Bug Fixes * Fixed an issue in the Pagerduty integration where some users would be missed during ingestion * Fixed an issue where users with mixed-case email addresses might not be properly matched when uploading CSV files for adding users to groups or resources * Fixed Linear ticket status to default to Triage when Triage is enabled ### Bug Fixes * Fixed latency issues in UAR group reviews that were causing timeouts for on-premises deployments ### Bug Fixes * Fixed an issue where some users were missed during ingestion in the PagerDuty integration ### Bug Fixes * Fixed bug where ticket propagation creation events were mislabeled * Fixed issues in the iLevel connection where role assignments were pulled incorrectly and deprovisioning failed ### Added * Added support for interactive buttons in Google Chat notifications, allowing users to approve, deny, cancel, or escalate requests directly from Google Chat ### Improved * Moved native app create groups button to assets table for better accessibility ### Bug Fixes * Fixed an issue with GitHub SAML user ingestion where certain users would not have their access ingested due to ID mismatches * Fixed a bug where you were not able to bulk request roles in an Okta app ### Added * Added support for interactive buttons in Google Chat notifications, allowing users to approve, deny, cancel, or escalate requests directly from Google Chat ### Improved * Moved native app create groups button to assets table for better accessibility ### Bug Fixes * Fixed an issue with GitHub SAML user ingestion where certain users would not have their access ingested due to ID mismatches * Fixed a bug where you were not able to bulk request roles in an Okta app ### Bug Fixes * Fixed a bug where Opal Service Users would fail to authenticate * Fixed an issue where Slack slash command option names with expiration information could be truncated incorrectly, potentially causing display problems in the Slack UI * Fixed a bug in Oracle Fusion Connection that redirected users to a broken URL after creating a Github App ### Added * Enabled deprovisioning from the iLevel connection ### Improved * Updated Freshservice ticket type prefixes to automatically sync when ticket type is changed in remote system * Enhanced request comments to appear as ticket comments for propagation tickets ### Bug Fixes * Fixed the K-Bar not working on Windows computers * Fixed an issue where users could be assigned as reviewers for items they don't have visibility to * Fixed an issue where Jira tickets could not be synced if the number of tickets was too long to query ### Bug Fixes * Fixed a bug causing adding users to groups with access to unmanaged resources to fail. ### Bug Fixes * Fixed a bug causing adding users to groups with access to unmanaged resources to fail. ### Improved * Strengthen Content Security Policy (CSP) for air-gapped Opal deployment environments. * Improve the UX in slack when there are too many search results. ### Added * Added actions to the inbox view for requests. ### Bug Fixes * Fixed a bug related to expiring nested group access that resulted in excessive expiration notifications. ### Improved * Improve performance when selecting owners in the approval stages. * Improve retry handling for rate-limited requests from Freshservice. * Improve rate-limit error handling by auto-retrying requests for Jira ticketing integration. * Moved access rule conditions into a modal instead of the left sidebar on Access Rule UARs. ### Bug Fixes * Fixed a bug allowing expired resources to get extended access. * Fixed a bug preventing groups from being given access to Anthropic workspaces. * Fixed a bug that sometimes caused request configuration priorities to be out of order. * Fixes a bug where listing assignments could be extremely slow. ### Self-hosted Only * Fixed Azure US Government integration to use the correct endpoint. * (Airgapped On-Prem Only) Avoid browser calls to initialize Pendo for airgapped environments. ### Added * Adds option to revoke all unreviewed access at the end of an Access Review * Updated the request details screen to show the approver chain after a request has been completed ### Bug Fixes * Fixed a bug where group-group creation could fail ### Improved * Improved Slack handler performance * Improved performance for apps dropdown query ### Self-hosted only * Hide Opal Status banner for on-premise deployments ### Bug Fixes * Fixed poor performance when loading items with visibility restrictions. Some users were seeing timeouts as a result of this bug. ### Added * Added Google Chat notification support for access requests being approved, denied, and cancelled ### Improved * Improved race condition handling between scheduled & real time sync * Improved Salesforce setup documentation * Improved visibility of import items button for native apps by moving it into the assets table * Made handling of role conditions more explicit in GCP, favoring creating new role bindings rather than attaching to existing bindings ### Bug Fixes * Fixed a bug where the requested role would not be included in request audit tickets * Fixed an issue where Opal would not retry push-only custom app webhooks on 5xx, 429 and 408 errors * Fixed a bug where flakiness in the Azure API on listing application templates could result in entitlement sync failing for Azure Enterprise Apps * Fixed a bug where Jira would erroneously find multiple users in certain cases * Fixed an issue in the request UI to prevent expired delegations from displaying as reviewers * Fixed scroll on settings pages ### Bug fixes * Fixed a bug where flakiness in the Azure API on listing application templates could result in entitlement sync failing for Azure Enterprise Apps. ### Added * Added search functionality to Add Apps Catalog * Added support for pulling icons for Azure Enterprise Applications when icons are not set by the user in Azure * Added Notion as a new third-party ticket provider for creating and managing tickets ### Improved * Improved request delegation by only showing active users for selection * Enhanced request delegation security by preventing excluded groups, non-team members, and non-managers from being delegated to ### Bug fixes * Fixed a bug where group-group deletions could fail to propagate in certain group topologies. ### Added * Added feature to Danger Zone let admins put a custom warning banner at the top of Opal to message users when necessary (e.g. incidents/outages/custom instructions/etc) ### Improved * Enhanced audit ticket descriptions to include all custom fields from requests ### Bug fixes * Fixed performance issue when creating new group \<=> group assignments. ### Added * Added ability to search for resources using their remote IDs (such as AWS ARNs or instance IDs), making it easier to find specific resources in large environments ### Bug Fixes * Fixed a bug where the catalog modals closed when redirecting from the button or from search * Fixed inconsistent behavior when deleting access edges (group users, group resources etc). If the access edge doesn't exist, empty success will always be returned instead of sometimes returning not found errors. * Fixed a bug where syncing a large number of groups could produce Opal internal errors ### Bug Fixes * Fixed bug causing IDP group mappings to get hidden in the catalog. ### Improved * Enabled searching by resource's remote ID in UI for easier resource discovery ### Improved * Improved IDP group mappings [API](https://docs.opal.dev/reference/getidpgroupmappings) with RESTful URL structure and included application resource ID in responses ### Bug Fixes * Fixed permissions issue preventing non-super-admins with import permissions from triggering resource imports for Native Apps * Fixed tag dropdown search functionality to properly filter results * Fixed table sorting to correctly handle resource access without expiration dates ### Added * Added the ability to sort access review assignments by reviewer name, making it easier to organize and find assignments ### Improved * Improved Okta app visibility by showing apps both as top-level items and as resources under the Okta Native app, enabling bulk edit/removal via the Assets table ### Bug Fixes * Fixed rendering issue for custom fields in ticketing integrations * Fixed a bug where indirect access could fail to propagate in specific edge cases ### Deprecated * Deprecated mistakenly added `extensions_duration_in_minutes` field in [Resource](https://docs.opal.dev/reference/updateresources)/[Groups](https://docs.opal.dev/reference/updategroups) API (should be set in `request_configurations`) ### Bug Fixes * Fixed nested group indirect access propagation failure in specific edge cases ### Added * Added Github app setting to toggle automatically linking Github user identities for Organizations using SAML SSO * Added ability for admins to create delegations for all users in the organization at inventory/delegations * Added a new REST API [endpoint](https://docs.opal.dev/reference/getidpgroupmapping) to retrieve individual IDP group mappings by app resource ID and group ID * Added public API [endpoints](https://docs.opal.dev/reference/getdelegations) for managing request reviewer delegations, allowing users to delegate access review requests to other users during absences * GET endpoint for listing delegations * POST endpoint for creating delegations * GET endpoint for retrieving specific delegations * DELETE endpoint for removing delegations * Added support for user account deprovisioning for Okta, Salesforce, PagerDuty, Duo, Google Workspace, and Custom Connectors. Deprovisioning can be enabled for an app under "Edit App". Once enabled, user accounts will be deprovisioned when: * Their access is revoked in an access review * When deprovisioning is disabled, user accounts will not be displayed in access reviews, only their entitlements. * The user is deprovisioned in the configured HRIS/IDP * The account is manually deprovisioned via Opal ### Improved * Improved Slack admin/deny/approval with MFA modal to be simpler to use (Slack only) ### Bug Fixes * Fixed an issue where a nil pointer would sometimes be surfaced for Okta group rules sync, instead of the actual error * Fixed an issue where approvals with MFA would not resolve when approving through Slack (Slack only) * Fixed a bug where the App Details tab could become stuck on loading * Removed revocation indicator on Requests details view ### Added * Added new public API [endpoints](https://docs.opal.dev/reference/getrequests): * `GET /requests/:id/comments` * `POST /requests/:id/comments` * `POST /requests/:id/deny` * Added an [API endpoint](https://docs.opal.dev/reference/get_user_groups), `GET /groups/users/:user_id`, to request all groups a user is a member of ### Improved * The`POST /groups` [API endpoint](https://docs.opal.dev/reference/creategroup) now creates Okta and Google Groups if `remote_info` is not specified. This is useful for Terraform or custom automation when creating new remote groups is desirable. * The Connect button is now shown when available instead of Request in the Catalog card view. ### Bug Fixes * Fixed `GET /requests/:id` [endpoint](https://docs.opal.dev/reference/getrequests) issues where reviewer stages were missing information and `requested_items` list showed incorrect access levels * Fixed a bug preventing updating access review deadlines ### Improved * Improved categorization of native apps ### Bug Fixes * Fixed an issue where the `GET /resources` [API](https://docs.opal.dev/reference/getresources) would return 500s * Fixed issue where users were not rendered correctly when adding more than 50 of them to an Access Review * Fixed a bug preventing creation of apps for GitHub organizations in enterprises with managed users ### Bug fixes * Fixed creating Okta Groups and Google Groups from Opal ### Added * Added feature to quickly re-request access to resources in Slack * Added feature to extend access to requests in the Opal UI and Slack, configurable when editing resources * Added the ability to star resources as Favorites in the Catalog ### Bug Fixes * Fixed Select all button not being clickable * Fixed access review name filters not working ### Added * Added visibility toggle to AWS credentials on Connect page ### Bug Fixes * Fixed date picker in UAR flow that was causing incorrect dates to be used * Fixed modal behavior to properly close when navigating forward/backward in browser * Fixed issue that was breaking the sign-in flow when accessing deep links into Opal ### Improved * Disallow read-only admins from hiding/unhiding grants from Risk Center ### Added * Added a new API [endpoint](https://docs.opal.dev/reference/createidpgroupmapping) to create or update individual IDP group mappings, allowing for more granular control when managing group mappings ### Improved * Improved the access review preview interface with better handling of items that have no reviews, making it easier to identify which connections, groups, and resources will generate review items * Reworked bulk update and bulk import logic to offload large tasks to be asynchronous, large item updates will be processed in the background and will notify admins in case of success or failure * Masked AWS Credential values on the resource Connect screen * Enhanced access review capabilities for custom connectors when user deprovisioning is enabled * Cleaned up interaction with adding/removing reviewers in request configuration ### Bug Fixes * Fixed a bug where propagation events would not be created for user provisioning * Fixed a bug where the resource/group configuration form could error out when setting or unlinking a template ### Added * Added links to configuration template label on detail cards * Added an option to set recommended duration as Permanent in request configurations * Added copy name as link to catalog cards ### Bug Fixes * Fixed bug where Jira tickets don't have their reporter set if your Jira Data Center instance uses non-email usernames (requires Jira Data Center version 8.14 or later) * Fixed REST API logging error for status codes ### Improved * Updated styling for access review overview ### Bug Fixes * Fixed Approve OpenAPI endpoint which would error in some cases * Fixed issue where Escalate to skip-manager modal was showing the viewer's skip manager instead of the target user's skip manager * Fixed API bug where importing a child resource would fail if the parent resource was unmanaged ### Improved * Updated Slack message preview for Soon To Expire Access messages to display the asset and time until expiration * Updated group more actions button design and functionality * Updated duration events to display as durations properly instead of timestamps * Updated toast notifications to automatically disappear after 4 seconds by default * Updated design for catalog cards with improved visual styling ### Added * Added ENTITY\_TAG\_ADDED events when tags are attached to a group/resource/user * Added ENTITY\_TAG\_REMOVED events when tags are removed from a group/resource/user ### Improved * Improved performance of visibility group selector * Improved user resource and groups tables' performance with unmanaged resources on web * Improved user resources API endpoint performance with unmanaged resources and proper pagination * Improved Jira ticket creation to handle suspended or inactive reporters gracefully * Ticket creation is no longer cancelled if Jira Service Management projects are missing the opal-specific request type "Access Change - Opal" ### Bug Fixes * Fixed break glass users dropdown displaying when not in edit mode * Fixed Dashboard page date range selector * Fixed incorrect display of human users in Databricks groups' "Non-Human Access" tab ### Improved * Updated manual sync toast notifications to automatically close after success or failure states ### Bug Fixes * Fixed bulk selecting functionality on resource groups when multiple roles are assigned * Fixed audit tickets not updating their status properly * Fixed ticket creation failures in Jira Service Management projects when required fields were missing ### Added * Added ability for Admins to turn off Request Review Delegation * Added owner names to bundles on catalog * Added ticket propagation support for Jira Service Management projects for self-hosted Jira instances - the reporter will now be set for JSM projects and the links to the tickets will direct to the customer-facing URL rather than the agent URL * Added a link to propagation tickets created for requests on non-admin request views * Added support for setting the Request Type field for Jira Service Management issues when Issue Types are associated with Request Types ### Improved * Improved event naming consistency - `GROUPS_ADDED_TO_GROUPS` events now show up as `ROLE_ASSIGNMENTS_CREATED` * Improved event naming consistency - `GROUP_GROUPS_UPDATED` events now show up as `ROLE_ASSIGNMENTS_UPDATED` * Improved event naming consistency - `GROUPS_REMOVED_FROM_GROUPS` events now show up as `ROLE_ASSIGNMENTS_DELETED` * Improved Jira ticket linking for JSM projects to use customer-facing URLs instead of agent URLs * Enhanced Jira integration to always set the reporter field to the person making the request in Opal - if the email does not exist in Jira, a customer account will be created ### Bug Fixes * Fixed configuration template edit button not redirecting to the edit page * Fixed Jira bug for self-hosted Jira instances where access tickets could not be linked to requests ### Bug Fixes * Fixed Resource Edit form tooltips not appearing to ensure proper guidance during form completion ### Improved * Added hovercards to Owners' Source Group label for better information discovery ### Added * Added support for roles in CSV uploads for custom apps * Updated search to correctly show the logo for Resource Apps instead of the logo for their parent Apps. ### Bug Fixes * Fixed the 'Create UAR without Scope' warning modal incorrectly appearing when only scoping by group types * Fixed bulk expiration button showing incorrect expiration option on first load ### Self-hosted only * Replaced Bitnami Redis images with Opal-hosted alternatives (On-prem customers **must** upgrade to this version by **08/28/2025**) ### Added * Added the ability to delegate access request reviews to other organization members. Schedule delegates from **Settings** > **Delegates**, accessible from your user profile in the lower left corner of the dashboard ### Improvements * Improved end user details interface by separating ID and link copy buttons for better usability ### Bug Fixes * Fixed groups in the Group Access table and Group Details modal not being clickable * Fixed an issue with API-initiated access requests having a broken support ticket when no `support_ticket` parameter is passed. * Fixed search functionality on the sent tab of requests ### Added * Admins can now download requests from the admin tab ### Improvements * Improved owner escalation policy validation to prevent setting values below 5 minutes or above 1440 minutes ### Bug Fixes * Fixed UAR cells getting cut off to prevent content truncation in the user interface * Fixed inventory tag and inventory owner links to navigate to the correct tab on the details page when clicked * Fixed requests incorrectly indicating requested assets as never used ### New Functionality * Added custom date option to bulk expirations menu * Added connector group and connector resource app identifiers to sublabels and hovercards * Added ability to filter inventory/owners by specific users ### Feature Enhancements * Reworked access rules failsafe threshold for better accuracy * The threshold is now evaluated against additions and removals separately (as opposed to cumulative changes) * The threshold no longer has a 20 user minimum, this will make the failsafe useful for smaller single-team rules. * Updated notification styling ### Bug Fixes * Fixed GitHub connection creation sync failure ### Self-hosted only * Added support for SMTP connections on port 25, with or without STARTTLS # CLI Changelog Source: https://docs.opal.dev/changelog/cli-changelog Learn about notable changes to the Opal CLI. See the [npm package](https://www.npmjs.com/package/opal-security) for changes released prior to version 3.2.1. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 5.1.2 * Added `--openBrowser` flag to session creation commands to automatically open the browser without prompting for confirmation. This is useful for automation and CI/CD workflows. ## 5.1.1 * Fixed issue preventing Postgres and Kubernetes sessions from starting ## 5.1.0 * Removed device code flow as option for authentication ## 5.0.1 * Improved MFA and OIDC Authentication flows to poll on completion and no longer rely on cached sessions. This change requires Opal 1.1087.0 or later. ## 4.1.0 * Temporarily re-add `curl-example` command. * Change Auth Code flow to use port 49152 instead of 8080, with 49153 and 49154 as fallbacks. ## 4.0.3 * Backwards-compatibility fix to ensure users with config files from previous CLI versions can still login correctly. ## 4.0.2 * `opal login` defaults to using the [authorization code flow with PKCE](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce) for authentication by default. * Logging in with the previous [device authorization flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow) now requires the `--device-code` or `-d` flag. * User now has to explicitly press enter before browser is opened for browser based authentication. * Add new `set-auth-config` and `clear-auth-config` commands. These allow setting/clearing the user's organization ID, auth client ID, and auth issuer * Remove `set-auth-provider` and `clear-auth-provider`. Functionality for these have been subsumed into the new commands. * When a user sets `organizationID` and `clientID` via `set-auth-config`, then `opal login` will no longer prompt the user to select an org. ## 3.2.4 * Resolved an issue where the `opal request create` command failed to retrieve all available assets when a connection had more than 100 requestable items. The command now correctly handles pagination to display all requestable assets, regardless of quantity. ## 3.2.3 * Eliminated noisy third party deprecation warnings in CLI outputs for cleaner command-line experience ## 3.2.2 * Added new `--bundle` flag to the `opal request create` command, allowing users to create requests based on assets in an existing bundle * Added new`--template` flag to the `opal request create` command, allowing users to create new access requests based on previous ones with requested * Added more descriptive error messaging in `opal request create` * Print URL link to authentication page in browser if `opal login` does not open browser automatically ## 3.2.1 Introduced on 2025-06-13. * New request experience in CLI: * `opal request create` starts an interactive experience to create an access request in Opal, or users can pass in appropriate flags to bypass the interactivity * `opal request get --id` retrieves data about a certain request id including its status * `opal request list` retrieves data about the user's 10 most recent outgoing access requests * New command `opal whoami`: * Returns information about the current setup, including the set Opal server URL, organization name, and actively logged-in user email, if any. *** # Create Access Campaigns Source: https://docs.opal.dev/docs/access-campaigns Learn how to create a User Access Campaign. Access Campaigns, Opal's new interface for access reviews, is now GA! For current customers, this interface supports all features from the [legacy Access Reviews](/docs/access-reviews-legacy). If you're on the legacy feature and need help transitioning, please contact our support team. Access Campaigns allows you to automate User Access Reviews (UARs) in Opal. Compliance teams can: * Snapshot user access when a review is started * Scope access campaigns intelligently with natural language or a query builder * Automatically assign reviews for managers, resource owners or admins to review in a self-service way * Send custom instructions and bulk reminders all without leaving Opal * Generate a report to summarize all actions for audit purposes ## Requirements You must have the Opal Auditor or Opal Admin role to create an access campaign, and be on versions `1.1196.0` or later to view the new interface. To learn more about the roles in Opal, see our page on [role capabilities](/docs/roles-in-opal#role-capabilities). ## Creating an Access Campaign Navigate to the Access Campaigns tab and click **+ Campaigns** in the top left corner. ### Scoping a Campaign Define the scope of your campaign by using natural language input or adding a Principal, Asset, and/or Edge filter in the query builder. Scoping an access campaign with the query builder. The query you build will be run against your access graph to produce all access items that fulfill those criteria, much like an SQL query. Here are the filters you may use to scope your campaign: | **Filter type** | **Description** | **Conditions supported** | | ------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Principal | Entities that have access to an asset (e.g. User, Resource, Groups) | • Principal
• Principal Type
• Principal Item Type
• Principal Name (`string` match)
• App (Entities managed by specific integration)
• Principal Tag
• IDP Status (for users only)
| | Asset | What principals have access to (e.g. Resource, Groups) | • Asset
• Asset Type
• Asset Item Type
• Asset Name (`string` match)
• App (Entities managed by specific integration)
• Asset Tag
| | Edge | Relationship between Principal and Asset | • Role Name (exact `string` match)
• Role Remote ID
• Access Duration Type (Permanent or Time-bound) | To condition principals, add a condition **+ Condition** under *From principals where*. To condition assets, add a condition under *To assets where*. To condition on the edges between the principal and asset, add a condition under *via access that is*. Examples of queries you can build include: To find all users Principal who have access to Github repositories Asset, you would select **"Principal Type is User"** and **"Asset Item Type is Github Repo"**. Then, to narrow it down only to admin access Edge to Github Repositories, add an edge condition **"Role Name is Admin"**. Picture showing how to scope down to all users who have github repo admin access. It shows principal type = users, asset item type = github repo, and role = Admin. If you want to scope it even further down to the specific Github repository, you would instead select **Asset is \[Github repository name]**. > To find all permanent Edge access to a specific Okta app like Figma Asset, you would select **"Asset is Figma"** under *to Assets where*, and edge condition **"Permanent access only"** under *via access that is*. The reason you would add the condition **"Asset is"** instead of **"App is"** is because Figma is not a native app in Opal; rather it is managed by the Okta app integration as can be seen in the dropdown. > To find all users Principal access to resources tagged uar:True Asset, you would first select **"Principal Type is User"** under *From Principals where*. Then, you would add the asset condition **ALL OFF** **"Asset Type is Resource"** and **"Asset Tag is SOX:Eng"**, with the Tag source being the place you tagged your resources (e.g. Opal, your HRIDP system). Conditioning the Asset with **"Asset Type is Resource"** ensures that you only scope in user -> resource relationships. If you have groups you've tagged that you want to scope in as well, remove the condition **"Asset type is Resource"** to include ALL asset types. > To find all groups Principal access to either Github Repository A or B Asset, you would first select **"Principal Type is Group"** under *From Principals where* to find all **Group** access. Then, you would add the asset condition **ANY OF** **"Asset is Github Repository A"** or **"Asset is Github Repository B"**. Switching the entity condition to **ANY OF** applies an **OR** condition to the two Entity conditions, allowing you to scope in groups that have access to either one or the other repositories. You may generalize this to specific assets (e.g. specific resources), asset item types (e.g. any Github Repositories OR Gitlab repositories), or apps (e.g. Okta apps). > For both Principals and Assets, you can also add **Advanced Access Filters**. These filters will allow you to further condition on the access principal or asset might have. Examples of queries you can build with this include: * All access that users who have access to Github Repo Admin have To preview the results of the scope you’ve created, click **Preview Scope**. ### Set Reviewer Assignment Policy Select a reviewer assignment policy from the dropdown. The preview table updates automatically to reflect your selection. Selecting a reviewer assignment policy with the preview table. You can't change the bulk assignment policy after you create the campaign, but you can make manual adjustments to individual reviewers after saving your draft. If you choose **Manual Assignment**, you'll need to assign all reviewers after creating the draft. Turn on **Allow self-review** to let users review their own access. You can change this setting after creating the draft. ## Configure your draft After you click **Create**, your access campaign enters **Draft** mode. This allows you to edit reviewer assignments, add general information, and configure specific controls before you launch it to your reviewers. When you click **Create**, Opal takes a snapshot of the access relationships between principals and assets in your campaign. Any access changes that occur after this point won't be reflected in the campaign. For example, if Jane's GitHub role changes from admin to read-only after you click **Create**, the campaign will still show her original admin role. ### Reviewer Assignment Open the **Reviews** tab to manually edit reviewer assignments. Use multi-select, grouping, filtering, and sorting to bulk-assign reviews to one or more reviewers. Group your reviews by principal, asset or reviewer. You can also bulk assign reviews to **each user's relative manager or asset owner**. If a review is assigned to multiple reviewers, it is only accepted if all reviewers approve access. If any reviewer revokes access, then the review item is revoked. ### Settings #### Set your deadline Configure the deadline of your access campaign in the **Settings** tab. This is required before starting the campaign. #### General All notifications are sent through channels set up in Opal’s global configuration. You may configure Slack or email notifications via **Configuration > Settings > Notifications**. [Learn more](/docs/notifications). Turn on **Notify reviewers** to send reviewers an email when they have new items to review, and include a custom message to be sent at the start of the campaign. Turn on **Show tags to reviewers** if you want reviewers to have visibility into user, resource and group tags to make their decisions. #### Revocation Choose when you want revocations to propagate to the end system. You can either revoke access on decision, or when you end the campaign. Enable **Require reason on denial** to require reviewers to provide a comment explaining why access was revoked. Choose if you'd like to allow reviewers to reassign their reviews, and select who they can reassign them to. #### Reminders Choose a set schedule to send preset access campaign reminders to all reviewers with incomplete reviews. You may choose to include the reviewer’s manager in these reminders as well. ## Start your campaign When you’re ready to start your access campaign, click the **Start Campaign** button. You can also schedule your access campaign to start at a later date. ## See also This wraps up access campaign creation. See the links below for an end user guide to reviews or managing the campaign you just created. * [Complete your reviews](/docs/complete-access-reviews) * [Manage your access campaign](/docs/managing-access-campaigns) # Access Comparison Source: https://docs.opal.dev/docs/access-comparison Learn how to use our access comparison tool Access Comparison lets you compare a reference user's access against other users, side by side. Use it to quickly provision access for new hires or when someone changes teams, and spot access drift with a single click. ## Requirements * You must be an Opal Admin or a read-only admin to use Access Comparison. * You must be on version `1.1182.0` or later. Navigate to **Access Comparison** and select a reference user. The table lists every resource and group the reference user has access to in Opal, along with their access level and remaining duration. Click **Add user...** to add one or more users to compare against the reference user. Each user appears as a new column showing their access to the same resources and groups. Differences in access will surface at the top of the table. Use the search field to find specific resources. Each cell should shows the user's access level (for example, Admin or Viewer) and how long the access lasts. If a comparison user doesn't have access to an item, their cell shows a **Request** button. Click it to open an access request for that user. ## FAQs If a user can reach a resource through a group, that shows up as the group under the Groups tab rather than on the resource itself. The table only lists items the reference user currently has access to, and only items managed in Opal. If a comparison user has access to something the reference user doesn't, it won't appear — make that user the reference user to see their full access. No. The comparison reflects current access at the time you load the page. To review how access has changed over time, use the Events page. # Create Access Reviews Source: https://docs.opal.dev/docs/access-reviews-legacy Learn how to create and configure User Access Reviews in Opal. Access Reviews will soon be a legacy feature. Manage your Access Reviews with [Access Campaigns](/docs/access-campaigns), now in beta. Opal can be used to automate User Access Reviews (UARs). In Opal, compliance teams can: 1. Snapshot user access when a review is started 2. Intelligently assign reviews for resource owners to review in a self-service way 3. Scope reviews by apps and resources 4. Generate a report to summarize all actions for audit purposes ## Start User Access Reviews ### Assume the Opal Auditor role To start a user review, you must have the **Opal Auditor role**. In the **Inventory** tab, select the App **Opal**. Here you'll find the **Opal Auditor** role. If you're an Opal Admin, you can add yourself directly to the resource by navigating to the **User Access** tab and clicking on the **+ Add Users**. If you're not an Opal Admin, you can request access to the role. ### Create User Access Reviews Start an access review from the **+ Access Review** button in the Ongoing tab. #### Scheduled reviews You can create recurring reviews from the **Schedules** tab, specifying months and . Select **+ Create Schedule** and specify the months and day of the week to run the review. You can edit the review—its groups, users, resources, etc.—at any time, and changes take effect on the next scheduled review. #### Add general information In the left panel, enter general details about the review: * **Name: ** Customize the name of the review. * **Reviewer auto-assignment:** By default, this is set to **Manually assign reviewers**. Click **Select Policy** to choose your auto-assignment configuration. * **Self-review:** Enable to allow self-reviews. * **Apply changes when:** Set when access changes should take effect: immediately as decisions are made, or all at once when your access review ends. * **Deadline: ** Set the deadline by which reviewers must complete their reviews. * **Time zone:** Set the timezone to be used across reviews. #### Set visibility Set the **Group Resource Visibility** to **Strict**, **Moderate**, or **Full visibility**, depending on the sensitivity of the resources and groups being reviewed. #### Set review scopes In the **Users** and **Entities** sections, you can filter specific users and entities (resources, nonhuman identities, groups, and apps) to review. See the preview next to the **Cancel** and **Create** buttons—e.g., 171 resources, 60 groups, 18 apps in the below screenshot—to see affected entities as you update filters. Consider the following filters when you select what to review: * **Filter by user**: Include only resources and groups that certain users have access to in the review. The user filter is applied first before any other filters are applied to entities. * **Filter by specific entities**: Select specific resources, nonhuman identities, groups, and apps to include in the review. Note that the entity filter only includes the entity itself. For example, if you specify a resource and want to review the groups who can access the resource, you must add the groups as entities as well. * **Filter by apps**: Specify resources and groups to include from certain apps. * **Filter by admin**: Include resources and groups owned by specified admins. * **Filter by resource and group type**: Include resources and groups of certain types. * **Filter by tags**: Include resources and groups with certain tags. * **Filter by name**: Include resources and groups whose names match a given substring. If you use [linked groups](/docs/linked-groups) and would like to include users from source groups in the access review, consider toggling on **Include linked group source groups**. #### Set notifications In the left side panel, you can configure the following settings: * **Access Review Reminders**: Choose a set schedule to send preset campaign reminders to all reviewers with incomplete reviews. * **New review notifications**: Enable to notify users on Slack and email when they're assigned a new review. *** # Access Rules Source: https://docs.opal.dev/docs/access-rules Use Access Rules to enforce access policies at scale. Access Rules are available on cloud-hosted Opal and self-hosted Opal versions 1.943.0 and later. Access Rules are a set of conditions—built from attributes from your HRIS/IDP source—you can use to **dynamically** grant access to groups and resources, enabling Attribute-Based Access Control (ABAC). With Access Rules, you can easily enforce policies at scale and adapt your access requirements to changing business logic, without additional overhead. Use Access Rules to: * Provision access based on workplace events (e.g. Joiner, Mover, Leaver)—Opal automatically syncs users and updates access downstream when users onboard, transfer, or leave your IDP or HRIS * Automate and codify your desired state of identity and access at scale ## Requirements To create and delete Access Rules, you must: * Be an [Opal Admin](/docs/roles-in-opal) Before you set up Access Rules, you also must: * [Connect Opal](/docs/add-your-first-idphr-provider#connect-opal-to-an-idphris-system) to your IDP/HRIS system * [Import attributes](/docs/add-your-first-idphr-provider#import-attributes) from your IDP/HRIS system as **User Tags (custom attributes)** To confirm your attributes are correctly imported, go to **Inventory** > **Tags** and search for your tag. Alternatively, go to **Inventory** > **Users**, select a user you expect to be tagged, and go to the **Details** tab. There, you'll see the attributes with a logo in the column on the right indicating the source. ## Create Access Rules Access Rules do not affect access to groups or resources until you've explicitly [granted access](#grant-access-to-groups-and-resources), so it is safe to create and modify Access Rules while you determine your ideal conditions. To create an Access Rule, go to **Inventory** > **Access Rules** and select **+ Access Rule**. Give your Access Rule a name based on the users you're targeting, a **Description**, and choose an **Admin**. ### Set conditions Access Rules consist of conditions, which you use to filter a list of users based on tags imported from your IDP. Conditions use the conjunctive normal form, expressed as an **AND** of **ORs**. You can exclude users with the **Except...** clause. The following example filters users to full-time engineers living in the United States. The condition includes users tagged with **Country:United States** **AND** (**position:Senior Network Engineer** **OR** **position:Integration Engineer**), and the **Except...** clause excludes any users tagged with **employeeTimeType:PartTime**. You can continue to modify conditions until you've granted access to groups and resources. After you select **Create Rule**, you can view the filtered users and [grant access to groups and resources](#grant-access-to-groups-and-resources). To modify conditions after you've added groups and resources, you must delete and re-create the Access Rule. The users in an Access Rule are automatically synced when your IDP is updated, so you don't need to do any extra work to keep access up-to-date with your IDP and internal business logic. ## Grant access to groups and resources To avoid over-provisioning access to privileged entities, **do not** use Access Rules to grant access to any sensitive groups and resources. Leverage [direct access requests](/docs/configure-reviewers) for these groups and resources instead. In the **Resources** tab on your Access Rule, you can grant access to groups and resources as you would for an individual group or user. You can also set the access duration to be indefinite or timebound. After you add groups and resources, the **Inventory** page for the group or resource displays the users granted access through the Access Rule. The **Access Path** column shows all sources of access for users. Clicking on the **Access Path** shows a detailed breakdown of the paths. This lets you easily determine how a user can access a group or resource, and predict what will happen when access is revoked or expired from different paths. ### Grant access with Terraform Use [opal\_group\_resource\_list](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/data-sources/group_resource_list) and [opal\_group\_containing\_group](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/resources/group_containing_group) to assign resources and groups to access rules. Set the access rule ID as the `group_id`. ## Pause Access Rules After you create an Access Rule, you can pause it to prevent new users from being added to the rule. Existing users, even if they no longer match the conditions, still have access to any resources or groups granted from the rule. Pause and activate Access Rules by toggling the **Status** column, either on the **Access Rules** page or from the detail page. ## Enable failsafe functionality Failsafe functionality is available on Opal cloud and self-hosted Opal versions \[1.929.0] and later. Admins can configure Opal to automatically pause Access Rules which would result in large membership changes. To enable this, go to **Configuration > Settings > Advanced** and toggle **Enable access rules failsafe**. The failsafe is automatically triggered when either pending additions or removals exceed 30% of the current membership, the sync will be paused **before** any changes are applied. The status on the upper right is updated to reflect the failsafe when it's triggered, as in the following example. If you don't have any resources or groups assigned to the rule, you can edit the rule's conditions to produce a smaller set of user changes. ## Test conditions for given users To test whether a given user will be included in an access rule and granted access to its resources and groups, enter a user from the **Conditions** tab on an existing rule. This can be especially useful for debugging. **EXCEPT** ## Remove Access Rules To remove an Access Rule, go to the detail page for the Access Rule, select **...**, then **Remove from Opal**. Deleting an Access Rule does **not** automatically revoke access from remote systems, even if access was granted from the rule. To remove access granted by Opal, **first** delete all resources and groups from the rule, then remove the rule. ## Review resources associated with Access Rules To review what resources an Access Rule grants its matching users access to, you can create a User Access Review, scope it to the desired Access Rules by selecting **Add entity** > then searching for the Access Rule under the **Groups** dropdown. You can also review resources across *multiple* Access Rules by selecting **Add entity type** > then searching for the **Access Rule** under the **Group types** dropdown. In this example, the selected user is not included in the access rule because their tags satisfy the EXCEPT condition. ## Configure access requests based on Access Rule membership You can also use Access Rules to dynamically enforce which users can request access to resources based on attributes. On the resource, navigate to **Edit** > **Request Configuration** > **Add a New Configuration** > and under **Requesting Groups**, select the Access Rule. In this example, the selected user is not included in the access rule because their tags satisfy the EXCEPT condition. ## Configure visibility based on Access Rule membership You can also use Access Rules to dynamically enforce which users can view resources based on attributes. On the resource, navigate to **Edit** > **Restrict to groups** > **Add groups with visibility:** > then select the Access Rule. In this example, the selected user is not included in the access rule because their tags satisfy the EXCEPT condition. *** # Add additional IDP/HR providers Source: https://docs.opal.dev/docs/add-additional-idphr-providers Opal allows customers to ingest identities and their attributes from multiple IDP/HR systems. If your organization uses multiple IDPs, multiple HR systems, or you simply want to combine user metadata from disparate sources, you can simultaneously connect to each and rank priority on a per-attribute basis. ## Add additional providers To add an additional provider, you must first configure the provider as an Opal App. Go to the **Inventory** page, select **+ App**, and complete the flow. Next, go to the **Configuration** > **Settings** page, then **Identity and HR Integrations**. Select **+ IDP/HRIS Connection** to add another provider. ## Import attributes Attributes from any connected provider can be imported as either: * **User Tags**—custom attributes * **Opal System Attributes**—such as the user's Name, Manager, or Position **User Tags (custom attributes)** are linked to the IDP or HRIS provider they are imported from. The source provider for each user tag will be visible after import. ## Handle system attribute conflict Opal's built-in attributes, such as title, first name, last name, and manager, can be imported from any connected IDP or HRIS. These attributes are not directly tied to the originating provider, so you need to specify which provider takes precedence if a user is active in multiple systems. You can configure this priority in the **Attribute Mapping** tab. ### Handle email changes If a user's email changes in the IDP, Opal updates the user's email in Opal to match the new email in the connected system, while preserving all existing links with remote systems. **Note:** Updating the email in only some IDPs may cause unintended state drift. You can configure which IDP takes precedence for email changes in the **Attribute Mapping** tab. ## Secondary emails When multiple IDP/HRIS providers are configured, secondary emails imported from them will be merged for the corresponding user. # Add your first IDP/HR provider Source: https://docs.opal.dev/docs/add-your-first-idphr-provider Connect your first IDP/HR system with Opal to sync your organization's identities and attributes from a predefined source of truth. To learn more about how Opal interacts with the end system, see [Connect Identity or HR Provider](/docs/connect-your-identity-provider-idp). Opal currently supports integrations to: * Okta * Google * Microsoft Entra ID * Workday ## Add a new IDP/HRIS integration Under the **Configuration** section, go to **Settings**, then **IDP & HR Integrations**. Click **+ IDP/HRIS Connection** on the top right. Select your IDP/HRIS provider and click **+ Connect** . ### Use existing connection If you previously set up a connection with your IDP/HRIS provider outside of the IDP/HRIS integration flow—e.g., a connection for managing entitlements—Opal shows you a dropdown selection of existing connections associated with the provider. ### Add new connection If this is the first time you are setting up a connection to the provider, you must create an App for the provider first. Opal will direct you to the connection set up page for the App. Follow Opal's integration guides to set up the app on the end system: * [Okta](/integrations/okta) * [Google Workspace](/integrations/google-workspace) * [Microsoft Entra ID](/integrations/azure) * [Workday](/integrations/workday) ## Import attributes After you connect to your IDP/HRIS provider, you can import attributes. Attributes can be imported as either: * **User Tags**—custom attributes * **Opal System Attributes**—such as the user's Name, Manager, or Position **User Tags (custom attributes)** are linked to the IDP or HRIS provider they are imported from. You can view the source providers for imported user tags from the **Inventory** > **Tags** > **Users** page. # Agent Inventory Source: https://docs.opal.dev/docs/agent-inventory Track and govern the AI agents operating in your organization from a dedicated inventory. Agent Inventory is currently in limited beta. Contact Opal support to enable it for your organization. The **Agent Inventory** tracks AI agent identities in your organization—separately from human users and other non-human identities—so you can see what agents exist, who's accountable for them, and what they can access. Agent Inventory tracks AI agents that operate as identities in your environment, such as Claude or Bedrock agents with their own credentials and access. This is distinct from [Paladin](/docs/paladin/overview), Opal's own platform for building agents that review access requests. Each agent in the inventory has: * A **source**, indicating how it was ingested—see [Agent sources](#agent-sources) * A **tier**—Unknown, Supervised, Autonomous, or Orchestrator—reflecting the agent's degree of autonomy. Agents default to Unknown until an admin classifies them. * A **harness**, the framework or platform running the agent * A **purpose**, a short description of what the agent is for * Linked identities, plus resource and group counts ## Requirements Before you begin, you must: * Be an Opal Admin ## Agent sources | Source | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Anthropic | Synced from an [Anthropic](/integrations/anthropic) connection—covers Anthropic service account and API key principals, plus usage-based detection of agentic activity | | Okta Directory | Synced from an Okta Directory connection, using one of several source types—see below | | AWS / AWS SSO | Synced from an AWS or AWS SSO connection—covers Amazon Bedrock AgentCore agents | | Re-labeled from user | An existing user or service account identity that an admin has promoted to an agent | | Manually created | An agent created directly in Opal, without an underlying connector | ## Sync agents from a connector To ingest agents from a connector, the underlying connection must already exist in Opal—for example, an [Anthropic connection](/integrations/anthropic) or an [AWS connection](/integrations/setting-up-your-aws-organization-in-opal). 1. Go to **Inventory** > **Agents**. 2. Select **Sync from connectors**. 3. Choose a connection to sync from. 4. Depending on the connection type: * **Anthropic**: Review the candidate service accounts, API keys, and usage-based detections, then select which to ingest. * **Okta Directory**: Choose a source—**Directory user types**, **Service & OAuth apps**, **Okta for AI Agents**, or **Scan directory**. Okta for AI Agents imports directly with no review step; the other sources surface candidates for you to review before confirming. * **AWS / AWS SSO**: Bedrock AgentCore agents are imported directly, with entitlements granted automatically. 5. Confirm the import. ## Re-label existing identities as agents If an existing user or service account is actually an AI agent, you can re-label it instead of creating a duplicate record. 1. Go to **Inventory** > **Agents** and select **Add Agents**. 2. Opal surfaces candidate identities that look like agents, based on their attributes and access patterns. 3. Select the identities to promote, then confirm. Promoted identities appear in the Agent Inventory with source **Re-labeled from user**. ## Manually create an agent You can also create an agent directly, without syncing from a connector. 1. From the Inventory, open a user or non-human identity record. 2. Select **Link to Agent** > **Create new agent**. 3. Fill in the agent's details: * **Name** (required) * **Owning group** (required) * **Accountable sponsor** * **Purpose** * **Tier**: Supervised, Autonomous, or Orchestrator * **Harness** * **Members**—the identities this agent assumes 4. Save the agent. # Custom Connector API Spec Source: https://docs.opal.dev/docs/api-spec Use this API spec to build your custom connector after you've generated boilerplate code following the steps in [Create your own connector](/docs/how-to-create-your-own-connector). You can also reference the [Datadog custom connector](https://github.com/opalsecurity/opal-datadog-connector) as you build your implementation. # Endpoints ## GET `/status` Checks the status of the connector. This endpoint is mainly used for successfully creating the app in Opal and verifying that the connector is properly configured. **Query params** | param | type | description | | ------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | **Response params (200)** No body is required in the response for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | ------------------------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See [Error Codes](#error-codes) for details. | ## GET `/resources` Returns a list of all resources for the app in question. This endpoint is called after you've [imported resources](/docs/import-and-configure-resources-and-groups#manual-import) to the app, so you may not see it being called when you first create your app. To distinguish resources pulled from multiple sources, you can include the type in the identifier, then base64-encode the ID so it can be properly parsed—e.g., `base64(resource-type:1)`. **Query params** | param | type | description | | ---------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints. | | cursor | string | For pagination. Empty string on the first call, then set to the value provided via `next_cursor`. | | parent\_id | string | **Optional**: Used for nested resources. If you enable **Nested Resources** in the Opal connector form, this endpoint finds the resource specified by `parent_id` and returns its immediate children. See [Create your own connector](/docs/how-to-create-your-own-connector) for more information. | **Response params (200)** | param | type | description | | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | resources | array of objects | List of the resource objects. See below for what each resource should include. | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Resource object** | field | type | description | | ----------- | ------ | ------------------------------------------------------------------------------------------------------ | | id | string | The id of the resource that uniquely identifies it in your system. It can be at most 65535 characters. | | name | string | The name of the resource. | | description | string | The description of the resource. | **Example response** ```json json theme={null} { "next_cursor": "gjroieapghnfagjfdgpadshfasd", "resources": [ { "id": "1", "name": "Gooli Metadata", "description": "Access to Gooli customer metadata" }, { "id": "2", "name": "Eviato Metadata", "description": "Access to Eviato customer metadata", "can_have_usage_data": true }, { "id": "3", "name": "Moolybib Metadata", "description": "Access to Moolybib customer metadata" } ] } ``` ## GET `/resources/{resource_id}` Get a specific resource by its ID. **Path params** | param | type | description | | ------------ | ------ | ----------------------- | | resource\_id | string | The id of the resource. | **Query params** | param | type | description | | ------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints. | **Response params (200)** | field | type | description | | -------- | ------ | --------------- | | resource | object | Resource object | **Resource object** | field | type | description | | ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The id of the resource that uniquely identifies it in your system. It should match the ID (``) of the request. | | name | string | The name of the resource. | | description | string | The description of the resource. | | can\_have\_usage\_data | boolean | **Optional**. This property is used to enable ingesting usage events for a resource to enable LPPM for custom connections. See [the events endpoint](/docs/api-spec#get-events) for more information. `false` if unspecified. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Example response** ```json json theme={null} # GET /resources/1?app_id=app_unique_id { "resource": { "id": "1", "name": "Gooli Metadata", "description": "Access to Gooli customer metadata" } } ``` ## GET `/resources/{resource_id}/access_levels` If your resource doesn't require any access level, this endpoint can return `"access_levels": []`. Returns all available access levels for a resource, paginated. Access levels are exposed in the Opal dashboard and to end users as roles, which users select when they request access to the resource. **Path params** | param | type | description | | ------------ | ------ | -------------------------------------------------------- | | resource\_id | string | The id of the resource to retrieve the access levels of. | **Query params** | param | type | description | | ------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then set to the value provided via next\_cursor | **Response params (200)** | param | type | description | | -------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | access\_levels | array of objects | List of the access levels objects. See below for what each object should include. If your resource doesn't require any access level, it can return `[]`. | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Access level object** | field | type | description | | ----- | ------ | --------------------------------------------- | | id | string | ID of the access level to grant to this user. | | name | string | The user-facing name of the access level. | **Example response** ```json json theme={null} { "next_cursor": "gjroieapghnfagjfdgpadshfasd", "access_levels": [ { "id": "1", "name": "Admin" }, { "id": "2", "name": "Read-Only Admin" }, { "id": "3", "name": "Guest" } ] } ``` ## GET `/resources/{resource_id}/users` Returns the users that currently have access to the provided resource. **Path params** | param | type | description | | ------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------- | | resource\_id | string | The id of the resource you’re trying to list the details of, as returned as `id` in the `/resources/` endpoint. | **Query params** | param | type | description | | ------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then passed values provided via next\_cursor | **Response params (200)** | param | type | description | | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | users | array of objects | List of the resource user objects. See below for what each resource should include | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message to expose in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **ResourceUser object** | field | type | description | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | user\_id | string | The user identifier used in the remote system, this will be provided back to you when making access changes. | | email | string | The email of the user. This will be used on our end to correlate it with Opal users. Although not required at the moment, we'd like implementers to include this value as well. | | access\_level | object | The access level granted to the user. If omitted, the default access level remote ID value (empty string) is used. | **Example response** ```Text JSON theme={null} { "next_cursor": "bpaubmkeospb", "users": [ { "email": "[email protected]", "user_id": "1", "access_level": { "id": "1", "name": "Admin" } }, { "email": "[email protected]", "user_id": "2", "access_level": { "id": "2", "name": "Read-Only Admin" } } ] } ``` ## POST `/resources/{resource_id}/users` Adds a user to the access list of the specified resource. **Path params** | param | type | description | | ------------ | ------ | ------------------------------------------------------- | | resource\_id | string | The id of the resource you’re trying to add the user to | **Body params (JSON encoded)** | param | type | description | | ----------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | user\_id | string | The id of the user to add to the resource | | access\_level\_id | string (optional) | The ID of the access level to assign to the user. | | custom\_fields | array (optional) | Custom fields from the access request that initiated this provisioning. Only present when provisioning was triggered by a user access request. Each object contains `field_name` (string), `field_type` (string: `SHORT_TEXT`, `LONG_TEXT`, `BOOLEAN`, `MULTI_CHOICE`), and `field_value` (string). | **Example request** ```Text JSON theme={null} { "app_id": "datadog-production", "user_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65", "access_level_id": "12f5ef35-5de6-4224-a006-7fe4c20db5c6", "custom_fields": [ { "field_name": "environment", "field_type": "SHORT_TEXT", "field_value": "production" } ] } ``` **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## DELETE `/resources/{resource_id}/users/{user_id}` Remove a user from the access list of the specified resource. **Path params** | param | type | description | | ------------ | ------ | ------------------------------------ | | resource\_id | string | The id of the resource to be edited. | | user\_id | string | The id of the user to be removed. | **Query params** | param | type | description | | ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | access\_level\_id | string (optional) | The ID of the access level associated to the user for this resource. | **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## GET `/groups` Returns a list of all groups for the app in question. The `/groups` endpoints are only called if **Connector Groups** is enabled in the [custom app form](/docs/how-to-create-your-own-connector#4-create-a-custom-app). **Query params** | param | type | description | | ------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list groups for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then set to the value provided via next\_cursor | **Response params (200)** | param | type | description | | ------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | groups | array of objects | List of the group objects. See below for what each group should include | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all groups have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Group object** | field | type | description | | ----------- | ------ | --------------------------------------------------------------- | | id | string | The id of the group that uniquely identifies it in your system. | | name | string | The name of the group | | description | string | The description of the group | **Example response** ```json json theme={null} { "next_cursor": "gjroieapghnfagjfdgpadshfasd", "groups": [ { "id": "1", "name": "Eng team", "description": "Eng team users" }, { "id": "2", "name": "Finance team (North America)", "description": "Users in the finance team in NA" } ] } ``` ## GET `/groups/{group_id}` Get a specific group by its id. **Path params** | param | type | description | | --------- | ------ | ---------------------- | | group\_id | string | The id of the resource | **Query params** | param | type | description | | ------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | **Response params (200)** | field | type | description | | ----- | ------ | --------------- | | group | object | A Group object. | **Group object** | field | type | description | | ----------- | ------ | ------------------------------------------------------------------ | | id | string | The id of the resource that uniquely identifies it in your system. | | name | string | The name of the resource | | description | string | The description of the resource | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Example response** ```json json theme={null} # GET /groups/1?app_id=app_unique_id { "group": { "id": "1", "name": "Eng team", "description": "Your friendly eng team." } } ``` ## GET `/groups/{group_id}/users` Returns the users that currently belong to the provided group. **Path params** | param | type | description | | --------- | ------ | -------------------------------------------------------- | | group\_id | string | The id of the group you’re trying to list the details of | **Query params** | param | type | description | | ------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then passed values provided via next\_cursor | **Response params (200)** | param | type | description | | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | users | array of objects | List of the group user objects. See below for what each group should include | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message to expose in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **GroupUser object** | field | type | description | | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | user\_id | string | The user identifier used in the remote system, this will be provided back to you when making access changes. | | email | string | The email of the user. This will be used on our end to correlate it with Opal users. Although not required at the moment, we'd like implementers to include this value as well. | **Example response** ```Text JSON theme={null} { "next_cursor": "bpaubmkeospb", "users": [ { "email": "[email protected]", "user_id": "1", }, { "email": "[email protected]", "user_id": "2", } ] } ``` ## POST `/groups/{group_id}/users` Adds a user to the access list of the specified group. **Path params** | param | type | description | | --------- | ------ | ---------------------------------------------------- | | group\_id | string | The id of the group you’re trying to add the user to | **Body params (JSON encoded)** | param | type | description | | -------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | user\_id | string | The id of the user to add to the group | | custom\_fields | array (optional) | Custom fields from the access request that initiated this provisioning. Only present when provisioning was triggered by a user access request. Each object contains `field_name` (string), `field_type` (string: `SHORT_TEXT`, `LONG_TEXT`, `BOOLEAN`, `MULTI_CHOICE`), and `field_value` (string). | **Example request** ```Text JSON theme={null} { "app_id": "datadog-production", "user_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65", "custom_fields": [ { "field_name": "environment", "field_type": "SHORT_TEXT", "field_value": "production" } ] } ``` **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## DELETE `/groups/{group_id}/users/{user_id}` Remove a user from the access list of the specified group. **Path params** | param | type | description | | --------- | ------ | --------------------------------- | | group\_id | string | The id of the group to be edited. | | user\_id | string | The id of the user to be removed. | **Query params** | param | type | description | | ------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## GET `/groups/{group_id}/resources` Returns the resources that currently belong to the provided group. **Path params** | param | type | description | | --------- | ------ | -------------------------------------------------------- | | group\_id | string | The id of the group you’re trying to list the details of | **Query params** | param | type | description | | ------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then passed values provided via next\_cursor | **Response params (200)** | param | type | description | | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | resources | array of objects | List of the group resource objects. See below for what each group should include | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message to expose in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **GroupResource object** | field | type | description | | ------------- | ------ | ---------------------------------------------------------------------------------------------------------------- | | resource\_id | string | The resource identifier used in the remote system, this will be provided back to you when making access changes. | | access\_level | object | The access level the group has access to the resource with. | **Example response** ```Text JSON theme={null} { "next_cursor": "bpaubmkeospb", "resources": [ { "resource_id": "1", "access_level": { "id": "1", "name": "Admin" } }, { "resource_id": "2", "access_level": { "id": "2", "name": "Read-Only Admin" } } ] } ``` ## POST `/groups/{group_id}/resources` Adds a resource to the access list of the specified group. **Path params** | param | type | description | | --------- | ------ | -------------------------------------------------------- | | group\_id | string | The id of the group you’re trying to add the resource to | **Body params (JSON encoded)** | param | type | description | | ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | resource\_id | string | The id of the resource to add to the group | | access\_level\_id | string (optional) | The ID of the access level to assign to the group to the resource. | **Example request** ```Text JSON theme={null} { "app_id": "datadog-production", "resource_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65", "access_level_id": "12f5ef35-5de6-4224-a006-7fe4c20db5c6" } ``` **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## DELETE `/groups/{group_id}/resources/{resource_id}` Remove a resource from the access list of the specified group. **Path params** | param | type | description | | ------------ | ------ | ------------------------------------- | | group\_id | string | The id of the group to be edited. | | resource\_id | string | The id of the resource to be removed. | **Query params** | param | type | description | | ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints | | access\_level\_id | string (optional) | The ID of the access level associated to the group for this resource. | **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## GET `/groups/{group_id}/member-groups` Returns member groups for the specified group. Required if you enable **Nested Groups** when you create the custom app. **Path params** | param | type | description | | --------- | ------ | --------------------------- | | group\_id | string | The ID of the parent group. | **Query params** | param | type | description | | ------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list users for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then set to the value provided via next\_cursor | **Response params (200)** | param | type | description | | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | groups | array of objects | List of the member groups. See the following **Member group object** for fields. | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Member group object** | field | type | description | | ----------- | ------ | ---------------------------------------------------------------------- | | group\_id | string | The id of the member group that uniquely identifies it in your system. | | name | string | The name of the group. | | description | string | The description of the group. | ## POST `/groups/{group_id}/member-groups` Add a member group to the specified group. Required if you enable **Nested Groups** when you create the custom app. **Path params** | param | type | description | | --------- | ------ | --------------------------- | | group\_id | string | The ID of the parent group. | **Body params (JSON encoded)** | param | type | description | | --------- | ------ | ---------------------------------- | | group\_id | string | The ID of the member group to add. | | app\_id | string | The ID of the custom app. | **Example request** ```Text JSON theme={null} { "group_id": "member-group-id-123", "app_id": "unique-app-id-456", } ``` **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## DELETE `/groups/{group_id}/member-groups/{member_group_id}` Remove a member group from the specified group. Required if you enable **Nested Groups** when you create the custom app. **Path params** | param | type | description | | ----------------- | ------ | ------------------------------------- | | group\_id | string | The ID of the parent group. | | member\_group\_id | string | The ID of the member group to remove. | **Query params** | param | type | description | | ------- | ------ | ---------------------------------- | | app\_id | string | The identifier of your custom app. | **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | ## GET `/users` Returns a list of users for your custom connector app. **Query params** | param | type | description | | ------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | The Opal app ID to list users for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then set to the value provided via next\_cursor | **Response params (200)** | param | type | description | | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | users | array of objects | List of the user objects. See below for what each user should include | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **User object** | field | type | description | | ----- | ------ | ---------------------------------------------------------------------- | | id | string | The id of the user that uniquely identifies it in your system. | | email | string | The email of the user, this is required to associate the user in Opal. | **Example** ```Text JSON theme={null} { "next_cursor": "kjhiufewnoi", "users": [ { "email": "[email protected]", "id": "6bac7de4-08d7-4437-8397-75f44b6f76ae" }, { "email": "[email protected]", "id": "c1623a9c-cfb8-4c50-bd89-ea05bf597d60" }, { "email": "[email protected]", "id": "968bf191-ba81-4fa0-a5a9-aaff71655fbd" }, { "email": "[email protected]", "id": "d67d4184-6cdf-4c43-9d65-698997bd0c52" } ] } ``` ## POST `/users` This endpoint enables users to be provisioned into a custom connector app. This functionality is only available if user provisioning has been enabled for the connection. **Body params (JSON encoded)** | field | type | description | | ---------- | -------------- | ----------------------------------------------------------------- | | user\_id | string | The Opal User ID being created in your end system | | attributes | UserAttributes | The attributes of the user to create in your custom connector app | | manager | UserAttributes | The attributes of the manager of the user | | user\_tags | Array | The tags of the user that were ingested by Opal | **UserAttributes object** | field | type | description | | ----------------- | ------ | ---------------------------------------------- | | email | string | The email of the user | | first\_name | string | The first name of the user | | last\_name | string | The last name of the user | | title | string | The title of the user, if available | | team | string | The team the user is a part of, if available | | profile\_url | string | A URL to the user's profile photo | | secondary\_emails | Array | The secondary emails of the user, if available | **UserTag object** | field | type | description | | -------------- | ------ | ------------------------------------------------------------- | | key | string | The key of the tag in Opal | | value | string | The value of the tag in Opal | | tag\_id | string | The Opal ID of the tag | | connection\_id | string | The Opal ID of the connection where this tag was sourced from | **Response params (200)** | param | type | description | | ---------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | remote\_user\_id | string | The ID of the user in the custom connector app. This must be returned by your implementation and will be used by Opal to refer to this user in the future. | **Response params (Error)** | param | type | description | | ------- | ------- | -------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI | | code | integer | The error code that describes the error. See Error Codes for details | ## DELETE `/users/{user_id}` This endpoint deprovisions user accounts in a custom connector application. This functionality is only available if user deprovisioning has been enabled for the connection. **Path params** | param | type | description | | ---------- | -------------- | ----------------------------------------------------------------- | | user\_id | string | The ID of the user to be removed from the application. | | attributes | UserAttributes | The attributes of the user to create in your custom connector app | | manager | UserAttributes | The attributes of the manager of the user | | user\_tags | Array | The tags of the user that were ingested by Opal | **UserAttributes object** | field | type | description | | ----------------- | ------ | ---------------------------------------------- | | email | string | The email of the user | | first\_name | string | The first name of the user | | last\_name | string | The last name of the user | | title | string | The title of the user, if available | | team | string | The team the user is a part of, if available | | profile\_url | string | A URL to the user's profile photo | | secondary\_emails | Array | The secondary emails of the user, if available | **UserTag object** | field | type | description | | -------------- | ------ | ------------------------------------------------------------- | | key | string | The key of the tag in Opal | | value | string | The value of the tag in Opal | | tag\_id | string | The Opal ID of the tag | | connection\_id | string | The Opal ID of the connection where this tag was sourced from | **Query params** | param | type | description | | ------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | app\_id | string | The app ID specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints. | **Response params (200)** No body is required as part of this endpoint for status code 200. **Response params (Error)** | param | type | description | | ------- | ------- | -------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details | ## GET `/events` This endpoint is only called if **Connector Event Ingestion** is enabled in the [custom app form](/docs/how-to-create-your-own-connector#4-create-a-custom-app). **Query params** | param | type | description | | ------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | app\_id | string | **Required**. The Opal app ID to list events for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints | | cursor | string | For pagination. Empty string on the first call, then set to the value provided via next\_cursor | | start | string | Return events starting from this date. Use the format `YYYY-MM-DDT00:00:00Z`. | | end | string | Return events ending before this date. Use the format `YYYY-MM-DDT00:00:00Z`. | **Response params (200)** | param | type | description | | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | | events | array of objects | List of the event objects. See below for what each event should include | | next\_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all evets have been fetched. | **Response params (Error)** | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message that will be exposed in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | **Event object** | field | type | description | | ------------------------------------------------------ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | event\_id | string | The id of the event that uniquely identifies it in your system. | | event\_type | OneOf: `login.success` | The type of the event. | | [occurred\_at](https://www.rfc-editor.org/rfc/rfc3339) | RFC3339 datetime string | The date and time at which the event occurred. Return as a string using the following format: `{year}-{month}-{day}T{hour}:{minute}:{second}.{microsecond}%z`. | | actor\_user\_identifier | ActorUserIdentifier object | An ActorUserIdentifier object uniquely identifying the user that performed this event. See below for what this object should include. | | event\_content | OneOf: `EventContentLoginSuccess `object | The content of this event, which is specific to the event type. See below for what each event content should include. | **ActorUserIdentifier object** | field | type | description | | ----------- | ------ | ---------------------------------------------------------------------- | | user\_id | string | The id of the user that uniquely identifies it in your system. | | user\_email | string | The email of the user, this is required to associate the user in Opal. | **EventContentLoginSuccess object** | field | type | description | | ------------ | ------ | -------------------------------------------------- | | resource\_id | string | The id of the resource that the user logged in to. | **Example** ```Text JSON theme={null} { "next_cursor": "kjhiufewnoi", "events": [ { "event_id": "1", "event_type": "login.success", "occurred_at": "2025-01-15T00:58:33.967628+0000", "actor_user_identifier": { "user_id": "6bac7de4-08d7-4437-8397-75f44b6f76ae", "user_email": "[email protected]", }, "event_content": {"resource_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65"}, }, { "event_id": "2", "event_type": "login.success", "occurred_at": "2025-01-15T00:58:33.967628+0000", "actor_user_identifier": { "user_id": "[email protected]", "user_email": "c1623a9c-cfb8-4c50-bd89-ea05bf597d60", }, "event_content": {"resource_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65"}, }, { "event_id": "3", "event_type": "login.success", "occurred_at": "2025-01-15T00:58:33.967628+0000", "actor_user_identifier": { "user_id": "d67d4184-6cdf-4c43-9d65-698997bd0c52", "user_email": "[email protected]", }, "event_content": {"resource_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65"}, }, ] } ``` # Error codes All error codes need to implement the following object: | param | type | description | | ------- | ------- | --------------------------------------------------------------------- | | message | string | An error message to expose in the Opal UI. | | code | integer | The error code that describes the error. See Error Codes for details. | In addition, you can distinguish the type of error and meaning by following these codes | Status code | Meaning | | ----------- | ---------------------------------------------------- | | 200 | Response successful. | | 401 | Invalid request signature error. | | 404 | Entity not found (eg. resource, user, access level). | | 429 | Rate limit exceeded. | | 500 | Unexpected error. | All 429 and 500 errors will be retried up to 4 times with linear backoff, with 1 second between retries. # Rate Limiting Opal will respect your custom connector's rate limits if you choose to implement them. If you return a `429` status code, Opal will back off and retry the request up to 4 times. By default, Opal will retry after 1 second. To control when Opal retries, you can include a `Retry-After` header: ``` Retry-After: ``` Where `` is the number of seconds Opal should wait before retrying the request. # Signature To ensure that the API calls originate from Opal, we provide a header in each request that represents the encrypted request payload with a secret that is generated when creating the app connector in Opal. See [Setup connector app in Opal](/docs/how-to-create-your-own-connector#4-create-a-custom-app) for more details. On each HTTP request that Opal sends, we add an `X-Opal-Signature` HTTP header. The signature is created by combining the signing secret with the body of the request we're sending using a standard HMAC-SHA256 keyed hash. Here is an example with Node to compute the signature using your signing secret. You may compare it against the value retrieved from the `X-Opal-Signature` header: ```js Typescript theme={null} const timestamp = request.header('X-Opal-Request-Timestamp') const signingSecret = 'SIGNING_SECRET' // In Typescript/JS, request.body is always an empty object const sigBaseString = 'v0:' + timestamp + ':' + JSON.stringify(request.body) const hmac = crypto.createHmac('sha256', signingSecret); hmac.write(sigBaseString) console.log(hmac.digest('hex')) ``` ```Text Go (Gin) theme={null} func generateSignature( signingSecret string, timestamp string, serializedBlob []byte, ) (string, error) { // Concatenate base string sigBaseString := "v0:" + timestamp + ":" + string(serializedBlob) // Hash base string to get signature hash := hmac.New(sha256.New, []byte(signingSecret)) _, err := hash.Write([]byte(sigBaseString)) if err != nil { return "", errors.Wrap(err, "error writing hash") } return hex.EncodeToString(hash.Sum(nil)), nil } func validateOpalSignature(signingSecret string) gin.HandlerFunc { return func(c *gin.Context) { opalSignature := c.GetHeader("X-Opal-Signature") if opalSignature == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, &Error{ Code: http.StatusUnauthorized, Message: "X-Opal-Signature header is missing", }) return } opalRequestTimestamp := c.GetHeader("X-Opal-Request-Timestamp") if opalRequestTimestamp == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, &Error{ Code: http.StatusUnauthorized, Message: "X-Opal-Request-Timestamp header is missing", }) return } var bodyStr string // Read request body, once the request body is read, it cannot be read again // so we need to save it in a variable and then reassign it to the Request.Body var bodyBytes []byte var err error if c.Request.Body != nil { bodyBytes, err = ioutil.ReadAll(c.Request.Body) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, &Error{ Code: http.StatusInternalServerError, Message: "Unable to read request body", }) return } c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) bodyStr = strings.TrimSpace(string(bodyBytes)) } if bodyStr == "" { bodyStr = "{}" } signature, err := GenerateSignature(signingSecret, opalRequestTimestamp, []byte(bodyStr)) if signature != opalSignature || err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, &Error{ Code: http.StatusUnauthorized, Message: "Invalid signature", }) return } c.Next() } } ``` Note: when the body is empty, do coalesce the empty/null stringified body to `{}`. See the link below for more examples. Check [Create your own connector](/docs/how-to-create-your-own-connector#example-of-x-opal-signatures) for signature examples. # Assign and complete reviews Source: https://docs.opal.dev/docs/assigning-and-completing-reviews-legacy Learn how to assign and complete User Access reviews in Opal. Access Reviews will soon be a legacy feature. Manage your Access Reviews with [Access Campaigns](/docs/access-campaigns), now in beta. This guide assumes you have already [created a User Access Review](/docs/access-reviews-legacy). ## Assign reviewers If you don't see an option to assign reviewers, ensure you're an **Opal Auditor** or an owning team admin. If you are an **Auditor **or **owning team admin**, you can assign reviewers. In the **User Review** tab, you can manage and assign reviewers for user access points. Assign reviewers to a single user row by clicking **Assign Reviewers** for that row, or bulk assign by selecting multiple rows and clicking **Assign Reviewers** in the top right. If you've chosen an auto-assignment policy, you can still manually re-assign reviews. In the **Group Review** tab, you can manage and assign reviewers for group access points. Assign reviewers to a single user row by clicking **Assign Reviewers** for that row, or bulk assign by selecting multiple rows and clicking **Assign Reviewers** in the top right. After you assign a reviewer, Opal shows one of several reviewer statuses. The following are possible status types: * **Not Started: **No reviewer(s) have taken action * **Completed**: All reviewer(s) have completed the review * **Partially Completed: **If there is only one reviewer, then the reviewer has started but has not completed the review. If there are multiple reviewers, then not all reviewers have completed the review. * **Needs Attention:** * This status type indicates an error that needs to be addressed. Click on the **Needs Attention **status to see error details. In this example, the warning indicates **Self reviews are not allowed**, and an admin must add another owner for approval. ## Complete reviews After an admin assigns a reviewer, a snapshot for the resource and/or group is created for review. If an admin changes a resource or group after a review begins, the review won't capture this change. **My Reviews** shows reviews assigned to the logged-in user. After reviewers select a row to review, they are shown an overview of users and groups whose access to a resource must be reviewed. To review access for a resource or group, ensure you select the **row**, not the resource or group name. The **Group by User** and **Group by Resource** buttons control how reviews are grouped, which can be useful to change based on your requirements. These options are available on Opal Cloud and self-hosted Opal versions 1.0.912 and later. For each row, reviewers can: * **Approve** the user or resource by clicking on the **Accept** checkmark button * **Reject** the user or resource by clicking on the **Revoke** x button * Select **Add note** to explain access decisions: UAR reviewers can also modify a user's role by selecting **Swap Roles** in User \< > Resource and User \< > Group mappings. This creates a request that follows the access request flow configured for that resource. This request has to be approved by that resource's reviewers in order for the role modification to propagate. If there is no request flow configured, the user's role will not be modified. A notification will be sent to the reviewer that an attempt to modify that user's role has failed. **Bulk action** can be performed on multiple rows by selecting rows and choosing an option from the top bulk action bar. After you review all users or resources, select **Submit access review** in the bottom right. You cannot modify approvals and revocations after you've submitted. Changes are only propagated to end systems when all the UAR's items have been reviewed and the review is marked as completed. ### Access Changes To view and manage proposed changes, go to the **Access Changes** tab. ### Revocation rules **For connected applications**, Opal automatically revokes access on the end system based on the reviewer's decision, so after submitting a revoke decision, you do not need to perform any more actions. If your connection uses a **custom connector**, you must implement the [DELETE](/docs/api-spec#delete-%2Fgroups%2F%7Bgroup-id%7D%2Fusers%2F%7Buser-id%7D) `/groups/{group_id}/users/{user_id}` or [DELETE](/docs/api-spec#delete-%2Fresources%2F%7Bresource-id%7D%2Fusers%2F%7Buser-id%7D) `/resources/{resource_id}/users/{user_id}` endpoints to revoke users on your end system. If the endpoint returns a 200 success code, Opal marks the access as revoked. If the endpoint is not implemented or returns an error code, access is marked **Needs end-system revocation** and you must manually update it. If the connection uses **webhooks**, access will be marked as **Needs end-system revocation**, because webhooks only perform push events. Opal does not interpret webhook responses, so you'll need to manually mark access as revoked. # Best practices for access requests Source: https://docs.opal.dev/docs/best-practices-for-access-requests Learn about best practices for organizing and configuring resources. As you onboard to Opal, these best practices will help position you for success throughout the configuration, management, troubleshooting, and scaling of access request workflows. ## Ensure high-quality user attributes A powerful user access request workflow requires high-quality user attributes to inform access decisions, both manual and automatic. Dedicate time to reviewing user attributes in your IDP/HRIS to ensure that attributes and their set of values are well-understood and consistently applied. High-quality data ensures streamlined onboarding, and helps with later troubleshooting. Attributes such as `Cost Center`, `Organization` , and `Title` are often used for automated approvals and day-1 access provisioning. Unexpected changes in attributes and their values can have cascading downstream impacts that affect access. Be able to answer: * Who sets this attribute on a user? * How often do the values change? * Is there an established process to cascade change notifications downstream? * What happens when a value is renamed? (e.g., `Organization=Sales & Support` is renamed to `Organization=Customer Success`) You may also need to: * Ensure that any secondary user emails are [correctly merged](/docs/importing-user-secondary-email) * [Re-sync](/docs/sync-schedules-and-triggers) [users and user attributes](/docs/add-your-first-idphr-provider#import-attributes) in Opal to reflect changes your upstream systems ## Clearly name resources Simple, clear naming of resources improves discoverability and reduces access friction when end-users view the Opal catalog. * A name should represent the resource and/or data being exposed, and, combined with the resource description and role, indicate the level of access it represents * Use [catalog names](/docs/curate-catalog) if underlying resource names are less meaningful to your end users * Limit visible catalog resources to a small set of commonly requested items to improve discoverability and reduce friction for end-users Note: Visible resources are not necessarily requestable—[visibility settings](/docs/curate-catalog#visibility) determine whether resources appear in the catalog, and [request configurations](/docs/configure-reviewers) determine whether users can request them ## Define resource types based on levels of approval Not all resources look alike—aim to configure approvals based on the sensitivity of information held by a resource and the level of access granted to the resource. For example: * *Auto-approve*: Use for resources that don’t require security scrutiny, e.g., access to sandbox accounts * *Manager approval required*: Use for resources that are not sensitive, but still require review, e.g., read-only SaaS tools * *Admin approval required*: Use for sensitive resources or resources you’ll need to audit for compliance, e.g., AWS resources * *Custom approval by security team*: Use for production systems or highly sensitive assets, e.g., your production codebase Codifying these categories—defining a `request_type` in Terraform, for example—helps avoid inconsistencies across resource configurations. Note: Opal users can not approve their own requests in an Administrator context or otherwise. Users that attempt to submit requests for a resource where they are the only approver will receive an error. Requests to a resource where the requester is also part of the approvers will not be displayed for approval for these requesters ## Group or bundle resources together If certain resources are often requested together, you can use [bundles](/docs/bundles) to stack requests together, or add [resources to a group](/docs/manage-groups-in-opal#add-resources-to-groups) or [nest groups](/docs/nested-groups), and allow the group to be requestable. **Bundles** are an organizational tool, while **groups** are resources a user can access indefinitely or with time-bound access. * Use bundles if you want to grant *direct* access to resources, but you want to stack requests together. With bundles, requests are still evaluated individually. * Add resources to a group if you’re comfortable granting *indirect* access to resources via the group. This can lead to possible overexposure via group access, but might be more ergonomic for your review process. ## Codify and reuse common settings Use orchestration tools, such as [Terraform](/docs/use-terraform-with-opal) and Opal’s [REST API](/reference/authentication), to maintain settings in code and enforce version control and visibility on your Opal instance. Using Terraform, you may want to set global variables and modules to codify common resource types. To reuse common settings—MFA access, types of reviewers required, etc.—across similar resources, use [configuration templates](/docs/configuration-templates). You can map tags to configuration templates to automatically apply templates to tagged resources—for example, apply the same approval settings to all AWS resources tagged `prod`. *** # Best practices for access reviews Source: https://docs.opal.dev/docs/best-practices-for-user-access-reviews Use this guide before you configure User Access Reviews (UARs) in Opal to set yourself up for a scalable review process. ## Scope reviews appropriately As you think about access reviews, focus on **risk mitigation:** in your reviews, exclude systems that don’t expose access to sensitive assets. For example, your read-only SaaS apps may not be necessary to audit. To keep audits clean and access control transparent, only include users and groups that require review. For example, if you include Okta apps in your access reviews, don’t use Okta groups to manage visibility to resources, since they’ll then be unnecessarily included in the review. Instead, use [Opal groups](/docs/opal-101#groups) to manage visibility, and audit Okta apps for actual access. Your needs may vary based on your compliance requirements. For example, privacy compliance requirements may require a revise of who can read customer data, while this is not a concern for SOX regulation for public companies. ### Use access rules to optimize the review process You may need to review all users who have access to a resource, but in larger environments this can be costly, and review criteria may be identical across many users who have valid reasons to have standing access. To optimize the review process, you can use Opal [access rules](/docs/access-rules) to manage access to resources, rather than direct access. Consult the access rule's conditions first to ensure it properly gates users by attributes. In the subsequent access review, you'll only need to review the single rule, rather than users with direct access. ### Tag resources To keep scopes narrowly focused and easily collect resources, [tag resources and groups](/docs/opal-101#tags), e.g., `Audit:UAR`, then set the review's scope to include items with a given tag. To automate this approach, use [Terraform](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/resources/tag) or the [REST API](/api-reference/tags/post-tags-groups) to manage tags. You can also ingest tags from group attributes in Okta. ### Import non-Opal resources as CSVs If Opal doesn’t provide a native integration for a resource you want to review, you can: 1. Create a [custom](/docs/custom-integrations-overview#push-only-apps-and-webhooks-overview) **Push-only app** without a webhook configured. 2. In the app, import your [resource from a CSV](/docs/import-and-configure-resources-and-groups#import-resources-from-csvs). 3. Include the resource in your access review. ## Determine review frequency based on your needs Your needs will vary based on your organization’s size, maturity, structure, and industry. For example, for higher levels of FEDRAMP access reviews, you may need to produce monthly access review evidence. In general, auditors will likely need to see full access review and remediation completed within a few days or weeks after the access review was created. ## Set up review reminders By default, Opal sends notifications about incomplete access reviews one week before, three days before, one day before, and the day of your review deadline. To get ahead of reviews, you may want to notify reviewers about new reviews assigned to them, and send reminders to their managers. Opal lets you configure these when you create the access review. # Bundles Source: https://docs.opal.dev/docs/bundles Learn how to use bundles to group commonly used resources. Admins can create bundles—collections of resources and groups, contextualized by anticipated usage—to allow users to easily request access to common collections, without over-provisioning access. A bundle is a wrapper for resources. Any resource or group contained in a bundle retains its established policies and configurations. Adding an item to a bundle does not change its admin, owner, approval policies, or visibility settings. For example, given: * Bundle B contains resources X and Y * Resource X is only visible and requestable to users in group G If user U is not in group G, they can request access to bundle B, but only resource Y will be visible and requestable in the bundle. You can manage bundles in the Opal dashboard, using the [REST API](/reference/getbundles), or with [Terraform](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs). ## Create and manage bundles You must be an Opal admin to create a bundle. ### Create bundles Go to the **Inventory** and select the **Bundles** tab. Click on the **+ Bundle** button. Name your bundle and add an optional description: ### Set an admin Select an Opal Owner to own adding and removing items from the bundle. The admin does not gain permission to directly add users to any resources and groups contained in the bundle. ### Add resources and groups After you create the bundle, use the **Resources** and **Groups** tabs to add items to the bundle. ## User flow: request bundles Go to the **Catalog** and select the **Bundles** tab. Click on the appropriate bundle. ### Bulk select Select the resources you need Opal to bulk request. ### Bulk request In the request form, delete any resources you don't need and submit your bulk request. Approved access from bundle requests is propagated only after all items in the bundle have been approved. # Complete Access Reviews Source: https://docs.opal.dev/docs/complete-access-reviews Learn how to complete reviews in an Access Campaign. Your organization uses Opal to run access campaigns to verify if current access is needed. You might be asked to review the access of people you manage or an application you are an owner of. This guide will walk end users through how to complete their reviews in an Access Campaign. ## Start reviewing items If you have been assigned reviews, the campaign will appear under the **Campaign Inbox** tab. Click into the campaign to begin reviewing each item. * To approve, click the checkmark (✔️). * To revoke, click the **X**. * To add a comment, enter text in the corresponding comment box. Reviewing items in a campaign with approve, revoke, and comment controls. Use filtering, grouping, sorting, and bulk select to approve or revoke multiple items at once. Clicking on a row reveals more insight into the access you are reviewing. ## Submit reviews Reviewed items move to **Pending** until you're ready to finalize. Once you've made all your decisions, click **Submit Review**—your reviews will move to the **Completed** tab. If the review is set to **Revoke on decision**, items you’ve revoked will propagate to the end system as soon as you submit review. If the review is set to **Revoke on: Review end**, you can edit your decision in the completed tab up until your admin stops the access campaign. You may check how your admin has configured the access campaign by looking at the campaign’s details on the top right. ## FAQs If your admin has enabled **Reviewer reassignment**, you should see the option next to the revoke button. Image of the revoke button. After you reassign the reviewer, the review item should go into the **Pending** tab for you to submit after you're done making all your changes. If you do not see this option, contact your admin about re-assigning the review item. If you haven't submitted your decision on the review item, you can always change your decision by navigating to **Pending** and correcting it. If you've submitted your decision, you must check how your admin has configured the access campaign by looking at the campaign's details on the top right. * If the review is set to **Revoke on decision**, items you've revoked will already have propagated to the end system. Contact your admin to let them know of the error. * If the review is set to **Revoke on: Review end**, you can still edit your decision in the **Completed** tab until your admin stops the access campaign. Don't forget to submit all changes. # Configuration templates Source: https://docs.opal.dev/docs/configuration-templates **Configuration templates** are groupings of settings you can reuse across multiple resources and groups. Streamlined configuration settings allows your team to move faster, and increases security by avoiding policy drift. ## Create templates To create a template, go to **Templates**, then select **Configuration Templates**. Click **+ Configuration Template**. You can provide a name, select an admin, optionally link a Slack channel, optionally add on-call members, optionally add break-glass users, and configure MFA settings, the same as you would in an resource-specific request configuration. ## Apply templates To apply a template to a resource or group, edit the field on the resource or group page: When a template is applied, **all its access management settings are configured by the values in the template**: * If a configuration template is modified, all resources and groups linked to the template are also modified * The settings cannot be directly edited except by unlinking the configuration template Configuration templates can be set in bulk: ## Set templates using tags Importing based on tags is not supported for custom apps, so template mapping does not apply for custom apps. You can also configure Opal to automatically set a configuration template using tags. Go to **Configuration > Templates > Template Mapping and Priority** and associate a tag with a template that you have configured. When resources or groups are imported with certain tags, the set templates are automatically applied. Note that templates are only applied on **import**, so you must first create and define the mapping, then import the resources. If you import a resource and later define a template or tag the resource, the template will not be applied. You can also reorder template mappings by selecting **Reorder**, so resources with multiple tags use the template with the highest priority. # Configure Async Exports Storage (Self-Hosted) Source: https://docs.opal.dev/docs/configure-async-exports-storage Provision an S3 bucket so your self-hosted Opal instance can serve async data exports. Self-hosted Opal stores async data exports (large CSV/ZIP downloads) in an S3 or S3-compatible bucket. Cloud customers get this bucket automatically. If you self-host, you provision the bucket and provide credentials. The default path below is AWS S3. Opal also supports any S3-compatible object store — see [Alternative: Use Google Cloud Storage (GCS)](#alternative-use-google-cloud-storage-gcs) at the bottom for the GCS variant. This setup is optional. If you skip these steps, exports still work, but they're tied to the browser session — they cancel if you navigate away or close the tab. ## 1. Create the bucket Create a private bucket in the same region as your cluster, with: * All public access blocked * Server-side encryption (AES256 or KMS) * A bucket policy that denies any request where `aws:SecureTransport=false` Use a dedicated bucket per Opal deployment (e.g. `-opal-exports`). Don't share it with other applications or other environments. Do **not** add an S3 lifecycle expiration rule. Opal runs its own file cleanup job, and should be the only thing deleting objects in this bucket. A lifecycle policy might cause previously-saved exports to be orphaned. Versioning and CORS are not required. Downloads are served through the Opal backend, not directly from the browser. ## 2. Create an IAM user and access key Opal authenticates to the bucket with a static access key pair. This is a two-step process: create a dedicated IAM user with a scoped policy, then issue an access key for that user. ### 2a. Create the IAM user Create a dedicated IAM user (e.g. `opal-exports-service`) and attach a policy with **only** these permissions: ```json policy.json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::my-org-opal-exports/*" }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-org-opal-exports" } ] } ``` ### 2b. Issue an access key Generate one access key pair for the user (`aws iam create-access-key --user-name opal-exports-service`, or via the IAM console). Store both the access key ID and secret access key in your secret manager — you'll paste them in step 3. If you provision the user via infrastructure-as-code, create the access key separately so the secret stays out of state. The secret access key is shown only once at creation time. IAM Roles for Service Accounts (IRSA) and Workload Identity are not supported today. Static access keys are the only authentication method. ## 3. Update Opal Configuration Pick the section that matches your install method. ### KOTS Open the admin console and find the **Async Exports Storage** section: 1. Click **Enable Async Exports**. 2. **Storage type**: leave as **AWS S3** (default). 3. Bucket name: `my-org-opal-exports` 4. Region: e.g. `us-east-2` 5. Access key ID and secret access key from step 2. Save and deploy. KOTS encrypts the secret at rest. ### Helm Add the following to your helm values: ```yaml values.yaml theme={null} exportStorage: bucketName: my-org-opal-exports region: us-east-2 accessKey: secretKey: ``` Then run a `helm upgrade` to apply the configuration. ## 4. Verify Run a query in [Opal Query](/docs/opal-query) and trigger an export. You should get a download link and be able to download the exported data. Async exports are currently only supported via Opal Query. ## Alternative: Use Google Cloud Storage (GCS) If you run Opal on GCP and would rather not provision AWS infrastructure, GCS works via its [interoperability mode](https://cloud.google.com/storage/docs/interoperability) — an S3-compatible XML API. The setup mirrors the AWS flow above with three differences: * You create a GCS bucket instead of an S3 bucket. * You generate an **HMAC key** for a service account instead of an IAM user access key. * You point Opal at GCS' S3-compatible endpoint (`https://storage.googleapis.com`) instead of an AWS region. ### 1. Create the GCS bucket In your GCP project, create a bucket with: * **Location type**: Region (close to your cluster) * **Access control**: Uniform (recommended) * **Public access prevention**: Enforced * **Encryption**: Google-managed (default) Versioning is not required. GCS encrypts at rest and rejects plain HTTP by default — no extra policy needed. Do **not** add a Lifecycle rule that deletes objects. Opal runs its own cleanup job and should be the only thing deleting objects in this bucket. ### 2. Create a service account and HMAC key 1. **IAM & Admin** → **Service accounts** → create a dedicated service account (e.g. `opal-exports-service`). Skip the "grant access to project" step — we'll scope to the bucket instead. 2. **Cloud Storage** → your bucket → **Permissions** → **Grant access** → assign the service account the **Storage Object Admin** role on this bucket. 3. **Cloud Storage** → **Settings** → **Interoperability** → **Create access key for service account** → select your service account. 4. Save the **Access key** (starts with `GOOG1...`) and **Secret**. The secret is shown only once. ### 3. Update Opal Configuration **KOTS:** 1. Click **Enable Async Exports**. 2. **Storage type**: select **S3-compatible (e.g. GCS)**. 3. **Bucket name**: your GCS bucket name. 4. **Endpoint URL**: `https://storage.googleapis.com` 5. **Access key ID** and **Secret access key**: from step 2. Save and deploy. **Helm:** Use `endpoint` instead of `region`: ```yaml values.yaml theme={null} exportStorage: bucketName: my-org-opal-exports endpoint: https://storage.googleapis.com accessKey: secretKey: ``` Then run a `helm upgrade` to apply the configuration. ### 4. Verify Same as the AWS flow: run a query in Opal Query, trigger an export, and confirm a `.zip` appears in your GCS bucket under `exports//.zip`. # Configure reviewers Source: https://docs.opal.dev/docs/configure-reviewers Learn how to configure required reviewers for access requests. All resources and groups in Opal can be requestable with configurable approval options and reviewers. Use this guide to learn how admins can configure the reviewers of access requests. ## Owners Owners are users who can be: * **Reviewers**: Users who can review and approve access requests * **Admins**: Users who can manage the full configuration of policies for resources and groups You can manage owners from the **Inventory** > **Owners** tab. There, you can find the following settings for reviewers: 1. **Reviewer Escalation Policy:** * **Notify everyone**: As the default option, Opal notifies all required reviewers at once. Opal requires just one approval from all required reviewers to complete the request. * **Reviewer escalation policy**: Once configured, Opal creates an explicit escalation order. In this example, Opal notifies the first reviewer. After the escalation time has passed, Opal notifies the next reviewer, and so on. 2. **Linked reviewer Slack channel:** Opal creates a channel that receives a message for every access request. 3. **Source group:** Opal keeps the user list for this owner synchronized with a group of your choice. You can still edit the escalation path in the Users tab, but you can't add or remove users from this owner directly. 2262 ### Empty owner notifications Access reviews that require owner approval will fail when owner groups are empty. To receive [notifications](/docs/notifications) about empty owner groups, enable **Error notification setting** in **Configuration** > **Organization Settings** > **Advanced**. ## Approval workflows For resources and groups, the **Request Configuration** section gives admins an overview of the approval logic. You can create multiple request configurations if you want to apply different approval logic for different requesting users, groups, or roles. 2262 ### Custom notification text To send users notifications when they are approved for resources or groups, check the **Include custom notification text with approvals** checkbox in the request configuration or template, then specify a custom message. ### Approval flow In the **Approval Flow** section, admins can: 1. Set approval logic to **Auto-Approve**. When this setting is enabled, access requests are automatically approved. 2262 2. Configure an **Approval Workflow**. * You can include up to three approval **stages**. * Within each stage, approvers can be the resource's **Manager**, an **Owner** or an [**Automation**](/reference/authentication#using-the-api-with-opal-service-users) * If multiple approvers are selected, admins can choose to require **All** or **Any** reviewers. > * **All**: All reviewers must approve the access request to proceed to the next stage. This is **AND** logic. > * **Any**: Any reviewers can approve before the access request proceeds to the next stage. This is **OR** logic. ## Automate Approvals with Service Users Service Users can be assigned as request reviewers to automate approvals based on dynamic conditions. This can be integrated with your own tooling to evaluate if users are compliant with your access policies, such as if security training is complete or if the user is in their authorized work location. When assigning a service user to a request, an automation is configured for that service user. An automation consists of: 1. **When** the automation is triggered. For access requests, use **Assigned to request**. 2. **Then** the action that is taken. For access requests, use **Send webhook**. 3. **Endpoint** that a webhook is sent to. The webhook handler must be able to make Opal API calls to provide an approve, deny, or comment decision. 4. **HMAC Secret** that is used to sign the webhook payload. The payload sent in the webhook is: ```json json theme={null} { "created_at": "2025-10-30T01:09:29.332217Z", "custom_fields_responses": [], "duration_minutes": 60, "id": "47cfe906-cf21-4c70-9b76-bf4c749fc4da", "reason": "I need admin access to change the branch protection rules.", "requested_items_list": [ { "access_level_name": "admin", "access_level_remote_id": "admin", "name": "another-repo", "resource_id": "50031716-6485-4998-bb11-8fb1360385da" } ], "requester_id": "ee84c7db-42f4-4664-a313-fe6102f20e93", "status": "PENDING", "target_user_id": "ee84c7db-42f4-4664-a313-fe6102f20e93", "updated_at": "2025-10-30T01:09:29.382338Z" } ``` Once the webhook is received, you must make an Opal API request as the service user to one of: * [POST /requests/\{id}/approve ](/api-reference/requests/post-requests-approve)to approve the request. * [POST /requests/\{id}/comments](/api-reference/requests/post-requests-comments) to add a comment and leave the request open. * [POST /requests/\{id}/deny](/api-reference/requests/post-requests-deny) to deny the request. Follow the instructions under ["Using the API with Opal Service Users"](/reference/authentication#using-the-api-with-opal-service-users) when making your API request. # Configure SSO and MFA Source: https://docs.opal.dev/docs/configure-sso-and-mfa Learn how to set up MFA for Opal logins and actions. Opal supports MFA for two types of product functions: 1. **Opal logins**: you can configure Opal to require MFA when a user logs in. 2. **Opal actions**: you configure resources in Opal to require MFA for requesting access, approving an access request, and/or connecting to a session. Opal can be set up to require validation via its own MFA provider or via your Okta IDP's MFA provider. ## Enable MFA for Opal logins Toggle the **Require Opal MFA for logins** setting to enable Opal-managed MFA. 1592 Alternatively, you can enable MFA through your SAML provider. In this case, disable this setting. See the [Okta multifactor authentication guide](/docs/okta-multifactor-authentication#using-okta-mfa-for-opal-logins) for more detail on the Okta configuration. ## Enable MFA for Opal actions MFA for Opal actions can be toggled on a per-action, per-resource level. Edit your resource to enable MFA. To modify your MFA Provider settings for Opal Actions, go to **Configuration > Settings > Authentication** and select **Configure** next to **MFA settings for gated Opal Actions**. Three different options for MFA providers are supported: 1. **Opal managed MFA**: Users can register their MFA devices through Opal. 2. **[Okta-managed MFA \[Legacy\]](/docs/okta-multifactor-authentication#using-okta-mfa-for-opal-actions-requesting-approving-connecting-legacy)**: Okta Verify and TOTP. Users may only use these two factors for MFA. 3. **[OIDC MFA](/docs/oidc-provider-setup-for-opal-actions)**: Opal supports any OIDC provider, including Okta and Azure, as a MFA solution. Once configured, users will be able to use any MFA method that your OIDC provider supports, including WebAuthn (Yubikey, TouchID, etc.) and TOTP. ## Reset MFA for users To reset MFA for an individual user, go to the **Inventory** > **Users** tab, then find the user detail page. Select the **...** dropdown on the upper right, then **Reset MFA for User**. Follow the confirmation modal to finish resetting the user's MFA. # Connect Identity or HR Providers Source: https://docs.opal.dev/docs/connect-your-identity-provider-idp Connecting your IDP/HR system allows Opal to sync your organization's identities and attributes from a predefined source of truth, whether your IDP, your HRIS system, or both. Opal allows you to aggregate your identities and their metadata across disparate systems to provide a centralized, complete picture of users in one view. ## How Opal interacts with IDP/HR providers When you connect Opal to your **Identity Provider (IDP)** or **HR Information System (HRIS)**, Opal creates users for your employees and syncs helpful information about them. * Opal automatically imports your organization's user list * Opal can import user information such as manager and title, along with custom attributes you specify * Opal can revoke user access when accounts are terminated in the IDP Note that Opal does NOT currently automatically create Opal users for every individual in your IDP. Opal users are only created when they're also part of an imported app. ## Supported Identity/HR providers Opal currently supports the following IDP/HRIS providers. See these guides for more information: * [Okta Identity Provider](/integrations/okta-idphris-integration) * [Google Identity Provider](/integrations/google-idphris-integration) * [Azure AD Identity Provider](/integrations/azure-entra-idphris-integration) * [Workday HR Provider](/integrations/workday-idphris-integration) ## User deprovisioning When users are deprovisioning in your IDP/HRIS provider, you can enable settings in Opal to automatically remove any access to end systems provisioned by Opal, as well as remove the user's access to Opal. If you enable both settings, end system access will be removed first. Go to **Configuration** > **Organizational Settings** and select the appropriate deprovisioning settings. # Connect Productivity Tools Source: https://docs.opal.dev/docs/connect-your-productivity-tools Learn how to connect Opal with your existing productivity tools. Opal integrates with productivity tools to help your end users access Opal with tools they already use: 1. Communication tools, such as [Slack](/integrations/slack), to streamline notifications and approvals 2. Ticketing providers, such as [Jira](/integrations/jira) and [Linear](/integrations/linear), to integrate [ticket lifecycle](/docs/ticket-propagation) with access requests 3. On-call providers, such as [PagerDuty](/integrations/pagerduty-oncall) and [Opsgenie](/integrations/opsgenie), to automate break-glass access To set up a productivity integration: 1. In the Opal dashboard, go to **Configuration > Settings**. 2. Select **Productivity Integrations**. 3. Click **Connect** next to your desired integration. See the provider-specific guides for more detail on prerequisites and required fields. # Curate end-user catalog Source: https://docs.opal.dev/docs/curate-catalog The following settings are available from a resource's **Edit** page. Use them to customize who can edit resources and how resources appear to end users in the catalog. ## Resource admins **Owners** can be used as **Admins** of a resource, allowing you to decentralize access management. Both **Owners** and **Opal Admins** can manage the configuration of approval and additional security settings from a resource's **Edit** page. ## Allow requests To allow end users to request access to your resource, expand the **Request configuration** section and ensure **Allow requests** is enabled. See [Security settings](#security-settings) to learn how to further customize request flows. ## Allow request extensions Allow end users to extend access from the catalog by toggling **Allow request extensions** in a request configuration, then select a duration. The additional duration is added to the original request expiration time. End users can request extensions from the Opal UI or in Slack. ## Visibility Every resource and group has a **visibility** setting. This is a "hard" visibility setting: a user who doesn't have access, isn't an admin, and isn't in a group that's been granted visibility cannot see the resources or groups in their catalog. Visibility enables you to hide resources from anyone who doesn't have access, or restrict requests to certain groups. * **No visibility restrictions**: This resource/group is visible to all employees. * **Restrict to groups**: Only users in certain groups, admins, and users who have direct access to this item can see the item. 2262 You can set **Import Visibility** at the app level. This creates a default Visibility setting for all resources that are imported from the app. Setting visibility at the app level does not change visibility settings for resources that have already been imported. 2262 2262 ## Security settings Some resources are more sensitive than others. You can apply the following settings to your critical resources: 1. **Require MFA to approve requests (and make connections):** Requires MFA to approve requests via web and Slack. In addition, for certain resources, Opal will also require MFA before end users can connect to the resource. 2. **Maximum duration:** Enforces the maximum amount of time a resource or group can be requested for 3. **Recommended duration:** Shows the recommended duration as the default option in both Slack and web 4. **Require support ticket:** Requires a support ticket to create a request 5. **Custom fields**: Create a standard set of questions for employees to answer before submitting an access request 2262 ### 1. Require MFA to approve requests If **MFA to approve requests** is enabled, Opal triggers an MFA prompt before reviewers can approve requests in Slack and web. This protects sensitive resources and validates the approver's identity. For some resources, Opal can be used to generate short-lived credentials via CLI or web. If MFA is enabled for these resources, end users must validate their identities before connecting to the resource. ### 2. Maximum duration Set a maximum duration to enforce timebound access. If it's unset, duration defaults to the organization-wide maximum duration if present, and indefinite access otherwise. ### 3. Recommended duration If a recommended duration is selected, it shows as the default duration on access requests, but employees can still specify other durations. ### 4. Require support ticket If this is enabled, then a support ticket must be submitted with an access request. This will enable an access request to be dynamically revoked if the support ticket has been completed. This enables a strong security posture as you can revoke access based on the completion of an activity. You can only attach tickets that are assigned to you. If both a time duration and support ticket is enabled, then Opal will take the event that comes first. ### 5. Custom fields Admins can customize questions for employees to answer before submitting an access request to a given resource. 1. To create custom fields, Admins must first go to **Templates** under the in the left-hand bar and click on **+ Custom Access Request** Create a custom access request template 2. After creating the template, admins can set up custom fields by clicking on **Add Block**. Add a block to a custom access request template Field options include the following types: * **Short Answer**: Short text fields * **Paragraph**: Longer text fields * **Checkbox**: A binary selection * **Multiple Choice**: A dropdown selection that only allows one choice * **Multi-select**: A dropdown selection that allows multiple choices * **Callout**: A customizable message that will be shown when a user requests access to a certain group or role Each form field block will allow you to customize it in different ways. You can configure an individual item to require an input by using the toggle on the bottom right of the block, and you can rearrange the order of blocks by clicking and dragging the three dots on the top left. Mark a custom field as required You will see how your fields look like and behave on the right side of the screen, under the **Form Preview** section. Form preview for a custom access request template Once you are done, you can save the template with the **Save Changes** button on the bottom right of the screen. In addition, you can revert the template to its last saved state using the button next to it. 3. Once the template has been created and saved, you can attach your Custom Field template to a resource under the **Custom fields** section: Attach a custom field template to a resource Your fields should now appear when a user requests access to this resource: Custom fields on an access request ## Links to apps in catalog We support direct links to apps in our catalog so that users can launch that app or resource immediately. | App | App-level link | Resource-level links | | ----------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AWS | ✔️ | ✔️ | | AWS IAM Identity Center (SSO) | ✔️ | ✔️ | | Anthropic | ✔️ | Workspace | | Azure / Microsoft Entra ID | ✔️ | Subscription, resource group, management group, storage account/container, VM, SQL server/database/managed instance, user-assigned managed identity, Enterprise App | | ClickHouse | ✔️ | | | Coupa | ✔️ | Role | | Databricks | | Account service principal | | Datadog | ✔️ | Role | | GCP | ✔️ | Project, organization, storage bucket, compute instance, BigQuery dataset/table, service account, Cloud SQL (Postgres/MySQL) | | GitHub | ✔️ | Repository, organization, organization role | | GitLab | ✔️ | Project | | Google Groups | ✔️ | | | Google Workspace | ✔️ | Role | | Grafana | ✔️ | Folder, dashboard, basic role | | HubSpot | ✔️ | | | incident.io | ✔️ | | | NetSuite | ✔️ | Role | | Okta | ✔️ | App (synced sign-on URL), role | | OpenAI Platform | ✔️ | Project, service account | | Oracle Fusion | ✔️ | Role | | Rootly | ✔️ | | | Salesforce | ✔️ | Profile, role, permission set | | Slack | ✔️ | | | Snowflake | ✔️ | Database, schema, table | | Tableau | ✔️ | | | Tailscale | ✔️ | | | Teleport | ✔️ | | | Twingate | ✔️ | Resource | | Workday | ✔️ | Role | | Zendesk | ✔️ | Role | By default, users can navigate to the link to the app or resource regardless of their access. If you want to hide this link, you may do so in **Configuration > Settings > Access Requests > Hide launch links for users without access**. You may input a **custom launch URL** in the catalog at the app level for applications we do not natively populate or to override the standard link. To do so, navigate to the application, click **Edit** on the top right and input your custom **Launch URL**. # Overview Source: https://docs.opal.dev/docs/custom-integrations-overview Learn about different options for building custom integrations in Opal. If you need more flexibility over your integrations than Opal’s built-in integrations provide, or want to connect to a system Opal doesn’t yet support, you can build custom integrations yourself. You create custom integrations as either **Custom Connectors**, which require you build out endpoints yourself, or as **Push-only App**, which you can connect to your configured webhooks, or use as a bookkeeping tool without connecting to end systems. ## Custom Connectors overview Custom Connectors require you to implement API endpoints following Opal’s specifications. They allow for a bidirectional sync and are automatically synced with your end system in the same manner as native integrations. The Opal server responds to user-initiated requests to add or remove users to resources or groups, then propagates these changes to your custom app using signed API requests. Use Custom Connectors if: * You require a bidirectional sync and want to write data back to Opal * You want more control over your integration To set up custom connectors, you first build your custom connector, exposing the [Custom Connector API spec](/docs/api-spec), then create a custom app in Opal using your custom connector configuration. See the [Create your own connector](/docs/how-to-create-your-own-connector) guide for end-to-end instructions. ### Custom connectors architecture The following shows the high-level architecture of key components for custom app integrations. High level architecture of the key component interactions for a custom app integration The Opal components include: * **Opal server** - The Opal server responds to user-initiated requests from the web or Slack interface to add or remove users from resources. These are propagated to your custom app via signed requests to the API connector. * **Opal consistency checker** - On a periodic interval, Opal runs a consistency checker to retrieve the up-to-date user lists of your resources in the custom app via signed requests to the API connector. The components for your organization include: * **API connector** - This is the interface that enables the integration. This connector should expose the API endpoints in the [Custom Connector API Spec](/docs/api-spec). * **Custom app** - The custom app that you want to integrate against. ## Push-only apps and webhooks overview Create **Push-only apps** to push events to configured webhooks or use the app as a bookkeeping tool. If you do not have a webhook configured in your **Organizational Settings**, push-only apps do not connect to any end system. Webhooks use a one-way push to your specified URL on access changes to groups and resources. They allow for only a one-way sync, so you are responsible for any access drift between your end systems and Opal. For example, you could use a one-way sync to send notifications and update issue trackers when users are added and removed from Opal groups. Use Webhooks if: * You require a one-way sync and don’t need to write data back to Opal * You want to add or modify users in your end system in response to Opal events Opal currently supports only one webhook URL per organization. To set up Webhooks, see the [Webhooks guide](/docs/webhooks). Webhooks should not be confused with [Events Streaming](/docs/events-streaming). To send Opal Events to your logging destinations, e.g., DataDog, use Events Streaming. *** # Custom Opal roles Source: https://docs.opal.dev/docs/custom-opal-roles Learn how to create custom Opal roles. Custom Opal roles consist of granular sets of permissions which you can assign to users and groups. Use custom roles to manage users and teams with differing access requirements. See [Special roles in Opal](/docs/roles-in-opal) for an overview of existing, reserved Opal roles—Admin, Auditor, etc. ## Requirements To create or edit custom Opal roles, you must be an [Opal Admin](/docs/roles-in-opal). If you self-host Opal, some permissions require a recent version. The version each newer permission group was introduced in is noted in the [Permissions](#permissions) tables below. ## Create a custom role 1. Go to the **Inventory** and search for the **Opal** app. 2. From the **Assets** tab, select **+ Custom Role**. The Assets tab of the Opal app in the Inventory, with the + Custom Role button highlighted 3. Start with a templated set of permissions based on an existing role—**Full admin**, **Read-only admin**, **Owner**, or **Auditor**—or select **Custom role** to start from scratch. You can add and remove granular permissions in the following step. The template picker with five options: full admin, read-only admin, owner, auditor, and a custom role from scratch 4. Set the role's name, description, and [admin](/docs/configure-reviewers#owners), then optionally update [permissions](#permissions) for the role. Permissions are grouped into a tab per asset type: **Native Apps**, **Resources**, **Groups**, **Users**, **Bundles**, **Access Reviews**, **Owners**, and **Tags**. Within each tab, **General Permissions** always apply to every asset of that type, while **Scoped Permissions** can be limited to specific assets—switch the toggle from **All** to **Scoped** and pick the assets to scope to. The permissions step of the create custom role flow, showing the asset type tabs and the general and scoped permission sections After you save the role, add users and groups to the role from the **User Access** and **Group Access** tabs, as you would grant access to any other resource. ## View and edit a custom role A custom role's permissions live on its **Scope** tab. * The **All**, **Enabled**, and **Disabled** filter controls which permissions the tab lists. It defaults to **Enabled**, so a role with few permissions shows a mostly empty tab—switch to **All** to see every permission and its state. * Select **Edit role** to change permissions or scopes, then **Save**. * Select **Duplicate role** to start a new role pre-filled with this role's permissions, named `Copy of `. Opal's reserved roles—Admin, Auditor, Read-only Admin, and Global Requester—also have a **Scope** tab, without the **Edit role** button. Open one to see exactly which permissions it grants, or select **Duplicate role** to use it as the starting point for a custom role. ## Permissions You can edit permissions on a custom role at any time. Permissions are non-additive, and do not depend on each other. You may need to explicitly add read permissions for edit permissions to take effect. **Read** and **View** permissions indicate the role can access the item in the Inventory and Catalog. See [Role and visibility hierarchy](#role-and-visibility-hierarchy) for examples of how roles interact with existing settings, and [Inherited permissions](#inherited-permissions) for how a scoped permission extends to assets beneath the asset you scoped it to. Every permission below is available on custom roles. The **Applicable roles** column lists which of Opal's reserved roles also hold the permission, where: * **Admin Owner** refers to the Admin set on an app, resource, group, or bundle—not the Opal Admin role—and applies only to the assets they own. * **Group Leader** refers to a [group leader](/docs/group-projects), and applies only to the groups they lead. ### Native apps Applies to all apps: | Permission | Description | Applicable roles | | ---------- | ----------- | ---------------- | | Create | Create apps | Admin | Can be scoped to specific apps: | Permission | Description | Applicable roles | | -------------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------- | | Read | View apps and all their resources and groups (overrides visibility groups) | Admin, Read-only Admin, Auditor, Admin Owner | | View assignments | View all principal assignments with access to groups and resources within the apps | Admin, Read-only Admin, Auditor, Admin Owner | | Export | Export data for any resource and group within the apps | Admin, Read-only Admin, Auditor, Admin Owner | | Sync | Sync apps and all the entities within them | Admin, Admin Owner | | Read all settings and request configurations | View app settings and request configurations for all their resources and groups | Admin, Read-only Admin, Auditor, Admin Owner | | Edit settings | Edit app settings, and the settings for all their resources and groups | Admin, Admin Owner | | Sync Settings | Edit sync settings for the apps, such as credentials and import-all | Admin | | Add and remove tags | Edit tags for all resources and groups within the apps | Admin, Admin Owner | | Edit request configurations | Edit request configurations for any entity within the apps | Admin, Admin Owner | | Import | Import or create groups and resources within the apps | Admin, Admin Owner | | Add, edit, remove users and groups | Edit groups, users, NHIs, and all related resource assignments and their dependencies | Admin, Admin Owner | | Assign UAR reviewers | Assign users to perform access reviews for entities within the apps | Admin, Auditor, Admin Owner | | Delete | Delete apps, and delete any group or resource within an app | Admin, Admin Owner | ### Resources All resource permissions can be scoped to specific resources. | Permission | Description | Applicable roles | | -------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------- | | Read | View resources and, where applicable, all their child resources (overrides visibility groups) | Admin, Read-only Admin, Auditor, Admin Owner | | View assignments | View all users and NHIs with access to the resources and, where applicable, their child resources | Admin, Read-only Admin, Auditor, Admin Owner | | Export | Export data for the resources and all their child resources | Admin, Read-only Admin, Auditor, Admin Owner | | Sync | Sync the resources and all their child resources | Admin, Admin Owner | | Read all settings and request configurations | View settings and request configurations for the resources and all their child resources | Admin, Read-only Admin, Auditor, Admin Owner | | Edit settings | Edit settings for the resources and all their child resources | Admin, Admin Owner | | Add and remove tags | Edit tags for the resources and all their child resources | Admin, Admin Owner | | Edit request configurations | Edit request configurations for the resources and their child resources | Admin, Admin Owner | | Add, edit, remove users and groups | Edit groups, users, NHIs, and all related resource assignments and their dependencies | Admin, Admin Owner | | Assign UAR reviewers | Assign users to perform access reviews for the resources | Admin, Auditor, Admin Owner | | Delete | Delete the resources | Admin, Admin Owner | ### Groups All group permissions can be scoped to specific groups. | Permission | Description | Applicable roles | | -------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------- | | Read | View groups (overrides visibility groups) | Admin, Read-only Admin, Auditor, Admin Owner | | View assignments | View all users with access to the groups | Admin, Read-only Admin, Auditor, Admin Owner, Group Leader | | Export | Export data for the groups | Admin, Read-only Admin, Auditor, Admin Owner | | Sync | Sync the groups | Admin, Admin Owner | | Read all settings and request configurations | View settings and request configurations for the groups | Admin, Read-only Admin, Auditor, Admin Owner | | Edit settings | Edit settings for the groups | Admin, Admin Owner | | Add and remove tags | Edit tags for the groups | Admin, Admin Owner | | Edit request configurations | Edit request configurations for the groups | Admin, Admin Owner | | Add, edit, remove users and groups | Edit groups, users, and NHIs for the groups | Admin, Admin Owner, Group Leader | | Request on behalf | Request access on behalf of the groups | Admin, Admin Owner, Group Leader, Global Requester | | Assign UAR reviewers | Assign users to perform access reviews for the groups | Admin, Auditor, Admin Owner | | Delete | Delete the groups | Admin, Admin Owner | **Request on behalf** for groups requires version 1.1200 or later. It lets a role—or a service user holding that role—create access requests for a team without being an Opal Admin or a group leader of that team. Requests still go through the normal approval chain, and the permission grants no ability to edit group membership. ### Users Applies to all users: | Permission | Description | Applicable roles | | -------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------- | | Read | View all users in the Inventory | Admin, Read-only Admin, Auditor, Global Requester | | Create | Create Opal users | Admin | | Delete | Delete any users | Admin | | Export | Export all users | Admin, Read-only Admin, Auditor | | Reset Opal MFA | Reset Opal MFA for all users in the organization. Not applicable for OIDC or Okta MFA | Admin | Can be scoped to specific users: | Permission | Description | Applicable roles | | ------------------- | ----------------------------------------------------------------------- | ---------------- | | Edit settings | Edit settings (manager, team when not synced with an IdP) for the users | Admin | | Add and remove tags | Edit tags for the users | Admin | Requesting access on behalf of specific *users* is not available on custom roles. It remains limited to Opal Admins and the [Global Requester](/docs/roles-in-opal) role. To let a custom role request for a team, use **Request on behalf** in the groups tab. ### Bundles Applies to all bundles: | Permission | Description | Applicable roles | | ---------- | ----------------------------------- | ---------------- | | Create | Create bundles for the organization | Admin | Can be scoped to specific bundles: | Permission | Description | Applicable roles | | -------------------------------------------- | ------------------------------------------------------ | -------------------------------------------- | | Read | View bundles (overrides visibility groups) | Admin, Read-only Admin, Auditor, Admin Owner | | Delete | Delete bundles | Admin, Admin Owner | | Read all settings and request configurations | View bundle settings | Admin, Read-only Admin, Auditor, Admin Owner | | Edit settings | Edit bundle settings | Admin, Admin Owner | | Add, edit, remove groups and resources | Edit all assigned resources and groups for the bundles | Admin, Admin Owner | ### Access reviews All access review permissions apply to all access reviews. | Permission | Description | Applicable roles | | -------------- | ------------------------------------------------------------------ | ------------------------------- | | Read | View all access reviews, and all the reviewer assignments | Admin, Read-only Admin, Auditor | | Create | Create access reviews | Admin, Auditor | | Stop | Stop access reviews | Admin, Auditor | | Edit settings | Edit access review settings, such as name, deadline, and reminders | Admin, Auditor | | Send reminders | Send reminders to reviewers for access reviews | Admin, Auditor | | Export | Export all role assignments for all the access reviews | Admin, Read-only Admin, Auditor | To assign reviewers, use the **Assign UAR reviewers** permission in the native apps, resources, or groups tabs. Access review schedules do not have their own permissions. The **Read**, **Create**, and **Edit settings** permissions above also cover the corresponding actions on schedules; deleting a schedule remains limited to Opal Admins and Auditors. ### Owners All owner permissions apply to all owners. | Permission | Description | Applicable roles | | ---------------------------------- | -------------------------- | ---------------- | | Create | Create owners | Admin | | Add, edit, remove users and groups | Edit the members of owners | Admin | | Edit settings | Edit owner settings | Admin | | Delete | Delete owners | Admin | Owner permissions require version 1.1093 or later. Before that, creating, updating, and deleting owners was restricted to Opal Admins. ### Tags All tag permissions apply to all tags. | Permission | Description | Applicable roles | | ---------- | ----------- | ------------------------------- | | Read | View tags | Admin, Read-only Admin, Auditor | | Create | Create tags | Admin | | Delete | Delete tags | Admin | Tag permissions require version 1.1190 or later. These permissions govern the tags themselves. To let a role change which tags are applied to an asset, use **Add and remove tags** in the native apps, resources, groups, or users tabs instead. ### Not available on custom roles The following remain restricted to Opal's reserved roles and cannot be granted through a custom role: | Capability | Applicable roles | | ----------------------------------------- | -------------------------------------------- | | View configuration templates | Admin, Read-only Admin | | Create and delete configuration templates | Admin | | Edit configuration template settings | Admin | | Request on behalf of specific users | Admin, Global Requester | | View Risk Center | Admin, Read-only Admin | | View events | Admin, Read-only Admin, Admin Owner, Auditor | | View global settings | Admin, Read-only Admin | | Edit global settings | Admin | ## Role and visibility hierarchy Custom roles take precedence over [visibility settings](/docs/curate-catalog#visibility). If a user is assigned a role which gives view access to an asset (resource, group, etc.), but the asset's visibility settings do not include the user's groups, the user can still view the asset. Note that edit permissions do **not** imply view permissions. If you grant a role the permission to edit a resource, but not permission to view it, permission to view the resource is not included in the role. In that case, the user may be prevented from viewing the resource, depending on visibility settings. For example: * Custom Role A has permissions to edit, but **not** view, Resource B. * Resource B has group visibility settings set to allow Group C to view the group. * User D and User E are granted access to Custom Role A. * User D is a member of Group C. * In this case, User D will be able to view and edit Resource B, but User E will not. ## Inherited permissions A scoped permission extends downward from the asset you scoped it to: * A permission scoped to an **app** also applies to every resource and group in that app. * A permission scoped to a **resource** also applies to that resource's nested resources. If Resource A contains nested Resource B, a permission granted on Resource A also applies to Resource B. Groups do not inherit permissions from each other—a permission scoped to a group applies to that group only, even if it contains nested groups. To cover every group in an app, scope the permission to the app instead. ## Manage roles via API To create a scoped role with the API, use [POST /resources](/api-reference/resources/post-resources) with `app_id` set to the Opal connection ID and `resource_type` set to `OPAL_SCOPED_ROLE`. To set scoped permissions, use `PUT /resources/{resource_id}/scoped-role-permissions`. To read scoped permissions assigned to a role, use `GET /resources/{resource_id}/scoped-role-permissions`. # Delegate reviews Source: https://docs.opal.dev/docs/delegate-reviews Learn how to schedule delegated access request reviews. If you're going on vacation or need to delegate reviews to other members of your organization for a set period, you can create a schedule to temporarily add additional reviewers to access requests. On self-hosted Opal, delegated reviews are available on versions 1.1032.0 and later. ## Schedule delegated request reviews Currently, users can only create delegations for themselves, not other users. Any user can create a delegated request review. To create a delegated request review: 1. Select your profile in the lower left corner of the Opal dashboard, then **Settings** > **Delegates**. 2. Select **+ Delegation** and enter the user to add to reviews, a date range, and reason for the delegation. You can view and delete delegated reviews from the same page, as well as see schedules for reviews delegated to you. ## Review delegated requests Delegated access requests appear in the **Inbox** tab on the **Requests** page, as any other pending request, and the delegated reviewer is shown as **Delegated**. Delegated reviewers are added as additional reviewers, not replacements. ## Manage delegations Admins can create, view, and delete all schedules for delegated reviews from **Inventory** > **Delegations**. ## Notifications and events Delegated reviewers receive the same [notifications](/docs/notifications) as any other reviewer. [Events](/docs/event-types) are logged when you create and delete delegation schedules. Access requests and revocation events include details in descriptions if reviews were delegated from another user. ## Manage delegations programmatically Use the `/delegations` [endpoints](/api-reference/delegations/get-delegations) in the REST API and the delegations [resource](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/resources/delegation) and [data source](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/data-sources/delegation) in Terraform to progammatically manage your request delegations. *** # Disaster recovery guide Source: https://docs.opal.dev/docs/disaster-recovery-guide In this guide, you'll learn about potential failure modes when running self-hosted Opal and how to recover from them to minimize downtime. Typical disaster recovery events may include: * **Database is not responsive**: See the [configure and restore database backups](#configure-database-backups) section to get the database back up and running. * **Kubernetes cluster is not responsive**: Follow the steps in the [reinstall cluster](#reinstall-cluster) section. ## Reinstall cluster To reinstall the cluster: 1. If Opal is still accessible, toggle the “read-only mode” in Admin settings before cutting traffic over to the new cluster. 2. Have a copy of the database encryption keys securely stored in a password manager. Opal won’t be able to use the data from an existing database on the new cluster without these keys. 3. Follow the installation instructions while re-using the existing encryption keys and database: 1. [AWS EKS instructions](/docs/self-host-opal-aws-guide) 2. [GKE instructions](/docs/self-host-opal-gke-guide) 4. We can re-use the existing database, or recreate it from a backup if it's unhealthy. 5. Restore the Admin console backup, if available. This will restore all Kubernetes objects, the Admin console configuration and Opal version. 6. Disable “read-only mode” in Admin settings. 7. Update DNS to point to the load balancer on the new cluster. ## Configure database backups Time estimate: 15 min The database is the most sensitive piece of the Opal infrastructure as it holds all the state. The only thing not included is the Admin console configuration. It’s a good practice to set up automated backups in the event that data need to be recovered, or if the database becomes unhealthy. Here are the steps to enable backups on Amazon RDS: 1. Select the database instance, then click **Modify**: 2. Navigate to the **Additional configuration** section and select a Backup retention period. You may also enable Backup replication in another region. This can be helpful in the event of an outage with an entire AWS region. We recommend setting a backup retention period of 30 days. 3. Click **Continue** to finish the backup configuration, then you can select **Apply immediately**. Finally, click **Modify DB instance**. 4. Automated backups are now enabled. ## Restore the database from a backup Time estimate: 30 min Here are the steps to restore a database from a backup on Amazon RDS: 1. Select the database instance, then click the **Actions** button, then click **Restore to point in time**. 2. Select a date to restore data from a specific snapshot: 3. Make sure that the rest of the configuration settings are matching with the existing instance. 4. Click **Restore to point in time** at the bottom. 5. A new instance will come online. We now need to switch Opal over to use the restored database. 6. Rename the existing database: 7. Then rename the restored database to match the original database name. This way it will have the same hostname, and there is no additional change needed on the application side. 8. Triggering a new deployment in the Admin console may be needed to force the application to reset the database connection. ## Back up the Admin console Time estimate: 30 min Backups of the Opal Admin console can be created through KOTS. To enable backup support, you will need to install and configure Velero on your cluster. This section describes backing up the admin console and all application data. 1. **Installing the Velero CLI** 1. Open a terminal in the environment that you manage the cluster from, which can be a local machine that has `kubectl` installed. 2. Check for the latest supported release of the Velero CLI in the Velero GitHub repo at [https://github.com/vmware-tanzu/velero/releases](https://github.com/vmware-tanzu/velero/releases). We recommend using the latest supported version. For more information about supported versions, see [Velero Version Compatibility](https://docs.replicated.com/enterprise/snapshots-understanding#velero-version-compatibility). Note the version number for the next step. 3. Run the following command to download the latest supported Velero CLI version for the Linux AMD64 operating system to the cluster: ```shell shell theme={null} curl -LO https://github.com/vmware-tanzu/velero/releases/download/VERSION/velero-VERSION-linux-amd64.tar.gz ``` Replace VERSION with the version number using the format `vx.x.x`. Example: ```shell shell theme={null} curl -LO https://github.com/vmware-tanzu/velero/releases/download/v1.10.1/velero-v1.10.1-linux-amd64.tar.gz ``` 4. Run the following command to uncompress the TAR file: ```shell shell theme={null} tar zxvf velero-VERSION-linuxamd64.tar.gz ``` Replace VERSION with the version number using the format `vx.x.x`. 5. Run the following command to install the Velero CLI: ```shell bash theme={null} sudo mv velero-VERSION-linux-amd64/velero /usr/local/bin/velero ``` Replace VERSION with the version number using the format `vx.x.x`. 6. Run `velero version` to test that the Velero CLI installation worked correctly. You might get an error message stating that there are no matches for the server version. This is acceptable, as long as you get a confirmation for the client version. After the Velero installation, you also see the server version. 2. **Set up backups on AWS** 1. Follow the instructions for [installing Velero on AWS](https://github.com/vmware-tanzu/velero-plugin-for-aws#setup) in the Velero documentation. 2. Run the `velero install` command with these additional flags: `--use-node-agent --uploader-type=restic --use-volume-snapshots=false` Example: ```bash bash theme={null} velero install \ --provider aws \ --plugins velero/velero-plugin-for-aws:v1.7.1 \ --bucket $BUCKET \ --backup-location-config region=$REGION \ --secret-file CREDS_FILE \ --use-node-agent \ --uploader-type=restic \ --use-volume-snapshots=false ``` 3. **Creating full backups** Full backups, or instance snapshots, back up the admin console and all application data. You can create a full backup with either the CLI or the Admin console. * Create a backup with KOTS CLI To create a full backup with the Replicated kots CLI, run the following command: ```bash bash theme={null} kubectl kots backup --namespace NAMESPACE ``` Replace `NAMESPACE` with the namespace where the admin console is installed. * Create a backup with KOTS Admin Console To create a full backup in the admin console: 1. Go to **Snapshots** > **Full Snapshots (Instance)**. 2. Click **Start a snapshot**. When the backup is complete, it appears in the list of backups on the page, as shown here: 4. **Restoring from backups** From a full backup, you can do any of the following types of restores using the kots CLI: * Full restore: Restores the admin console and the application * Partial restore: Restores the application only * Admin console: Restores the admin console only If you are restoring to a healthy cluster, you can skip reinstalling Velero and continue to running the `get backups` and `restore` commands in the last two steps. To restore a backup on an existing cluster: 1. (New or Unhealthy Clusters Only) Install a version of Velero compatible with the one that was used to make the snapshot backup: refer to section 2 for instructions. 2. Run the `kubectl kots get backups` command to get a list of backups. 3. Run the following command to restore a full backup: ```bash bash theme={null} kubectl kots restore --from-backup BACKUP ``` Replace `BACKUP` with the the name of the backup to restore from. # Access Request FAQs Source: https://docs.opal.dev/docs/end-user-faq Learn answers to frequently asked questions for end users requesting access with Opal. ## What is Opal? Opal is an access management platform that helps organizations securely manage access to resources. As an end user, you'll primarily use Opal to request access to resources, review your existing access, and participate in access reviews. ## How do I request access to assets? Browse, search, and request access to assets directly from the Opal UI or Slack. Request access through the **Catalog** or using the **Request Access** button. You can also find apps from the **Search** page. After selecting an app, you can choose **Resources** that you want to request. **Resources** are specific permissions within applications. Example resources include: * Salesforce: Roles, profiles, and permission sets * GitHub: Repositories, Teams * Amazon Web Services: IAM Roles, databases (RDS), and compute (EC2) To make a request, open the resource and select **Request** in the **Actions** column, or select **Request Access** in the sidebar. Enter the following fields: * **Reason**: By default, this is a required field. Admins can hide this field using the [request configuration](/docs/curate-catalog) settings. Ensure you include enough context in your reason for your reviewers to approve your request. * **Expires in**: Specify how long you need access. Use the default values, or request a custom range by selecting **Custom**. * **Expire access when ticket is closed**: If you don't know how long you'll need access, you can bind the access request to a support ticket that is assigned to you. Once the ticket is completed, your access will be automatically revoked. You may request access on behalf of another user. For security reasons, this functionality is limited to the following groups. * **Opal Admins** can request access on behalf of all users for all resources. * **Resource and group admins** can request access on behalf of others for those resources and groups where they are an admin. * **Managers** can request access on behalf of their reports for those resources to which the manager has access. You can navigate Opal assets and submit access requests directly in Slack using the `/opal` slash command. ### Search for requestable assets (recommended) Run this search command from any Slack channel to find requestable assets. `/opal search ` Select an asset from the results to start an access request, or click **View results on web** to see the full list of results on the web UI. Example of the /opal search command in Slack ### Browse available assets Run the command by itself to browse assets by App. `/opal` Image of full request modal in Slack ### Using `/access` instead Both commands work identically—use whichever you prefer: * `/access` (opens browse modal) * `/access search ` (searches directly) ## What happens after my access request? Once approved, you'll be notified over email and in Slack. Go to **My Access** in the Opal web **Catalog** to view all resources you currently have access to. If you want to learn more about your request, you can click on the **Access request pending** button in Slack, or go to the **Requests** page in the Opal dashboard. From here you can: * See the status of your request * See who the reviewers are * Send reminders to your reviewers * Cancel your request * Escalate approval of your request to your skip manager by clicking **Escalate to skip manager** ## How can I escalate my request? If your approver is your manager, you can escalate your request to your skip manager by selecting **Escalate to skip manager** on the request. Your skip manager will be notified to approve your request. This is especially useful if your manager is unavailable and your request is urgent. ## How do I check the status of my access request? You can view the status of your access requests in the **Sent** tab of the **Requests** page. Each request shows its current status (Pending, Approved, Denied, etc.) and any pending approval steps. ## How long does my access request take to process after it's approved? Most approved access requests are propagated to end systems within two minutes. ## How can I approve access requests? You will be notified via Slack and email if you are a reviewer. In Slack, approvers can approve or reject access requests. This automatically provisions access. Requestors and requestees can also leave comments in Slack. Replies in the thread via Slack will show as comments in Opal. These comments bi-directionally sync with Opal's web UI. ## Can I approve my own access requests? No. Currently access requests can not be approved by the submitter. Users are blocked from creating request conditions where they are the only possible approver, and submitters do not receive approval notifications for assets that they have requested where they are also a member of the approval owner. ## What happens if my access expires? If you have been granted short-lived access to a resource and the time limit has expired, then Opal will automatically revoke your access. If your request is for more than 24 hours, Opal will send out notifications one day and one hour before to remind you to extend access. You will only receive a reminder notification if your access originated from a request. You will not get a notification if you were manually added to the resource or group by an admin. If your organization has enabled it, you may be able to extend your access from Slack or the Opal UI. Extended access does not need to be approved again. When your access expires after the requested duration, Opal sends you a Slack notification with a link to easily re-request. # Escalate if no response Source: https://docs.opal.dev/docs/escalate-if-no-response Automatically move an access request to a second set of reviewers when the first reviewers don't respond in time. Access requests stall when the assigned reviewer is asleep, on vacation, or buried in their inbox. Escalation adds a timer to a stage of your approval workflow: if nobody reviews the request within the time you set, Opal automatically escalates it to an additional set of reviewers. ## Prerequisites * You must be an Opal Admin, or an **Admin** of the resource or group you're configuring. * The stage you add escalation to must use **Any** logic. Escalation can't be added to a stage that requires **All** reviewers to approve. See [Approval flow](/docs/configure-reviewers#approval-flow). ## How escalation works When you add escalation to a stage, Opal adds an **Escalation stage** directly after it. The escalation stage contains the original stage's reviewers *plus* the owners and users you escalate to. * If any original reviewer responds before the timer expires, the request proceeds normally and the escalation stage is satisfied at the same time. Nothing extra happens. * If nobody responds before the timer expires, the request advances to the escalation stage, and the escalated reviewers are notified. * The original reviewers stay on the escalation stage, so they can still approve after escalation. Escalation widens the pool of people who can act; it doesn't take the request away from anyone. The escalation stage is derived from the stage above it. To change its reviewers, edit the original stage—reviewers you add there are copied forward automatically. Escalation only widens the pool of reviewers. It never grants access on its own—a request that nobody acts on still expires the way it normally would. ## Configure escalation 1. Go to the resource or group's **Edit** page and expand **Request Configuration**. 2. In the **Approval Flow** section, find the stage you want to add a timer to and select **Escalate if no response**. Approval Flow section of a request configuration. Stage 1 has an owner assigned and a row of buttons below it: Requester's manager, Entity Admin, Secure with Paladin, and Escalate if no response. 3. Under **Escalate after**, choose how long to wait before escalating: | Option | Wait time | | ---------- | ------------------------------------------- | | 30 minutes | 30 minutes | | 1 hour | 1 hour | | 2 hours | 2 hours | | 4 hours | 4 hours | | 8 hours | 8 hours | | 24 hours | 24 hours | | Custom | Any value from 1 to 1440 minutes (24 hours) | 4. Select the **owners** and **users** to escalate to. You can pick any combination, but you must select at least one. The "Escalate if no one responds" dialog showing an Escalate after dropdown set to Custom, a Custom delay field, and two selects for owners and users to escalate to. 5. Select **Add escalation**. The new **Escalation stage** appears below the stage you configured. 6. Save the request configuration. To remove escalation later, delete the **Escalation stage**. The original stage keeps its reviewers. ## What escalation looks like on a request On a pending request, the **Reviewers** section labels both halves of the pair. The original stage is tagged with the wait time you configured, and the stage below it is tagged **ESCALATION**. A pending request's Reviewers section. Stage 1 is tagged "escalates if not reviewed in 240 minutes" and Stage 2 is tagged "escalation", with the same owner and reviewers listed on both stages. An Escalation Timer service user appears as an individual reviewer on Stage 1 with a status of Approved. Opal carries out the timeout with an **Escalation Timer** service user, which Opal provisions for you and adds as a read-only reviewer on the timed stage. When the window expires, the timer clears that stage so the request advances, and comments on the request—`No reviewer responded in time - escalating.`—so the handoff is visible in the **Activity** feed. The timer only moves the request forward to the escalation stage. It is not an approval of the request: the escalated reviewers still have to approve before access is granted. ## Examples ### Production database access — 4 hours An engineer requests read access to the production database during an incident. First-line reviewers are the database owners; the escalation reviewers are the on-call SRE rotation. * **Escalate after:** 4 hours * **Escalate to:** SRE on-call If the database owners don't respond within 4 hours, the on-call engineer picks it up. Nobody stays blocked during an incident waiting for a specific person to wake up. ### Marketing tool seat — 24 hours A new hire requests a seat in a marketing SaaS tool. The team manager reviews these, and the escalation reviewers are the marketing ops team. * **Escalate after:** 24 hours * **Escalate to:** Marketing Ops The window is deliberately loose. A seat request isn't urgent, but it also shouldn't disappear for two weeks while the manager is travelling. ### Admin role on a sensitive system — 8 hours, escalating to security A developer requests an admin role on a system holding customer data. The resource owner reviews first; the escalation reviewers are the security team. * **Escalate after:** 8 hours * **Escalate to:** Security Here escalation isn't about speed, it's about making sure a high-risk request always gets looked at by someone. It never quietly sits in one person's inbox. ## Related * [Configure reviewers](/docs/configure-reviewers) * [Notifications](/docs/notifications) # Events Source: https://docs.opal.dev/docs/event-types Use Events to audit access updates and additional events in Opal. Opal guarantees display and exports of logs up to 90 days. To indefinitely retain and export your logs, you should set up [Events Streaming](/docs/events-streaming). In the **Events** section of the left sidebar, administrators can view, filter, and export all audit events. Exports can be created by downloading CSVs using the **Export** button, or with the [/events API endpoint](/api-reference/events/get-events). Administrators can also create **Saved filters** for Events to easily access a view of events for a date range, user, event type, Object ID, or API token. The events include all changes for configurations in Opal, all actions taken in Opal, and all access changes. ## Event types Opal records the following events. | Event type | | --------------------------------------------------------------- | | `ACCESS_REVIEWS_CREATED` | | `ACCESS_REVIEWS_UPDATED` | | `ACCESS_REVIEW_AUTO_ASSIGN_REVIEWER_BY_APPROVERS` | | `ACCESS_REVIEW_AUTO_ASSIGN_REVIEWER_BY_MANAGER` | | `ACCESS_REVIEW_AUTO_ASSIGN_REVIEWER_BY_OWNING_TEAM_ADMIN` | | `ACCESS_REVIEW_CONNECTION_REVIEWED` | | `ACCESS_REVIEW_CONNECTION_REVIEWERS_UPDATED` | | `ACCESS_REVIEW_GROUP_REVIEWED` | | `ACCESS_REVIEW_GROUP_REVIEWERS_UPDATED` | | `ACCESS_REVIEW_RESOURCE_ACCESS_TO_GROUP_ACCEPTED` | | `ACCESS_REVIEW_RESOURCE_ACCESS_TO_GROUP_REVOKED` | | `ACCESS_REVIEW_RESOURCE_REVIEWED` | | `ACCESS_REVIEW_RESOURCE_REVIEWERS_UPDATED` | | `ACCESS_REVIEW_RESOURCE_USER_SUPPORT_TICKET_LINKED` | | `ACCESS_REVIEW_RESOURCE_USER_SUPPORT_TICKET_UNLINKED` | | `ACCESS_REVIEW_REVIEWER_FOR_CONNECTION_USER_SET` | | `ACCESS_REVIEW_REVIEWER_FOR_GROUP_RESOURCE_SET` | | `ACCESS_REVIEW_REVIEWER_FOR_GROUP_USER_SET` | | `ACCESS_REVIEW_REVIEWER_FOR_RESOURCE_USER_SET` | | `ACCESS_REVIEW_REVIEW_PERFORMED` | | `ACCESS_REVIEW_TEMPLATES_CREATED` | | `ACCESS_REVIEW_TEMPLATES_DELETED` | | `ACCESS_REVIEW_TEMPLATES_UPDATED` | | `ACCESS_REVIEW_USER_ACCESS_TO_CONNECTION_ACCEPTED` | | `ACCESS_REVIEW_USER_ACCESS_TO_CONNECTION_REVOKED` | | `ACCESS_REVIEW_USER_ACCESS_TO_GROUP_ACCEPTED` | | `ACCESS_REVIEW_USER_ACCESS_TO_GROUP_REVOKED` | | `ACCESS_REVIEW_USER_ACCESS_TO_GROUP_REVOKED_FROM_END_SYSTEM` | | `ACCESS_REVIEW_USER_ACCESS_TO_RESOURCE_ACCEPTED` | | `ACCESS_REVIEW_USER_ACCESS_TO_RESOURCE_CHANGED` | | `ACCESS_REVIEW_USER_ACCESS_TO_RESOURCE_REVOKED` | | `ACCESS_REVIEW_USER_ACCESS_TO_RESOURCE_REVOKED_FROM_END_SYSTEM` | | `API_TOKEN_CREATED` | | `API_TOKEN_DELETED` | | `BREAK_GLASS_USED` | | `BREAK_GLASS_USERS_ADDED_TO_GROUPS` | | `BREAK_GLASS_USERS_REMOVED_FROM_GROUPS` | | `BUNDLES_CREATED` | | `BUNDLES_DELETED` | | `BUNDLES_UPDATED` | | `BUNDLE_GROUPS_ADDED` | | `BUNDLE_GROUPS_REMOVED` | | `BUNDLE_RESOURCES_ADDED` | | `BUNDLE_RESOURCES_REMOVED` | | `CONNECTIONS_CREATED` | | `CONNECTIONS_DELETED` | | `CONNECTIONS_UPDATED` | | `CONNECTION_USERS_UPDATED` | | `CONNECTION_VISIBILITY_GROUPS_ADDED` | | `CONNECTION_VISIBILITY_GROUPS_REMOVED` | | `EVENT_MONITOR_EVENTS_DETECTED` | | `GROUPS_ADDED_TO_FOLDERS` | | `GROUPS_CREATED` | | `GROUPS_DELETED` | | `GROUPS_REMOVED_FROM_FOLDERS` | | `GROUPS_UPDATED` | | `GROUP_ACCESS_REQUEST_ESCALATION_PERIOD_UPDATED` | | `GROUP_ADMIN_OWNER_UPDATED` | | `GROUP_AUTO_APPROVAL_UPDATED` | | `GROUP_FOLDERS_CREATED` | | `GROUP_FOLDERS_DELETED` | | `GROUP_FUNCTION_UPDATED` | | `GROUP_IS_REQUESTABLE_UPDATED` | | `GROUP_MAX_DURATION_UPDATED` | | `GROUP_NAME_UPDATED` | | `GROUP_RECOMMENDED_DURATION_UPDATED` | | `GROUP_REQUEST_REQUIRE_MFA_UPDATED` | | `GROUP_REQUIRE_MANAGER_APPROVAL_UPDATED` | | `GROUP_REQUIRE_MFA_UPDATED` | | `GROUP_REQUIRE_SUPPORT_TICKET_UPDATED` | | `GROUP_REVIEWER_STAGE_CREATED` | | `GROUP_REVIEWER_STAGE_DELETED` | | `GROUP_REVIEWER_STAGE_UPDATED` | | `GROUP_VISIBILITY_GROUPS_ADDED` | | `GROUP_VISIBILITY_GROUPS_REMOVED` | | `GROUP_VISIBILITY_UPDATED` | | `HRIS_STATUS_ACTIVE` | | `HRIS_STATUS_DELETED` | | `HRIS_STATUS_DEPROVISIONED` | | `HRIS_STATUS_INACTIVE` | | `HRIS_STATUS_NOT_FOUND` | | `IDP_CONNECTIONS_CREATED` | | `IDP_CONNECTIONS_DELETED` | | `IDP_CONNECTIONS_UPDATED` | | `IDP_CONNECTION_USER_ATTRIBUTE_MAPPING_CREATED` | | `IDP_CONNECTION_USER_ATTRIBUTE_MAPPING_DELETED` | | `IDP_STATUS_ACTIVE` | | `IDP_STATUS_DELETED` | | `IDP_STATUS_DEPROVISIONED` | | `IDP_STATUS_INACTIVE` | | `IDP_STATUS_NOT_FOUND` | | `MESSAGE_CHANNELS_ADDED_TO_GROUPS` | | `MESSAGE_CHANNELS_ADDED_TO_RESOURCES` | | `MESSAGE_CHANNELS_CREATED` | | `MESSAGE_CHANNELS_DELETED` | | `MESSAGE_CHANNELS_REMOVED_FROM_GROUPS` | | `MESSAGE_CHANNELS_REMOVED_FROM_RESOURCES` | | `ON_CALL_SCHEDULES_ADDED_TO_GROUPS` | | `ON_CALL_SCHEDULES_CREATED` | | `ON_CALL_SCHEDULES_DELETED` | | `ON_CALL_SCHEDULES_REMOVED_FROM_GROUPS` | | `ON_CALL_SCHEDULES_UPDATED` | | `ON_CALL_SCHEDULES_UPDATED_FOR_GROUPS` | | `ORG_SETTINGS_UPDATED` | | `OWNERS_CREATED` | | `OWNERS_DELETED` | | `OWNERS_UPDATED` | | `OWNER_REVIEWER_CHANNEL_REMOVED` | | `OWNER_REVIEWER_CHANNEL_UPDATED` | | `OWNER_SOURCE_GROUP_REMOVED` | | `OWNER_SOURCE_GROUP_UPDATED` | | `OWNER_USERS_ADDED` | | `OWNER_USERS_REMOVED` | | `OWNER_USERS_UPDATED` | | `REQUESTS_ADMIN_APPROVED` | | `REQUESTS_ADMIN_DENIED` | | `REQUESTS_APPROVED` | | `REQUESTS_CANCELED` | | `REQUESTS_CREATED` | | `REQUESTS_DENIED` | | `REQUEST_COMMENT_ADDED` | | `REQUEST_GROUP_REQUESTED` | | `REQUEST_RESOURCE_REQUESTED` | | `REQUEST_REVIEWERS_ADDED_TO_REQUESTS` | | `REQUEST_REVIEWERS_APPROVED` | | `REQUEST_REVIEWERS_DENIED` | | `REQUEST_SKIP_MANAGER_ADDED_TO_REQUESTS` | | `REQUEST_SUPPORT_TICKET_ADDED` | | `RESOURCES_ADDED_TO_FOLDERS` | | `RESOURCES_CREATED` | | `RESOURCES_DELETED` | | `RESOURCES_REMOVED_FROM_FOLDERS` | | `RESOURCES_UPDATED` | | `RESOURCE_ADMIN_OWNER_UPDATED` | | `RESOURCE_APPROVE_REQUIRE_MFA_UPDATED` | | `RESOURCE_AUTO_APPROVAL_UPDATED` | | `RESOURCE_CONNECT_REQUIRE_MFA_UPDATED` | | `RESOURCE_FOLDERS_CREATED` | | `RESOURCE_FOLDERS_DELETED` | | `RESOURCE_IS_REQUESTABLE_UPDATED` | | `RESOURCE_MAX_DURATION_UPDATED` | | `RESOURCE_NAME_UPDATED` | | `RESOURCE_RECOMMENDED_DURATION_UPDATED` | | `RESOURCE_REQUEST_REQUIRE_MFA_UPDATED` | | `RESOURCE_REQUIRE_MANAGER_APPROVAL_UPDATED` | | `RESOURCE_REQUIRE_SUPPORT_TICKET_UPDATED` | | `RESOURCE_REVIEWER_STAGE_CREATED` | | `RESOURCE_REVIEWER_STAGE_DELETED` | | `RESOURCE_REVIEWER_STAGE_UPDATED` | | `RESOURCE_VISIBILITY_GROUPS_ADDED` | | `RESOURCE_VISIBILITY_GROUPS_REMOVED` | | `RESOURCE_VISIBILITY_UPDATED` | | `REVIEWERS_ADDED_TO_GROUPS` | | `REVIEWERS_ADDED_TO_RESOURCES` | | `REVIEWERS_ESCALATED` | | `REVIEWERS_REMINDED` | | `REVIEWERS_REMOVED_FROM_GROUPS` | | `REVIEWERS_REMOVED_FROM_RESOURCES` | | `ROLE_ASSIGNMENTS_CREATED` | | `ROLE_ASSIGNMENTS_DELETED` | | `ROLE_ASSIGNMENTS_UPDATED` | | `SESSIONS_CREATED_FOR_RESOURCES` | | `THIRD_PARTY_INTEGRATION_CREATED` | | `THIRD_PARTY_INTEGRATION_DELETED` | | `TOXIC_SET_VIOLATIONS_CREATED` | | `TOXIC_SET_VIOLATIONS_REMEDIATED` | | `TOXIC_SET_VIOLATIONS_UPDATED` | | `USERS_ADDED_TO_CONNECTIONS` | | `USERS_CREATED` | | `USERS_DELETED` | | `USERS_REMOVED_FROM_CONNECTIONS` | | `USERS_UPDATED` | | `USER_EMAIL_UPDATED` | | `USER_LOGGED_IN_OAUTH` | | `USER_LOGGED_IN_SAML` | | `USER_MANAGER_UPDATED` | | `USER_MERGED` | | `USER_NAME_UPDATED` | | `USER_NOTIFIED` | | `USER_NOTIFICATION_FAILED` | | `USER_NOT_NOTIFIED` | | `USER_POSITION_UPDATED` | | `USER_REMOTE_ID_UPDATED` | | `USER_TEAM_ATTR_UPDATED` | ### Propagation events | Event type | | ------------------------------------------------- | | `PROPAGATED_ADD_RESOURCE_TO_GROUP` | | `PROPAGATED_ADD_USER_TO_GROUP` | | `PROPAGATED_ADD_USER_TO_RESOURCE` | | `PROPAGATED_REMOVE_RESOURCE_FROM_GROUP` | | `PROPAGATED_REMOVE_USER_FROM_CONNECTION` | | `PROPAGATED_REMOVE_USER_FROM_GROUP` | | `PROPAGATED_REMOVE_USER_FROM_RESOURCE` | | `PROPAGATION_FAILURE_ADD_RESOURCE_TO_GROUP` | | `PROPAGATION_FAILURE_ADD_USER_TO_GROUP` | | `PROPAGATION_FAILURE_ADD_USER_TO_RESOURCE` | | `PROPAGATION_FAILURE_REMOVE_RESOURCE_FROM_GROUP` | | `PROPAGATION_FAILURE_REMOVE_USER_FROM_CONNECTION` | | `PROPAGATION_FAILURE_REMOVE_USER_FROM_GROUP` | | `PROPAGATION_FAILURE_REMOVE_USER_FROM_RESOURCE` | | `PROPAGATION_MANUAL_ADD_USER_TO_RESOURCE` | | `PROPAGATION_MANUAL_REMOVE_USER_FROM_RESOURCE` | | `PROPAGATION_SUCCESS_ADD_RESOURCE_TO_GROUP` | | `PROPAGATION_SUCCESS_ADD_USER_TO_GROUP` | | `PROPAGATION_SUCCESS_ADD_USER_TO_RESOURCE` | | `PROPAGATION_SUCCESS_REMOVE_RESOURCE_FROM_GROUP` | | `PROPAGATION_SUCCESS_REMOVE_USER_FROM_CONNECTION` | | `PROPAGATION_SUCCESS_REMOVE_USER_FROM_GROUP` | | `PROPAGATION_SUCCESS_REMOVE_USER_FROM_RESOURCE` | | `PROPAGATION_TICKET_UPDATED_REMOTELY` | | `PROPAGATION_TIMED_OUT` |

Deprecated

events `GROUP_RESOURCE` events were updated in self-hosted version 1.990.0, `GROUP_USERS` events in version 1.1016.0, and `RESOURCE_USER` events in version 1.970.0. The following table lists events that have been migrated and now map to `ROLE_ASSIGNMENTS_*` events. | Deprecated events | New event | | -------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `GROUPS_ADDED_TO_GROUPS`, `USERS_ADDED_TO_GROUPS`, `RESOURCES_ADDED_TO_GROUPS`, `USERS_ADDED_TO_RESOURCES` | `ROLE_ASSIGNMENTS_CREATED` | | `GROUPS_REMOVED_FROM_GROUPS`, `USERS_REMOVED_FROM_GROUPS`, `RESOURCES_REMOVED_FROM_GROUPS`, `USERS_REMOVED_FROM_RESOURCES` | `ROLE_ASSIGNMENTS_DELETED` | | `GROUP_GROUPS_UPDATED`, `GROUP_USERS_UPDATED`, `GROUP_RESOURCES_UPDATED`, `RESOURCE_USERS_UPDATED` | `ROLE_ASSIGNMENTS_UPDATED` | ### Remote events Remote events are accessible from the **Usage** tab on Okta apps, AWS IAM roles, and resources within custom connectors. They are not included in CSV exports nor returned from the `/events` API. | Event type | | ------------------------------------------ | | `REMOTE_EVENT_GROUP_ADDED_TO_RESOURCE` | | `REMOTE_EVENT_GROUP_REMOVED_FROM_RESOURCE` | | `REMOTE_EVENT_GROUP_USED` | | `REMOTE_EVENT_GROUPS_CREATED` | | `REMOTE_EVENT_GROUPS_DELETED` | | `REMOTE_EVENT_LOGIN_SUCCESS` | | `REMOTE_EVENT_RESOURCE_READ` | | `REMOTE_EVENT_RESOURCES_CREATED` | | `REMOTE_EVENT_RESOURCES_DELETED` | | `REMOTE_EVENT_USER_ADDED_TO_GROUP` | | `REMOTE_EVENT_USER_ADDED_TO_RESOURCE` | | `REMOTE_EVENT_USER_REMOVED_FROM_GROUP` | | `REMOTE_EVENT_USER_REMOVED_FROM_RESOURCE` | # Events Streaming Source: https://docs.opal.dev/docs/events-streaming Set up Opal to stream Events to your logging destination in near real-time. Events Streaming allows administrators set up a near real-time stream of Opal Events to a configured URL destination of choice (e.g. SIEM/logging sink). You can easily and flexibly integrate rich access data from Opal into your existing security logging stack for further auditing, notification, or detection purposes. ## Configuration Event streaming is available on Opal versions 1.970.0 and later. Use the following steps to set up event streaming in Opal. ### 1. Add Event Streaming Connection Go to **Settings > Event Streaming** and select **+ Event Streaming Connection**. ### 2. Fill in connection details 1. Provide a **Name** for the Event Stream Connection. 2. Enter a **URL** webhook endpoint you would like Opal to send the Events payloads to. 3. Select your **Authorization Type**. 4. Click **Add Connection**. Opal will send a test `POST` message to the end system to ensure that the endpoint can be reached before allowing for the Connection to be successfully configured. ### Authorization Types #### 1. API Key Add a `Key` , `Value` pair via: * `Header` (recommended) * `Query Parameter` (not recommended for secrets) #### 2. HMAC Verify that requests originated from Opal by checking signatures against a shared signing secret. Opal adds `X-Opal-Signature` HTTP header on each request sent. The signature is created by combining the signing secret with the body of the request sent using a standard HMAC-SHA256 keyed hash. A connection may have a max of 2 HMAC secrets to facilitate key rotation without any downtime. In this case, the value of `X-Opal-Signature` will be comma delimited (e.g., `X-Opal-Signature: , `), with each signature corresponding to one of the HMAC secrets. ##### How to: Verify HMAC Signature ```javascript javascript theme={null} const timestamp = request.header('X-Opal-Request-Timestamp') const signingSecret = 'SIGNING_SECRET' const sigBaseString = 'v0:' + timestamp + ':' + JSON.stringify(request.body) const hmac = crypto.createHmac('sha256', signingSecret); hmac.write(sigBaseString) const actualSignature = hmac.digest('hex')) const expectedSignature = request.header('X-Opal-Signature') console.log("Are equal?", actualSignature === expectedSignature) ``` ## Events payload All Event Streaming payloads have the same structure. Fields that are bespoke to the `event_type` will be nested under the `data` field, as in the following example object. ```json json theme={null} { "id": "cb8318b9-5719-4e5f-9740-a345650e22ca", "transaction_id": "491a0ece-5f7c-4227-957e-932977aca70b", "event_type": "EVENT_STREAM_CONNECTIONS_TESTED", "severity": "INFO", "timestamp": "2024-10-22T18:20:31.62251Z", "actor_email": "[email protected]", "actor_ip_address": "127.0.0.1", "actor_name": "Tester tester", "actor_user_id": "23b48b0e-db20-45ac-bb0c-7520e978d655", "actor_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", "data": { "connection_type": "WEBHOOK", "message": "This is a test message to validate the event stream connection" } } ``` ### Fields | Name | Description | | ------------------ | ------------------------------------------------------------------------------- | | `id` | The unique identifier for this event | | `transaction_id` | An identifier to group related events | | `event_type` | The event that took place | | `severity` | The severity level of the event. Possible values are `INFO`, `WARNING`, `ALERT` | | `timestamp` | The time the event happened | | `actor_email` | Email of the user that triggered the event | | `actor_ip_address` | IP address of the user that triggered the event | | `actor_name` | The name of the user that triggered the event | | `actor_user_id` | The identifier of the user that triggered the event | | `actor_user_agent` | The agent used by the user that triggered the event | | `data` | Fields that are bespoke to the `event_type` | ## Limitations You can set up at most three connections. ## Enable and disable connections New Event Stream Connections are **Enabled** by default. If you need to **Disable** the connection—e.g. you need to rotate an API Key or change query parameters—edit the connection, then disable the **Connection Enabled** toggle. This pauses streaming of events to the endpoint. If the connection is disabled for ***7+ days***, the next time it is re-enabled, it will stream from the latest events, instead of the time at which it was paused. ## Errors and notifications ### Errors and delivery failures Admins can see a list of failed event deliveries from the last 30 days. ### Notifications sent to admins Notifications are sent to admins in the following cases: * If messages for any connection failed to be delivered (daily notification) * Connection has been auto-disabled by Opal due to an abnormally high failure rate (non- 200 Status Code, frequent timeouts, etc) ## Example end systems to receive events ### Datadog To configure your Event Stream to point to directly to Datadog: 1. Get the [Logs API](https://docs.datadoghq.com/api/latest/logs/) URL that corresponds to your [Datadog Site](https://docs.datadoghq.com/getting_started/site/#access-the-datadog-site). 2. Create a [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/#add-an-api-key-or-client-token). 3. In Opal, go to **Configuration > Organization Settings > Event Streaming** (`/settings#event-streaming`), and create a new connection: 1. Name the connection. 2. Set the **URL** to the Logs API URL you copied in the previous step. 3. Select **API Key** as the **Authorization Type** and enter the following values: 1. **Key**: `DD-API-KEY`. 2. **Value**: \ 3. Under **Add to**, select **Header**. 4. **Optional**. To include metadata, specify a second API Key for the Log API's `ddtag` query string parameter. 5. Click **Add Connection**. ### Sumologic To configure your Event Stream to point directly to Sumologic: 1. Generate an [Access Key](https://help.sumologic.com/docs/manage/security/access-keys/) in Sumo Logic. 2. [Base64 encode](https://help.sumologic.com/docs/api/getting-started/#base64-example) the Access Key. 3. In Sumo Logic, create an [HTTP source](https://help.sumologic.com/docs/send-data/hosted-collectors/http-source/logs-metrics/) for a hosted Collector. If a hosted Collector doesn’t already exist, then [create a new hosted Collector](https://help.sumologic.com/docs/send-data/hosted-collectors/) 4. Copy the HTTP source URL. 5. In Opal, go to **Configuration > Organization Settings > Event Streaming** (`/settings#event-streaming`), and create a new connection: 1. Name the connection. 2. Enter the URL you copied in the previous step. 3. Select **API Key** and set the following values. 1. **Key**: `Authorization` 2. **Value**: `Basic ` 3. Under **Add to**, Select **Header**. 6. Select **Add Connection**. ### RunReveal To stream Opal events to RunReveal: 1. Create a [webhook source](https://docs.runreveal.com/sources) in RunReveal. You can optionally configure HMAC signature verification in this step. 2. Copy the webhook URL. 3. In Opal, go to **Configuration > Organization Settings > Event Streaming** and create a new connection. 4. Name the connection, enter the webhook URL. 5. Optionally select **HMAC** as the **Authorization Type** and save the generated secret in your RunReveal configuration. 6. Select **Add Connection**. # Google SAML Setup Source: https://docs.opal.dev/docs/google-saml-setup Learn how to configure Opal to authenticate users via Google SAML SSO. Use this guide to configure Opal to allow or require users to log in with Google SAML SSO. ## Setup 1. When logged into Google Workspace as an administrator, select **Apps > Web and mobile apps** on the left sidebar. 2. Click on **Add App**, then **Add custom SAML app**. 3. Name your SAML app **Opal**. You can use [this brand asset](https://drive.google.com/uc?export=download\&id=17DAV1wD6Ldc4hPmrX1smq4i4s6w-1brL) as the app icon. When ready, click **Continue**. 4. Copy the value of the **SSO URL **field. 5. In the **Settings > Authentication** section of the Opal dashboard, click **Setup** and paste the **SSO URL** into the **Identity Provider SAML 2.0 SSO URL (HTTPS)** field. 2262 2262 6. Download the Google IDP public certificate by clicking the down arrow icon next to the certificate. 7. In Opal, upload the certificate with the **Upload Certificate** button. 2262 8. Click **Save Changes** to save this data to Opal. 9. Open the **Setup** modal again and copy the **ACS URL **and **Entity ID** values. Go back to the Google SAML app creation page and paste these fields to the corresponding **ACS URL **and **Entity ID** fields. 10. Click **Continue**. 11. In the **Attribute Statements** page, map the following **Google Directory** attributes to **App attributes**: * **First name**: **given\_name** * **Last name**: **family\_name** * **Primary email**: **email** 12. Click **Finish** to complete the creation of the Google SAML app. 13. Turn on the SAML app by selecting **OFF for everyone** in the SAML app page, then **ON for everyone**, then **Save.** ## Test login via SAML 1. Go to Opal, then log out of your Opal account. 2. Click on **Continue with SAML** on the Opal login screen. 3. Manually type your email address in the **Email field**. This email must have the same domain name as the user who created the SAML app in the Opal UI. For example, if `stephen@opaltest.com` created the SAML integration, the SAML integration will be tied only to users with the `opaltest.com` domain. 4. Click **Continue with SAML**. This should prompt you to log in with Google. 5. You may arrive at the following linking screen. If so, click **Continue** and log in with the account corresponding to your email address. 804 7. At this point, you should be able to log into Opal. ## Test IDP-initiated flow from Gmail 1. Log in to Gmail. 2. On the upper right corner, click on the **Google Apps** dots icon, then **Opal**. 3. This should prompt you to log in with Google. 4. At this point, you should be able to log into Opal. ## Common issues Adding a SAML connection in Google and enabling it for your users can take up to **24 hours** to propagate. As a result, there are occasionally caching issues after you add a new SAML connection. Manifestations of this problem include the following errors: * **403 app\_not\_configured** * **403 not\_a\_saml\_app** * **500** In some cases, these caching issues can be circumvented by **clearing browser state** for Opal and Google, or attempting to log in using an incognito browser tab or a different browser. The most consistent fix is to wait up to 24 hours for the app to propagate. # Group leaders Source: https://docs.opal.dev/docs/group-projects With the **Group leader** role, users can request access, optionally timebound, on behalf of a group. The group leader role can allow you to set up groups that map to temporary cross-functional internal projects, especially when used in tandem with other group features, such as nested groups. ## Group leaders A user with the **Group leader** role can request access to a resource on behalf of a group, add and remove users to a group, and remove access to items for a group. To assign a group leader to a group, go to the resource detail page and select the edit icon. Next, check the box enabling group leaders and select user(s) to assign leaders. To request a resource for a group, a group leader can request access to a resource as usual, and choose the group to request on behalf of from the dropdown list. To add or remove users from a group, go to the group detail page, select the **User Access** tab, and select the users to add or remove. Alternatively, select the users you want to add with the **+ Add Users** button. ## JIT access for group projects After access to an item on behalf of a group has been requested and approved, the item shows on the group detail page under the **Resources** tab. Items in the **Resources** tab show the expiration date of the item, or indefinite if the access to the resource is not timebound. ## Revoke access from groups Group leaders can revoke user access to a group or group access to an item at any time by removing the user or item in from the **User Access** and **Resources** tabs, respectively, on a group detail page. Select **Remove** from the **User Access** page to revoke user access. Select **Remove** from the **Resources** tab to remove the resource. ## API endpoints For group projects, you can use Opal's API to retrieve the user ID of group leaders, add a group leader to a group, and make a request by a group leader on behalf of the group. * To get a group leader, use [GET /groups](/reference/getgroups) * To add a group leader, use [PUT /groups](/reference/updategroups) * To make a request, use [POST /requests](/reference/createrequest) *** # Create your own connector Source: https://docs.opal.dev/docs/how-to-create-your-own-connector Learn how to build custom connectors to connect Opal with any end system. To bootstrap development, Opal provides an [OpenAPI spec](https://github.com/OAI/OpenAPI-Specification) to help you automatically generate a server using [OpenAPI Generator](https://github.com/openapitools/openapi-generator). This guide provides instructions to implement the [required endpoints](/docs/api-spec) to use in a custom Opal app, then connect these endpoints to a custom Opal app. See Opal's [custom DataDog connector](https://github.com/opalsecurity/opal-datadog-connector) for an example server implementation. ## 1. Bootstrap connector server 1. If you use MacOS and [Homebrew](https://brew.sh/), use the following command to install OpenAPI Generator. ``` brew install openapi-generator ``` See [OpenAPI Generator README](https://github.com/openapitools/openapi-generator?tab=readme-ov-file#1---installation) for ways to install the generator app if you're not on MacOS. 2. Save the [Opal Custom App Connector API spec](https://raw.githubusercontent.com/opalsecurity/opal-connector-spec/main/openapi-connector-spec.yaml) as a YAML file. 3. Choose a server generator from the [OpenAPI Generator documentation](https://openapi-generator.tech/docs/generators/#server-generators). Opal's example uses `go-gin-server`. 4. Generate the connector server stub, replacing `go-gin-server` with your preferred server generator. See the available configuration options for your chosen server generator to tweak the output of the generated code. ``` openapi-generator generate \ -g go-gin-server \ -i openapi-connector-spec.yaml \ -o opal-myconnector-connector ``` ## 2. Implement Custom Connector API endpoints 1. After you generate the boilerplate code, navigate into the generated code directory. In the DataDog example: ``` cd opal-myconnector-connector ``` See the generated `README.md` file for instructions on how to run the code. You can change how you run and deploy the code based on your requirements and needs. 2. On your server, implement the endpoints specified in Opal's [Custom Connector API Spec](/docs/api-spec). Your implementation will vary based on your server and end system. Note the [Nested Resources in connectors](/docs/how-to-create-your-own-connector#nested-resources-in-connector) section as you implement the `/resources` endpoint. In the DataDog example, the `/resources` endpoints are implemented in the `api_resources.go` [file](https://github.com/opalsecurity/opal-datadog-connector/blob/main/datadogconnector/api_resources.go), and uses DataDog's [Go API client](https://github.com/DataDog/datadog-api-client-go) to connect to DataDog. 3. To verify requests originate in Opal, it's strongly recommended you validate requests using the `X-Opal-Signature` header. See more in the [Signatures](/docs/api-spec#signature) section, and in `routes.go` in the [example DataDog connector](https://github.com/opalsecurity/opal-datadog-connector/blob/main/datadogconnector/routers.go). ### Nested Resources in connectors Due to how hierarchical resources work in other systems, Opal assumes the following: * If a user has access to a parent resource, the user also has access to all its child resources. * If a child resource is imported into Opal, all its parent resources will also be imported. If you enable **Nested Resources** in the [connector setup](#4-create-a-custom-app), Opal assumes the following behavior: * `GET /resources`: instead of listing ALL the available resources in the connector, only the resources **without a parent** (root resources) are returned. Returning a resource that has a parent in this call might result in duplicate resources or errors. * `GET /resources?parent_id=`: when passing the ID of a resource as a query param, only the immediate children of this resource need to be returned. If no children belong to this resource, an empty list should be returned instead. For example, given this resource structure: Opal expects the following responses, given the following calls, to and from the connector. | Query | Response | | --------------------------------------- | ----------------------------------------- | | `GET /resources` | `[resource_a, resource_b, resource_c]` | | `GET /resources?parent_id=resource_a` | `[resource_aa]` | | `GET /resources?parent_id=resource_aa` | `[]` | | `GET /resources?parent_id=resource_b` | `[]` | | `GET /resources?parent_id=resource_c` | `[resource_ca, resource_cb, resource_cd]` | | `GET /resources?parent_id=resource_ca` | `[]` | | `GET /resources?parent_id=resource_cb` | `[resource_cba]` | | `GET /resources?parent_id=resource_cd` | `[]` | | `GET /resources?parent_id=resource_cba` | `[]` | ### Example request from Opal to the connector The following is a snapshot of requests made from Opal to a generic connector implementation, containing the raw path with query parameters, method, body, and example of signatures. You can use this to unit test your connector implementation. ```Text csv theme={null} URL|path|raw_query|method|headers|body|signing_secret|timestamp|signature http://localhost:8080/resources?app_id=dsdd|/resources|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/resources/1/users?app_id=dsdd|/resources/1/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/resources/1/access_levels?app_id=dsdd|/resources/1/access_levels|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/resources/2/users?app_id=dsdd|/resources/2/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/resources/2/access_levels?app_id=dsdd|/resources/2/access_levels|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/resources/3/users?app_id=dsdd|/resources/3/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/resources/3/access_levels?app_id=dsdd|/resources/3/access_levels|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups?app_id=dsdd|/groups|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/1/users?app_id=dsdd|/groups/1/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/1/resources?app_id=dsdd|/groups/1/resources|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/2/users?app_id=dsdd|/groups/2/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/2/resources?app_id=dsdd|/groups/2/resources|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/3/users?app_id=dsdd|/groups/3/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/3/resources?app_id=dsdd|/groups/3/resources|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/users?app_id=dsdd|/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835750"],"X-Opal-Signature":["896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835750|896277c63817aadbbdfea1f4fff18359b40e5ec5bd2b9dfc9aa449ee84c07ce4 http://localhost:8080/groups/1/resources/3?app_id=dsdd|/groups/1/resources/3|app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835866"],"X-Opal-Signature":["0c2044d2396e84917dc8050b1cb54cecaab5bc96ba814133eecf2adc9d6d4de8"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835866|0c2044d2396e84917dc8050b1cb54cecaab5bc96ba814133eecf2adc9d6d4de8 http://localhost:8080/groups/1/users/1?app_id=dsdd|/groups/1/users/1|app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835879"],"X-Opal-Signature":["244ae2057b438e7145df029bfd9a311f1be075b13d239b76c80f7223b90110d4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835879|244ae2057b438e7145df029bfd9a311f1be075b13d239b76c80f7223b90110d4 http://localhost:8080/groups/1/users/3?app_id=dsdd|/groups/1/users/3|app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835879"],"X-Opal-Signature":["244ae2057b438e7145df029bfd9a311f1be075b13d239b76c80f7223b90110d4"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835879|244ae2057b438e7145df029bfd9a311f1be075b13d239b76c80f7223b90110d4 http://localhost:8080/groups/1/users|/groups/1/users||POST|{"Accept":["application/json"],"Content-Type":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835889"],"X-Opal-Signature":["9a7fc343b92665839500263b0c6f14ff00bd33bdf1af37985525b2d51a12b7a1"]}|{"app_id":"dsdd","user_id":"5"}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835889|9a7fc343b92665839500263b0c6f14ff00bd33bdf1af37985525b2d51a12b7a1 http://localhost:8080/resources/3/users/3?access_level_id=2&app_id=dsdd|/resources/3/users/3|access_level_id=2&app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835901"],"X-Opal-Signature":["93030371458e338ba2f3b835854e3433bdef3ad4012659418977f58d42bc806f"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835901|93030371458e338ba2f3b835854e3433bdef3ad4012659418977f58d42bc806f http://localhost:8080/resources/3/users/3?access_level_id=1&app_id=dsdd|/resources/3/users/3|access_level_id=1&app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835901"],"X-Opal-Signature":["93030371458e338ba2f3b835854e3433bdef3ad4012659418977f58d42bc806f"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835901|93030371458e338ba2f3b835854e3433bdef3ad4012659418977f58d42bc806f http://localhost:8080/groups/1/resources/3?access_level_id=2&app_id=dsdd|/groups/1/resources/3|access_level_id=2&app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690835924"],"X-Opal-Signature":["04d5c6cad627f37263b957d8bce37c5fd743a480b927359f4af2171724d47970"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690835924|04d5c6cad627f37263b957d8bce37c5fd743a480b927359f4af2171724d47970 http://localhost:8080/groups/2?app_id=dsdd|/groups/2|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836017"],"X-Opal-Signature":["8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836017|8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6 http://localhost:8080/groups/2/users?app_id=dsdd|/groups/2/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836017"],"X-Opal-Signature":["8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836017|8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6 http://localhost:8080/groups/2/resources?app_id=dsdd|/groups/2/resources|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836017"],"X-Opal-Signature":["8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836017|8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6 http://localhost:8080/users?app_id=dsdd|/users|app_id=dsdd|GET|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836017"],"X-Opal-Signature":["8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836017|8d4b1c5bc918740859f51378fc4601642f653bb996a8debc1bf986b2cc632bf6 http://localhost:8080/groups/2/resources/2?app_id=dsdd|/groups/2/resources/2|app_id=dsdd|DELETE|{"Accept":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836024"],"X-Opal-Signature":["4581cba15df55453490bd42e26a73265dd442c87fcc9808bc9babef63268352a"]}|{}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836024|4581cba15df55453490bd42e26a73265dd442c87fcc9808bc9babef63268352a http://localhost:8080/groups/2/resources|/groups/2/resources||POST|{"Accept":["application/json"],"Content-Type":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836045"],"X-Opal-Signature":["a18c00f9d430e65b340a86007572511c834d0d29717650c166ccfc65e1d78647"]}|{"access_level_id":"1","app_id":"dsdd","resource_id":"3"}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836045|a18c00f9d430e65b340a86007572511c834d0d29717650c166ccfc65e1d78647 http://localhost:8080/groups/2/users|/groups/2/users||POST|{"Accept":["application/json"],"Content-Type":["application/json"],"User-Agent":[""],"X-Opal-Request-Timestamp":["1690836053"],"X-Opal-Signature":["39add5b34eaaed4776f7172bf0ec036d7e2ab6eb3a564671f079a6bf28730046"]}|{"app_id":"dsdd","user_id":"5"}|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt|1690836053|39add5b34eaaed4776f7172bf0ec036d7e2ab6eb3a564671f079a6bf28730046 ``` ### Example of X-Opal-Signatures To test `X-Opal-Signature`, you can use the following replay of some requests made to a connector with method, signature, hash and encryption key to build unit tests for your validation functions. ```Text CSV theme={null} method|path|signature|timestamp|hash|encryption_key GET|/resources/3|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/3/users|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/3/access_levels|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/2|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/2/users|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/2/access_levels|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/1|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/1/users|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/resources/1/access_levels|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt GET|/users|v0:1689194685:{}|1689194685|874d6f7810ae9611b816a1da9ed2ac6d38f6fef8b8b8770e5786727803146506|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt POST|/resources/1/users|v0:1689194631:{"app_id":"dsdd","user_id":"4"}|1689194631|2152942fccf3086d59de4cbbc708ee1861f87be76506560b755184d8ba90e8cc|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt POST|/resources/1/users|v0:1689194697:{"app_id":"dsdd","user_id":"4"}|1689194697|0f5dd435832cba2b26bcf98b775182deaacaf445334d403430969d42396a156d|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt POST|/resources/1/users|v0:1689194697:{"app_id":"dsdd","user_id":"5"}|1689194697|e4f572398bf72098a84f4f505a3a59b3429dcf64775df6032e3d348376ecc02a|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt DELETE|/resources/1/users/1|v0:1689194853:{}|1689194853|4700807ac9853cb813aaf6b6161f93634c6427bc4ea1625cd82fc42f3a8d2c5a|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt DELETE|/resources/1/users/4|v0:1689194853:{}|1689194853|4700807ac9853cb813aaf6b6161f93634c6427bc4ea1625cd82fc42f3a8d2c5a|f3XX78hHLiqEQ0F5SjHkEt7RxYeNKmKt ``` ## 3. Test a local connector implementation with Opal If you're developing a connector and you want to test it out with your Opal instance before deploying it to a public server (or your own network), you can use [ngrok](https://ngrok.com/) to make a local server available with a public hostname. With a connector running locally on port `:8080`, run `ngrok` with: ``` ngrok http 8080 ``` Now you can use the `ngrok` endpoint in your Connector app configuration in Opal. ## 4. Create a custom app In Opal, go to the **Inventory** page and select **+ App**. Select the **Custom App** tile, then the **Use custom app connector** option. Fill in the form with using the following settings to create the Opal custom app. | Field | Setting | Required | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | Identifier | Use any value you need, depending on how you use the `app_id` parameter from the [API spec](/docs/api-spec). | Yes | | Base URL | Your connector hostname and port, including protocol. For example, `https://customconnector.company.com:8090`. | Yes | | Connector Groups | Enable this option if you've implemented the `/groups` endpoints. If your connector does not require groups, leave it disabled. | No | | Nested Resources | [Enable](/docs/how-to-create-your-own-connector#nested-resources-in-connectors) this option if your connector requires nested resources. Ensure your connector implements the spec correctly, otherwise side effects may arise. | No | | Nested Groups | Enable this option if you use [nested groups](/docs/nested-groups) and have implemented the `/groups` endpoints. | No | | Event Ingestion | Enable this option if you've implemented the `/events` endpoint. | No | Select **Generate** to generate a signing secret, and use this code into your connector to verify that the `X-Opal-Signature` header is correct and authentic. See [Signatures](/docs/api-spec#signature) for more. Select **Create Custom App** to verify that the connector is properly set up. If successful, the connection will be created and you can now manage your access with Opal. # Import and configure resources Source: https://docs.opal.dev/docs/import-and-configure-resources-and-groups Once you have [connected apps and infrastructure integrations](/docs/set-up-connections) to Opal, you can import resources and groups to be managed. ## Automatic import From the app detail page in the **Inventory**, select the edit icon. Depending on the app type, you can either choose to automatically import all resources, import tagged resources only, or only import manually. Okta also supports auto-importing groups which contain imported resources: When you've made your selection, select **Save** and Opal will start syncing immediately. ## Manual import First, select **Import items** under the **...** menu for an app. Select the resources and groups to import, then [configure access requests](/docs/configure-reviewers#approval-workflows) for these resources. Finally, add them to Opal. These resources will be synced and are now available in Opal. ### Import resources from CSVs Within [**Push-only** custom apps](/docs/custom-integrations-overview), you can upload CSVs as resources. Go to the **Inventory**, then select **Import resource from CSV**. This can be especially useful for configuring [access reviews](/docs/access-campaigns) for systems without native Opal integrations. # Import user secondary emails Source: https://docs.opal.dev/docs/importing-user-secondary-email Learn how to consolidate access for users with multiple email addresses. If users in your organization can have multiple email addresses, it's often helpful to consolidate their access footprint under a single Opal account. You can do this by configuring Opal to import user secondary emails from your IDP. Opal then links users to all third-party user accounts registered under their secondary email. For example: * Suppose a user Alice has primary email `alice@foo.com` and secondary email `alice@bar.com`. * Suppose that Alice's Salesforce account is listed under her secondary email, `alice@bar.com]`. * When syncing with Salesforce, Opal will recognize that `alice@bar.com` belongs to Alice, and will represent this Salesforce account's access under Alice's Opal account. * When Alice is granted access to Salesforce via Opal, Opal will propagate access to Salesforce under `alice@bar.com` rather than under Alice's primary email. ## Import user secondary email(s) To set this up, add a custom attribute mapping to **Secondary email**. See the guide to [importing user attributes](/docs/add-additional-idphr-providers#import-attributes) . If your users can have more than one secondary email, you can configure Opal to import multiple **Secondary email** attributes. ### Auto-merge users by secondary email Auto-merging relies on the secondary email(s) imported from your IDP to be accurate. Inaccurate secondary emails will result in unintended and potentially irreversible auto-merges. Even after you've set up user secondary email import, it's possible for a single user identity to have two Opal user accounts: one for their primary email, and one for their secondary email. This can happen if the secondary Opal account was created before a primary Opal account's secondary email was updated. When this happens, it's helpful to merge these two Opal user accounts. Go to your organization's IDP settings and change the setting for **Auto-merge Opal users by secondary email** to **Enabled**: When this setting is enabled, Opal users whose email matches the secondary email of another Opal user will be auto-merged during IDP sync. In an auto-merge of two Opal users, one Opal user is preserved and the other is deleted. All user data associated with the deleted Opal user is removed. The preserved user is updated as follows: * Primary and secondary emails are immediately updated to the correct values. * IDP user attributes will be imported in the following sync. * End-system access for both users will be imported and associated with the preserved user in the following sync. (Notably, no access changes are propagated to any end-systems in an auto-merge.) Opal decides which user to preserve by examining which user has logged in more recently. Only logins after Feb 14, 2023 (Opal Cloud) or after `v1.0.396` (Opal Self-Host) are considered. # Install Opal using Helm Source: https://docs.opal.dev/docs/install-opal-using-helm Normally, our distribution platform Replicated uses [KOTS](https://docs.replicated.com/intro-kots) to manage deployments and updates in self-hosted clusters. If you’d prefer to avoid using KOTS, you can alternatively manage deployments yourself using Helm. This will require more work to configure Opal correctly, but will allow greater control over the resources you deploy. ## Initial setup To start, make sure you finish the Infrastructure Setup sections in the [AWS Setup Guide](/docs/self-host-opal-aws-guide#1-infrastructure-setup) or [GKE Setup Guide](/docs/self-host-opal-gke-guide#1-infrastructure-setup). Next, you’ll need to access Replicated's customer-facing download portal. This lists the available versions of Opal, and has instructions and credentials for Helm installs. Opal support will provide you with access to this download portal. Log in to the download portal and select the **Existing cluster with Helm** installation option. It'll look like this: download-portal.png From here, you can either * [Pull images directly](/docs/install-opal-using-helm#pull-images-from-opals-registry) from Opal’s registry OR * [Use a private Docker registry](/docs/install-opal-using-helm#use-a-private-docker-registry) We recommend using Opal’s registry unless you have specific security concerns that require you to use a private registry. ### Pull images from Opal’s Registry 1. Copy the license ID from step 1 in the download portal: ```shell shell theme={null} export LICENSE_ID= ``` 2. Log in to Replicated’s Helm registry, using the command in step 4 of the download portal: ```shell shell theme={null} # check the instructions in the download portal for the email to use here helm registry login registry.replicated.com --username --password $LICENSE_ID ``` 3. Continue with the following [Configure and Install via Helm](/docs/install-opal-using-helm#configure-and-install-via-helm) steps. ### Use a private Docker registry 1. Create the following repositories in your private registry: ```bash theme={null} fluent-bit ld-relay opal-migrate opal-web_backend redis redis-sentinel ``` 2. Ensure your cluster is setup with default credentials to pull images from your private registry. 3. Proceed with *all* of the steps from the Replicated download portal. When you reach steps 6 and 7 to download and edit `values.yaml`, continue with the following [Configure and Install via Helm](/docs/install-opal-using-helm#configure-and-install-via-helm) steps. # Configure and Install via Helm Refer to the following annotated `values.yaml` file when you’re ready to configure your values for the Opal chart. Take note of the comments, and fill in anything with ``. ```yaml yaml theme={null} env: # These are required for your environment to function correctly, and for us to push feature flags to you environment: on_prem onPremCustomerName: # Leave these values as-is authIssuer: https://auth.opal.dev/ authProvider: AUTH0 authAudience: https://opal.dev redisHost: opal-web-redis-master redisPort: 6379 # This will forward logs from your instance of Opal to us, making it easier for us to debug issues. # Set this to "false" if you'd like to disable log forwarding. # Either way, ensure it's a string, not a raw boolean enableRemoteLogging: "true" # Fill these in appropriately for the db instance you set up postgresUser: postgres postgresPassword: postgresDb: opal postgresHost: postgresSslMode: "require" postgresPort: 5432 # Generate secret values for these, and save them somewhere secure. authTokenEncryptionKey: csrfAuthenticationKey: databaseEncryptionKey: opalApiEncryptionKey: secureCookieHashkey: # You may alternatively point to an existing secret for these, like so: # authTokenEncryptionKey: # secretKeyRef: # name: "mySecretName" # key: "authTokenEncryptionKey" # A DNS name you own that you will use to access Opal. hostname: # Optional. Set this to have pods use an existing service account in your cluster. Otherwise, omit this. serviceAccount: # This configures an SMTP provider for your instance of Opal to use for sending email notifications # If set to false, Opal uses its default external service for sending emails smtpEnable: true smtpServer: smtpPort: # You may also enable authentication for your customer SMTP server by setting this to true # If you do this, set the username + password below, and make sure smtpPort is set to 465 smtpAuthEnable: false smtpUsername: smtpPassword: # Set smtpEncryptionEnabled to true to enable SMTP encryption. serverPort 465 uses SMTPS and serverPort uses STARTTLS smtpEncryptionEnabled: false # Optional. Enables async data exports backed by an S3 bucket. Omit this block entirely to leave the feature off. # See https://docs.opal.dev/docs/configure-async-exports-storage for how to provision the bucket and IAM user. exportStorage: bucketName: region: accessKey: secretKey: image: # Leave these two as-is to use Opal's image registry directly repository: proxy.replicated.com/proxy/opal-onprem/179751979675.dkr.ecr.us-east-2.amazonaws.com useReplicatedPullSecret: true # If using a private image registry, use the following instead: # repository: # useReplicatedPullSecret: false # Set these `registry` values to the same as `image.repository` above redis: image: registry: proxy.replicated.com/proxy/opal-onprem/179751979675.dkr.ecr.us-east-2.amazonaws.com # Omit pullSecrets on each of these if using a private registry pullSecrets: - replicated-pull-secret redis-sentinel-event-streaming: image: registry: proxy.replicated.com/proxy/opal-onprem/179751979675.dkr.ecr.us-east-2.amazonaws.com pullSecrets: - replicated-pull-secret sentinel: image: registry: proxy.replicated.com/proxy/opal-onprem/179751979675.dkr.ecr.us-east-2.amazonaws.com pullSecrets: - replicated-pull-secret # Setting `enabled` to true will deploy an Ingress resource you can use to route traffic to Opal # If you disable this, you will need to create your own ingress that routes traffic to the `opal-web` service ingress: enabled: true # Always leave `override=true` override: true overrideAnnotations: # Here, you should fill in annotations that will link your ingress to the load balancer you created in your cloud provider # For AWS, this will look something like: # kubernetes.io/ingress.class: alb # alb.ingress.kubernetes.io/scheme: internet-facing # alb.ingress.kubernetes.io/certificate-arn: # For GCP, this will look something like # ingress.gcp.kubernetes.io/pre-shared-cert: opal-replicated # kubernetes.io/ingress.global-static-ip-name: opal-replicated # If you need to turn off log-forwarding, set `enabled` to false here, and omit the other fields. log-forwarder: enabled: true customer: # Optional. This runs a LaunchDarkly Relay Proxy alongside Opal so feature flag evaluations continue working during a LaunchDarkly outage. ld-relay-proxy-mode: enabled: true # Optional. Adds Stakater Reloader annotations to Opal's Deployments so they automatically # roll when a referenced Secret changes, picking up rotated credentials without a manual restart. # Requires the Reloader controller to be installed in your cluster - see # "Install Stakater Reloader (optional)" below. Leave this false or omit it if you don't use Reloader. reloader: enabled: true ``` Once you’ve configured your values.yaml, run the `helm install` command from the final step in your download portal: Screenshot 2025-03-26 at 10.54.41 AM.png ## Install Stakater Reloader (optional) If you set `reloader.enabled: true` above, you'll also need the [Stakater Reloader](https://github.com/stakater/Reloader) controller running in your cluster. Reloader watches for changes to referenced Secrets and automatically triggers a rolling restart of the Deployments that use them. Opal's Helm chart already restarts pods when you change a value through `helm upgrade`, but it can't see changes made outside of Helm. Without Reloader, pods would keep running with stale, cached credentials until something else restarted them. With `reloader.enabled: true`, Opal's Deployments carry an annotation that tells Reloader to roll them as soon as the backing Secret's contents change, so credential rotations take effect without manual intervention. To install Reloader with Helm: ```shell shell theme={null} helm repo add stakater https://stakater.github.io/stakater-charts helm repo update helm install reloader stakater/reloader -n --set reloader.watchGlobally=false ``` We recommend setting `reloader.watchGlobally=false` so Reloader only watches the namespace it's installed into, rather than watching Secrets across your entire cluster. # Update Helm Installations For subsequent updates to your Opal cluster, log back in to your download portal and select the **Manual Updates** tab at the top. Set the current and update versions from the dropdowns: Screenshot 2025-03-26 at 10.56.31 AM.png If you’re using a private registry, you’ll need to re-run the commands to pull, retag, and push images to your private registry. We recommend you run these via a script or other automation. Finally, run the `helm upgrade` command from the final step in your download portal: Screenshot 2025-03-26 at 10.57.26 AM.png # Migrate from a KOTS-Based Installation If you previously installed Opal using KOTS and want to manage your deployment with Helm, you can perform a one-way migration. This will not cause any downtime for your cluster, but **will not** be reversible. First, follow the [Initial Setup](/docs/install-opal-using-helm#initial-setup) instructions above. Once you reach [Configure and Install via Helm](/docs/install-opal-using-helm#configure-and-install-via-helm), skip that section and follow the steps below instead. 1. Pull your cluster’s current Helm values into a local file: ```shell shell theme={null} helm get values opal-web -n -o yaml > values-from-kots.yaml ``` 2. Scale down the KOTS pods: ```shell shell theme={null} kubectl -n opal-onprem scale deploy kotsadm --replicas=0 kubectl -n opal-onprem scale sts kotsadm-minio --replicas=0 kubectl -n opal-onprem scale sts kotsadm-rqlite --replicas=0 ``` 3. Use `helm list` to check the currently installed version of opal: ```bash bash theme={null} $ helm list -n NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION opal-web opal-onprem 1 - deployed opal-web-1.944.0-sha-8d4530d 1.944.0-sha-8d4530d ``` Copy the value under `APP VERSION` for the step below 4. Run a Helm upgrade to take over the chart’s resources: ```shell shell theme={null} helm upgrade opal-web oci://registry.replicated.com/opal-onprem/stable/opal-web -n --version --values values-from-kots.yaml --take-ownership ``` Once this is complete, you can update your Helm installation in the future by following the instructions in [Update Helm Installations](/docs/install-opal-using-helm#update-helm-installations) above. You can also finish cleaning up the kubernetes resources created by KOTS by running the following: ```shell shell theme={null} kubectl -n delete deployment kotsadm; kubectl -n delete statefulset kotsadm-minio; kubectl -n delete statefulset kotsadm-rqlite; kubectl -n delete service kotsadm; kubectl -n delete service kotsadm-minio; kubectl -n delete service kotsadm-rqlite; kubectl -n delete service kotsadm-rqlite-headless; kubectl -n delete persistentvolumeclaim kotsadm-minio-kotsadm-minio-0; kubectl -n delete persistentvolumeclaim kotsadm-rqlite-kotsadm-rqlite-0; kubectl -n delete role kotsadm-role; kubectl delete clusterrolebinding kotsadm-rolebinding; kubectl delete clusterrole kotsadm-role; kubectl -n delete serviceaccount kotsadm; ``` *** # Opal's server IP ranges Source: https://docs.opal.dev/docs/ip-ranges If you need to allow Opal's servers to access your systems, add the following IP ranges to your allowlist. All outgoing requests from Opal originate from these addresses. ### IPv4 - app.opal.dev * 13.59.186.234/32 * 18.217.60.6/32 * 3.23.223.154/32 ### IPv4 - sandbox.opal.dev * 3.129.93.61/32 * 3.13.190.183/32 * 18.221.188.60/32 *** # Kubernetes Components and Health Monitoring Source: https://docs.opal.dev/docs/kubernetes-components-and-health-monitoring-guide Learn about various kubernetes pods and jobs, and health monitoring recommendations on self-hosted Opal. This guide provides high-level recommendations for self-hosted Opal customers on the various Kubernetes pods and jobs, their schedules, and recommended health monitoring practices. ## Overview Opal consists of several types of Kubernetes workloads: * **Deployments**: Long-running pods that handle API requests and background task processing * **CronJobs**: Scheduled jobs that run periodically to sync data, clean up resources, and process background tasks * **Jobs**: One-time jobs that run during upgrades ## Long-Running Deployments These are long-running pods that run continuously. We recommend monitoring pods to be in a “Running” state and watching for unexpected restarts. Refer below for specific deployments and recommendations. ### 1. Web Backend `opal-web` **Purpose**: Main API server handling HTTP requests from the frontend and external integrations. **Health Monitoring**: * [Ensure](https://kubernetes.io/docs/tutorials/kubernetes-basics/explore/explore-intro/#check-application-configuration) pods are running and ready (not in [CrashLoopBackOff, Error, or Pending](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-restarts) states) * Monitor pod restart count **Recommended Alerts**: * Pods not running or ready * Pod restart count increasing * Pods stuck in error or pending states ### 2. Event Consumers `opal-web-event-consumers` **Purpose:** Processes events from external systems and internal event streams. **Health Monitoring**: * Ensure pods are running and ready **Recommended Alerts:** * Pods not running or ready * Pod restart count increasing ### 3. Task Workers `opal-web-task-workers` **Purpose:** Processes background tasks from specialized queues. Different task worker types handle different operations such as general async work, event streaming, sync operations, and propagation tasks. **Health Monitoring:** * Ensure all task worker pods are running and ready * Monitor restart counts across all task worker deployments **Recommended Alerts:** * Any task worker pods not running or ready * Pod restart count increasing * Task worker pods stuck in error or pending states Some task workers have longer termination grace periods to allow long-running tasks to complete. ## Scheduled CronJobs These are automated jobs that run periodically on a fixed schedule to sync data and clean up resources. Generally, we recommend monitoring for full sync completions within its expected timeframe (typically 2x the schedule interval), and setting up alerts for job failures. Refer below for specific CronJobs. ### 1. Sync Jobs **Purpose:** Performs sync of user and resource data from connected systems (IDPs, cloud providers, HR systems). **Schedules:** * Regular sync: Every 4 hours `(0 */4 * * *)` * Daily sync: Daily at 9:30 AM UTC `(30 9 * * *)` * High-frequency sync: Every 5 minutes `(* /5 * * * *)` **Starting Deadline:** 120-180 seconds **Health Monitoring:** * [Ensure](https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/) regular sync completes successfully within 8 hours (2x schedule interval) * Ensure daily sync completes successfully within 48 hours (2x schedule interval) * Ensure high-frequency sync completes successfully within 20 minutes (4x schedule interval) **Recommended Alerts:** * No successful full sync completion in the past 8 hours * No successful daily sync completion in the past 48 hours * No successful high-frequency sync completion in the past 10 minutes * Sync job failure (exit code != 0) ### 2. Event Streaming Jobs **Purpose:** Manages event stream operations including publishing events to external systems, requeuing failed messages, deactivating unused streams, and cleaning up old messages. **Schedules:** * Event stream producer: Every 1 minute `(*/1 * * * *)` * Event stream re-queuer: Every 15 minutes `(*/15 * * * *)` * Event stream deactivator: Every 30 minutes `(*/30 * * * *)` * Event stream messages cleanup: Daily at 12:00 PM UTC `(0 12 * * *)` * Event stream notifier: Daily at 1:00 PM UTC `(0 13 * * *)` **Starting Deadline:** 120 seconds **Health Monitoring:** * Ensure event stream producer completes successfully within 2 minutes (2x schedule interval) * Ensure event stream re-queuer completes successfully within 30 minutes (2x schedule interval) * Ensure event stream deactivator completes successfully within 60 minutes (2x schedule interval) * Ensure event stream cleanup and notifier complete successfully within 48 hours (2x schedule interval) **Recommended Alerts:** * Event stream producer no successful completion in the past 2 minutes * Event stream re-queuer no successful completion in the past 30 minutes * Event stream deactivator no successful completion in the past 60 minutes * Event stream cleanup/notifier no successful completion in the past 48 hours * Event streaming job failure (exit code != 0) ### 3. Recommendations `recommendations-subscores` **Purpose:** Calculates and updates recommendation subscores for resources and groups to support access recommendations and risk analysis. **Schedule:** Every 5 minutes `(*/5 * * * *)` **Starting Deadline:** 120 seconds **Health Monitoring:** * Ensure job completes successfully within 10 minutes (2x schedule interval) **Recommended Alerts:** * No successful completion in the past 10 minutes * Job failure (exit code != 0) ### 4. Metrics Collection `metrics-collector` **Purpose:** Collects and aggregates metrics for reporting and analytics. **Schedule:** Daily at 6:30 AM UTC `(30 6 * * *)` **Starting Deadline:** 120 seconds **Health Monitoring:** * Ensure job completes successfully within 48 hours (2x schedule interval) **Recommended Alerts:** * No successful completion in the past 48 hours * Job failure (exit code != 0) ### 5. Scheduled Tasks Cleanup `scheduled-tasks-cleanup` **Purpose:** Cleans up old completed scheduled tasks from the database. **Schedule:** Every 5 minutes `(*/5 * * * *)` **Starting Deadline:** 120 seconds **Health Monitoring:** * Ensure job completes successfully within 48 hours (2x schedule interval) **Recommended Alerts:** * No successful completion in the past 48 hours * Job failure (exit code != 0) ## One-Time Jobs These jobs execute once to perform critical set up tasks. You should monitor that these jobs complete without errors during the upgrade window. ### Oneoff `oneoff` **Purpose:** Runs one-time database migrations and setup tasks during Helm upgrades. **Trigger:** Runs automatically as a post-upgrade [Helm hook](https://helm.sh/docs/topics/charts_hooks/)\*\* **Health Monitoring:** * Monitor job completion status * Check for job failures during upgrades **Recommended Alerts:** * Job failure during upgrades (exit code != 0) * Job running longer than expected ## General Health Monitoring Recommendations ### For All Deployments 1. **[Pod Status](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase):** Monitor for pods in `CrashLoopBackOff`, `Error`, or`Pending`states 2. **[Resource Usage](https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/#measuring-resource-usage):** CPU usage > 80% of request, Memory usage > 80% of limit 3. **Restart Count:** Alert if a pod restarts more than 3 times in an hour 4. **Availability:** Ensure pods are running as expected ### For All CronJobs 1. **Execution Status:** Ensure jobs have completed successfully within 2x-4x their schedule interval 2. **Failure Monitoring:** Alert on job failures (exit code != 0) 3. **Concurrency:** All CronJobs use `Forbid` concurrency policy - ensure previous jobs complete before new ones start ### Database Migrations Monitor for database migration failures or delays during pod startup and upgrades, as these may indicate database performance issues. ### Common Issues to Watch For 1. **Database Connection Issues:** All components depend on PostgreSQL. Monitor database connectivity. 2. **Redis Connection Issues:** Task workers and event consumers depend on Redis. Monitor Redis connectivity. 3. **Resource Constraints:** High memory/CPU usage may cause pods to be evicted or OOMKilled. 4. **Network Issues:** Pods need network access to external systems (IDPs, cloud providers) for syncing. ## Monitoring Best Practices 1. **Set up alerts** for all critical components (web backend, sync jobs) 2. **Monitor logs** for error patterns and exceptions 3. **[Track metrics](https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/)** for processing times, and error rates 4. **Set up dashboards** for: * Pod health and resource usage * CronJob execution status and duration * Application metrics (request rates, error rates, latency) 5. **[Use Kubernetes events](https://kubernetes.io/docs/tasks/debug/debug-cluster/#looking-at-events)** to monitor for scheduling issues, pod evictions, etc. ## Additional Notes * Every pod, including all CronJobs, include database migrations as init containers * All components use the same Docker image (`opal-web_backend`) with different command arguments * Health endpoints (`/api/health` and `/api/readiness`) are configured on all deployments via liveness and readiness probes # Risk Center Source: https://docs.opal.dev/docs/least-privilege-posture-management Use the Risk Center to gain insights on your least privilege posture. Opal automatically analyzes your access grants to detect vulnerabilities in your environment, and surfaces these in the **Risk Center**. The **Risk Center** includes insights on your current security posture, such as: * Trends in vulnerabilities, including unused, permanent, outside, or irregular access * Suggested remediation actions to reduce risk * A comprehensive view of access grants * Detailed views of overprovisioned Okta group rules ### Assign resource sensitivity For more precise recommendations, be sure your assets—groups and resources—have appropriate **Sensitivity** levels. Sensitivity is used, along with access metadata, to determine suggested remediations. Resources are assigned an inferred sensitivity based on their type, but you can manually override a resource's sensitivity. Find the resource in the **Inventory** and select the edit icon. You can also edit risk sensitivities in bulk from the **Inventory** by selecting multiple resources or groups in an app. ### Vulnerability types See an overview of trends in vulnerabilities in the charts at the top of the Risk Center, and filter by vulnerability in the asset grants table. #### Permanent access Access duration, used to calculate permanent access and unused access, is imported from third-party IDPs and apps, and is updated when access is granted through Opal. #### Outside access Outside access is any access grant provisioned outside Opal's approval flow. Note that this refers to provisioned access, not provisioned users—users imported from your IDP are not flagged for outside access. #### Irregular access Irregular access is determined by calculating similarity scores for users, based on user attributes, and similarity scores for resources, based on resource types. Opal flags irregular access when access grants are dissimilar to existing access grants. #### Unused access Access duration, used to calculate permanent access and unused access, is imported from third-party IDPs and apps, and is updated when access is granted through Opal. #### Overprovisioned rules Okta group rules are automatically analyzed and flagged for overprovisioning. Select **View rule insights** on an Okta group rule in the access grants table for a [detailed view](#view-rule-insights) on the group rule. The following agent risk types are part of the new Risk Center experience, currently in limited beta. Contact Opal support to enable it for your organization. #### No owner Flags an agent, tracked in your [Agent Inventory](/docs/agent-inventory), whose owning group has no active human member accountable for it. #### Exceeds owner access Flags an agent that can access resources its human owner cannot—so the owner can't vouch for everything the agent reaches. #### No plausible ownership Flags an agent whose access has no overlap at all with its owner's access, making it implausible the owner can vouch for what the agent does. #### No purpose Flags an agent with no stated purpose, so there's nothing to judge its access against. ### Remediate vulnerabilities When you select suggested actions, filters are automatically applied to the access grant table for the given asset or vulnerability type. From the table, you can apply the suggested action, revoke access, and view rule insights for Okta group rules. For agent risks, suggested actions also include assigning an owner or setting a purpose. It's often preferable to convert overprovisioned users to JIT, rather than revoke access, as it is a less jarring experience for end users who might be used to having longstanding access. Dismiss a suggestion to mark the identified vulnerabilities as safe. This prevents the suggestion from appearing for another 6 months. #### Remediate in bulk Select the checkbox on the **Access grants** table to remediate rows in bulk, then select the **Apply Suggested Remediations** button. Rows highlighted in yellow—in this example, all users—are marked for access calibration, with the **Expires** column detailing the change. #### View rule insights If you've [connected to Okta](/integrations/okta-idphris-integration), Opal automatically imports and analyzes Okta group rules to proactively identify overprovisioned groups. In the **Vulnerabilities** filter, select **Overprovisioned rule** to view overprovisioned Okta group rules. Selecting **View Rule Insights** on a rule brings you to the **Rule Insights** page. There, you can see the rule conditions, the groups governed by your Okta rule, and the resulting users with unused access. Select **Show Remote Rule** to see the rule, which you may want to restructure in Okta. Filter by the group using the left panel. Select the **Key Insights** filters to see the overprovisioned users, grouped by user attribute. ### Custom monitors Custom monitors are part of the new Risk Center experience, currently in limited beta. Contact Opal support to enable it for your organization. If the built-in vulnerability types don't cover a risk pattern you care about, you can define your own using a custom monitor. 1. From the Risk Center, select **Create risk**. 2. Name the monitor and set its **Severity**—High, Medium, or Low. 3. Build a query using the same query builder as [OpalQuery](/docs/opal-query) to define which entities the monitor should flag. 4. Preview the entities currently matching your query. 5. Save the monitor. Custom monitors appear alongside built-in vulnerability types, showing an active count and a trend of matches over time. You can dismiss a custom monitor to silence it organization-wide, and restore it later. ### Configure Risk Center ticket propagation The Risk Center ticketing provider setting is part of the new Risk Center experience, currently in limited beta. Contact Opal support to enable it for your organization. Opal can automatically create a ticket in your ticketing provider whenever a risk is assigned to a reviewer, so remediation work is tracked outside Opal. This is separate from [ticket propagation for access requests](/docs/ticket-propagation)—Risk Center tickets track vulnerability remediation, not access grants. To integrate with your ticketing provider, you must first connect it as a [Productivity Integration](/docs/connect-your-productivity-tools). To enable Risk Center ticket propagation: 1. Go to **Settings** > **Advanced**. 2. Turn on **Risk Center ticketing provider**. 3. Choose your ticketing provider and the project, team, workspace, or database to file tickets in. When you assign a risk to a reviewer, Opal creates one ticket for that risk, assigned to the reviewer, with a link back to the Risk Center. # Linked groups Source: https://docs.opal.dev/docs/linked-groups Learn how to link identical groups in Opal and set one as the source of truth. Groups pulled from different end systems might have similar names and identical members. In Opal, you can link these groups, setting one as the source of truth, to simplify the request experience for end users. When users request the other linked group, Opal redirects end users to the source of truth group. In the API, linked groups are available using the [/group-bindings](/reference/getgroupbindings) endpoints. ## Link groups To get started, admins can go to **Configuration > Linked Groups**. To make finding identical groups easier, Opal provides a list of suggestions based on group membership and naming. To confirm a suggestion is in fact an identical group, select **Compare** to identify the differences in naming and membership. If the groups are unrelated, select the **X**button on the rightmost section of the given row. If the groups are similar enough to be considered identical, select a source of truth. The source of truth is requestable by end users in Opal, and requests to the other groups(s) are redirected to the source of truth. Select **Link Groups** to set up this relationship. To unlink groups, select the red **X** button on the list of linked groups. Select the grey pencil button to edit the relationship. A relationship can be altered by changing the source of truth for the link or adding new groups to the link. If you already know which group you want to link, you can link straight from the group's detail page by clicking on the ... menu and selecting "Link to Group" ## End user experience If a user's access is removed from any group in a linked group, source of truth or otherwise, access to both groups is revoked. After you link groups, an end user requesting a non source of truth group sees the following modal: Requesting the source of truth redirects users to the correct resource, preventing confusion and clutter. # Manage break-glass access Source: https://docs.opal.dev/docs/manage-breakglass-access Learn how to set up groups and users to support break-glass access paths. To give end users temporary access to sensitive resources, you can use either break-glass users, or configure [nested groups](/docs/nested-groups). ## Break-glass users Admins and group owners can add **break-glass users** to groups by editing the group, then select **Break-glass users** in the sidebar. These users can give themselves temporary, 12-hour access to the group using an option to **Break Glass** on the group in the catalog. You can only set break-glass users on groups, not other resources. ## Nested groups and break-glass access You can alternatively achieve break-glass functionality using nested groups and request configurations. To do so: 1. Let the group you want to expose access to be **Target-group** and dedicate another group as **Breakglass-target-group**. From the **Inventory** under **Breakglass-target-group**, select **+Group** in the **Assets** tab and add **Target-group**. Users in **Breakglass-target-group** now have access to **Target-group**. 2. Set a resource configuration for **Breakglass-target-group** to be requestable and auto-approved for a group determined from on-call schedules, or however you need to populate the break-glass users. 3. You can set the **Target-group** request configuration independently for everyday access to the resource. This option gives you a separate break-glass access path, while letting you retain existing request configurations. # Overview Source: https://docs.opal.dev/docs/manage-groups-in-opal Learn about the different features Opal provides to manage groups. Use the following features to manage and scale groups within Opal. ## Nested groups In Opal, you can add a group to another group and automatically sync memberships between the groups. This allows you to automatically grant access to resources upon requests and approvals to other resources, and to establish relationships between groups in different end systems, Okta Push Groups, or SAML/OIDC groups. See the [nested groups guide](/docs/nested-groups) to learn more. ## Add resources to groups You can add any resource to a group from the **Resources** tab from the group detail page in the **Inventory**. This can be useful to bundle resources, especially in tandem with nested groups. ## Group aliasing Group aliasing refers to the ability to add groups as customizable roles to apps/resources in Opal. Add groups as resources if you want groups to be requestable by end users, but don’t want to give users direct access to the resource. This can be especially useful for SCIMPush Groups. You can also hide and rename groups in Opal to clarify the request experience for end users. See the [group aliasing guide](/docs/okta-group-aliasing) to learn more. ## Group leaders Users with the **Group leader** role can: * Request access to a resource on behalf of a group * Add and remove users to a group * Remove access to resources within a group Configure the **Group leader** role from the resource detail page. See the [group leaders guide](/docs/group-projects) to learn more. ## Linked groups Use linked groups in Opal when you have two groups with identical or near-identical sets of members and want to simplify access requests for end users. You set one group as the “source of truth,” and all access requests to the linked group are routed to the source of truth group. Linked groups do not affect group memberships, nor do they propagate access. See the [guide to linked groups](/docs/linked-groups) to learn more. # Manage Access Campaign Source: https://docs.opal.dev/docs/managing-access-campaigns Learn how to manage your Access Campaign. This guide assumes you've already created an access campaign. Learn more with our [access campaign creation guide](/docs/access-campaigns). ## Monitor Reviewer Progress Use the **Overview** tab to monitor progress as your campaign runs. Track each reviewer's status and send custom reminders individually or in bulk—to reviewers and their managers by clicking the bell icon. These notifications should be sent through the method configured by an admin in your Opal environment. To learn more about notifications, see our [notifications guide](/docs/notifications). The Overview tab showing reviewer progress and reminder controls. See individual decisions and comments on reviews in the **Reviews** tab. ## Stop your campaign You can stop your campaign by clicking **Stop Campaign**. This will lock the campaign for end users, but allow Opal Admins to review the campaign. This is useful when your campaign is configured to revoke access at the end of the campaign. The review period allows Opal Admins or Auditors to audit reviewers’ decisions to ensure all approvals and revocations are valid before changes are applied to the connected systems. ### Overriding reviewers' decisions You may only override decisions if you set your access campaign to **Revoke on campaign end**. If your access campaign is set to **Revoke on decision**, all revocations would have propagated to the end system before you stop the access campaign. If you notice a decision had been made that is incorrect, you can override that decision as an admin. Click the decision column, select the correct decision and optionally provide a reason. Your new decision will be reflected in the report as an Admin override. When you’re ready for changes to propagate, click **End Campaign**. ### Automatically revoke access to unreviewed resources Upon selecting **Stop Campaign**, admins can choose to automatically revoke access to unreviewed resources. This will change all unreviewed decisions to “Admin Revoked”. These changes will not propagate until you end the access campaign. ### Generate a report Upon ending your campaign, a report will be automatically generated with a summary and metrics of the actions taken during the review. You may export it as a PDF, or export a CSV of the individual reviews and reviewer decisions in your review. # Manage reviews Source: https://docs.opal.dev/docs/managing-user-access-reviews-legacy Learn how to manage User Access Reviews. Access Reviews will soon be a legacy feature. Manage your Access Reviews with [Access Campaigns](/docs/access-campaigns), now in beta. This guide assumes you have already [created a User Access Review](/docs/access-reviews-legacy). ## View review metrics Users with **Opal Auditor** roles can view key metrics for access reviews: total items assigned for review, total completed reviews, the number of reviewers who have completed their reviews, and the number of days until the access review deadline. ## Manage multiple reviewers per user In Opal, it is possible to assign multiple reviewers to review the same item. By clicking on the down arrow next to each user, you can see the decision and notes left by each reviewer. A review item is only accepted if all reviewers approve access. If any reviewer revokes access, then the review item is revoked. ## Manage reviewer progress In the **Reviewers** page, you can see reviewer progress on items they've been assigned to review. ## Stopping your review **Opal Auditors** can select **Stop Access Review** in the **Overview** tab to stop the Access Review. ### Generate a PDF overview Stopping your access review automatically generates a PDF to summarize metrics and actions taken during the review. ### Automated revoke access to unreviewed resources Upon selecting **Stop Access Review**, admins can choose to automatically revoke access to unreviewed resources. To preview which resources you are going to action on, click the number (e.g. 1) in the popup. # Admin Auditing MCP Server Source: https://docs.opal.dev/docs/mcp/admin-auditing Let admins audit access with Opal using our Admin Auditing MCP server. Opal's admin auditing MCP provides a set of tools that enables admins to use AI agents to view Opal events and syncs to investigate access and audit changes. Use cases include: * Investigate historical access patterns and anomalous access * Audit changes in organizational structure or role assignments * Review sync errors and propagation status * Monitor access requests and approvals * Track user access reviews and compliance activities ## Installation **OAuth is the recommended way to connect.** Omit the `Authorization` header below and your MCP client will prompt you to sign in to Opal in your browser — no stored secret. Find your Client ID under **User → Settings → OAuth Grants** (`app.opal.dev/user/settings/oauth-grants`); if your client asks for a resource or audience value, read it from this server's protected-resource metadata at `https://app.opal.dev/.well-known/oauth-protected-resource/mcp/admin-auditing`. See [Authentication](/docs/mcp/overview#authentication) for details. The commands below use an API token instead; [create one](/reference/authentication#using-the-api-with-personal-access-tokens) first to use that method. Self-hosted Opal deployments can host MCP servers as of version v1.1135.0. If you run self-hosted, remember to replace [https://app.opal.dev](https://app.opal.dev) with your own domain. To install, run the following command in your shell: ``` claude mcp add --transport http opal-admin-auditing https://app.opal.dev/mcp/admin-auditing --header "Authorization: Bearer ${OPAL_API_TOKEN}" ``` Then authenticate with Opal. ``` claude /mcp ``` To learn more, see Claude Code [documentation](https://code.claude.com/docs/en/mcp). To install, add the following to your `~/.cursor/mcp.json` file: ``` { "mcpServers": { "opal-end-user": { "transport": "http", "url": "https://app.opal.dev/mcp/admin-auditing", "headers": { "Authorization": "Bearer ${env:OPAL_API_TOKEN}" } } } } ``` Define your `OPAL_API_TOKEN`, and make sure you open Cursor from the same shell. ``` export OPAL_API_TOKEN="secret" ``` ``` open -a Cursor ``` To learn more, see Cursor [documentation](https://cursor.com/docs/mcp). To install, add the following to your `~/.gemini/settings.json` ``` { "mcpServers": { "opal-end-user": { "httpUrl": "https://app.opal.dev/mcp/admin-auditing", "headers": { "Authorization": "Bearer ${OPAL_API_TOKEN}" } } } } ``` To learn more, see Gemini [documentation](https://geminicli.com/docs/tools/mcp-server/). MCP is an open protocol supported by many other clients, and your specific client documentation can advise you how to connect. Key inputs: * Transport `http` * Endpoint `https://app.opal.dev/mcp/admin-auditing` * Add the header `Authorization: Bearer ${OPAL_API_TOKEN}`. ## Available Tools | Tool | Description | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `opal_get_resource` | Retrieves a resource. | | `opal_get_resources` | Returns a list of resources for your organization. | | `opal_get_sync_errors` | Returns a list of recent sync errors that have occurred since the last successful sync. | | `opal_get_resource_visibility` | Gets the visibility of this resource. | | `opal_get_uar` | Retrieves a specific UAR. | | `opal_get_ua_rs` | Returns a list of `UAR` objects. | | `opal_get_on_call_schedules` | Returns a list of `OnCallSchedule` objects. | | `opal_get_message_channels` | Returns a list of `MessageChannel` objects. | | `opal_get_tags` | Returns a list of tags created by your organization. | | `opal_sessions` | Returns a list of `Session` objects. | | `opal_get_user_tags` | Returns all tags applied to the user. | | `opal_get_users` | Returns a list of users for your organization. | | `opal_get_resource_scoped_role_permissions` | Returns all the scoped role permissions that apply to the given resource. Only `OPAL_SCOPED_ROLE` resource type supports this field. | | `opal_get_resource_tags` | Returns all tags applied to the resource. | | `opal_get_resource_nhis` | Gets the list of non-human identities with access to this resource. | | `opal_get_resource_users` | Gets the list of users for this resource. | | `opal_get_nhis` | Returns a list of non-human identities for your organization. | | `opal_get_resource_reviewer_stages` | Gets the list of reviewer stages for a resource. | | `opal_get_resource_reviewers` | Gets the list of owner IDs of the reviewers for a resource. | | `opal_get_resource_message_channels` | Gets the list of audit message channels attached to a resource. | | `opal_get_requests` | Returns a list of requests for your organization that is visible by the admin. | | `opal_get_request` | Returns a request by ID. | | `opal_get_owner_users` | Gets the list of users for this owner, in escalation priority order if applicable. | | `opal_get_owners` | Returns a list of `Owner` objects. | | `opal_get_idp_group_mappings` | Returns the configured set of available `IdpGroupMapping` objects for an Okta app. | | `opal_get_group_users` | Gets the list of users for this group. | | `opal_get_group_visibility` | Gets the visibility of this group. | | `opal_events` | Returns a list of `Event` objects. | | `opal_get_groups` | Returns a list of groups for your organization. | | `opal_get_group_containing_groups` | Gets the list of groups that the group gives access to. | | `opal_get_group_on_call_schedules` | Gets the list of on call schedules attached to a group. | | `opal_get_group_resources` | Gets the list of resources that the group gives access to. | | `opal_get_group_message_channels` | Gets the list of audit and reviewer message channels attached to a group. | | `opal_get_bundle_groups` | Returns a list of `Group` objects in a given bundle. | | `opal_get_bundle_visibility` | Gets the visibility of the bundle. | | `opal_get_bundle_resources` | Returns a list of `Resource` objects in a given bundle. | | `opal_get_apps` | Returns a list of `App` objects. | | `opal_get_configuration_templates` | Returns a list of `ConfigurationTemplate` objects. | | `opal_get_bundles` | Returns a list of `Bundle` objects. | # Admin Provisioning MCP Server Source: https://docs.opal.dev/docs/mcp/admin-provisioning Let admins manage access with Opal using our Admin Provisioning MCP server. Opal's admin provisioning MCP provides a set of tools that enables admins to use AI agents to manage user access in Opal. They can view user permissions and add or remove access to resources and groups. Use cases include: * Generate an overview of a user's access across resources and groups * Add or remove users from resources and groups * Update user access levels and durations * Manage group memberships ## Installation **OAuth is the recommended way to connect.** Omit the `Authorization` header below and your MCP client will prompt you to sign in to Opal in your browser — no stored secret. Find your Client ID under **User → Settings → OAuth Grants** (`app.opal.dev/user/settings/oauth-grants`); if your client asks for a resource or audience value, read it from this server's protected-resource metadata at `https://app.opal.dev/.well-known/oauth-protected-resource/mcp/admin-user-provisioning`. See [Authentication](/docs/mcp/overview#authentication) for details. The commands below use an API token instead; [create one](/reference/authentication#using-the-api-with-personal-access-tokens) first to use that method. Self-hosted Opal deployments can host MCP servers as of version v1.1135.0. If you run self-hosted, remember to replace [https://app.opal.dev](https://app.opal.dev) with your own domain. To install, run the following command in your shell: ``` claude mcp add --transport http opal-admin-user-provisioning https://app.opal.dev/mcp/admin-user-provisioning --header "Authorization: Bearer ${OPAL_API_TOKEN}" ``` Then authenticate with Opal. ``` claude /mcp ``` To learn more, see Claude Code [documentation](https://code.claude.com/docs/en/mcp). To install, add the following to your `~/.cursor/mcp.json` file: ``` { "mcpServers": { "opal-end-user": { "transport": "http", "url": "https://app.opal.dev/mcp/admin-user-provisioning", "headers": { "Authorization": "Bearer ${env:OPAL_API_TOKEN}" } } } } ``` Define your `OPAL_API_TOKEN`, and make sure you open Cursor from the same shell. ``` export OPAL_API_TOKEN="secret" ``` ``` open -a Cursor ``` To learn more, see Cursor [documentation](https://cursor.com/docs/mcp). To install, add the following to your `~/.gemini/settings.json` ``` { "mcpServers": { "opal-end-user": { "httpUrl": "https://app.opal.dev/mcp/admin-user-provisioning", "headers": { "Authorization": "Bearer ${OPAL_API_TOKEN}" } } } } ``` To learn more, see Gemini [documentation](https://geminicli.com/docs/tools/mcp-server/). MCP is an open protocol supported by many other clients, and your specific client documentation can advise you how to connect. Key inputs: * Transport `http` * Endpoint `https://app.opal.dev/mcp/admin-user-provisioning` * Add the header `Authorization: Bearer ${OPAL_API_TOKEN}`. ## Available Tools | Tool | Description | | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | | `opal_user` | Retrieves detailed user information from Opal. This endpoint is designed to fetch user details by either user ID (UUID) or email address. The endpoint follows a strict precedence rule where `user_id` takes priority. | | | `opal_get_user_tags` | Returns all tags applied to the user. | | | `opal_get_users` | Returns a list of users for your organization. | | | `opal_update_resource_user` | Updates a user's access level or duration on this resource. | | | `opal_delete_resource_user` | Removes a user's direct access from this resource. | | | `opal_set_owner_users` | Sets the list of users for this owner. If escalation is enabled, the order of this list is the escalation priority order of the users. If the owner has a source group, adding or removing users from this list won't be possible. | | | `opal_get_owner_users` | Gets the list of users for this owner, in escalation priority order if applicable. | | | `opal_add_group_user` | Adds a user to this group. | | | `opal_update_group_user` | Updates a user's access level or duration in this group. | | | `opal_delete_group_user` | Removes a user's access from this group. | | | `opal_get_group_users` | Gets the list of users for this group. | | | `approve_request` | Approves a user's request. | | | `deny_request` | Denies a user's request. | | # End User MCP Server Source: https://docs.opal.dev/docs/mcp/end-user Let end users interact with Opal using our end user MCP server. Opal's End User MCP provides a set of tools that enables end users to leverage AI agents to request access to Opal resources, groups, and bundles . Use cases include: * Browse available resources, groups, and bundles * Create access requests for yourself or as part of a workflow * Check request status and view your current access * Use in combination with other MCP servers (e.g., request access to Retool, then use Retool's MCP server to query data) ## Installation **OAuth is the recommended way to connect.** Omit the `Authorization` header below and your MCP client will prompt you to sign in to Opal in your browser — no stored secret. Find your Client ID under **User → Settings → OAuth Grants** (`app.opal.dev/user/settings/oauth-grants`); if your client asks for a resource or audience value, read it from this server's protected-resource metadata at `https://app.opal.dev/.well-known/oauth-protected-resource/mcp/end-user`. See [Authentication](/docs/mcp/overview#authentication) for details. The commands below use an API token instead; [create one](/reference/authentication#using-the-api-with-personal-access-tokens) first to use that method. Self-hosted Opal deployments can host MCP servers as of version v1.1135.0. If you run self-hosted, remember to replace [https://app.opal.dev](https://app.opal.dev) with your own domain. To install, run the following command in your shell: ``` claude mcp add --transport http opal-end-user https://app.opal.dev/mcp/end-user --header "Authorization: Bearer ${OPAL_API_TOKEN}" ``` Then authenticate with Opal. ``` claude /mcp ``` To learn more, see Claude Code [documentation](https://code.claude.com/docs/en/mcp). To install, add the following to your `~/.cursor/mcp.json` file: ``` { "mcpServers": { "opal-end-user": { "transport": "http", "url": "https://app.opal.dev/mcp/end-user", "headers": { "Authorization": "Bearer ${env:OPAL_API_TOKEN}" } } } } ``` Define your `OPAL_API_TOKEN`, and make sure you open Cursor from the same shell. ``` export OPAL_API_TOKEN="secret" ``` ``` open -a Cursor ``` To learn more, see Cursor [documentation](https://cursor.com/docs/mcp). To install, add the following to your `~/.gemini/settings.json` ``` { "mcpServers": { "opal-end-user": { "httpUrl": "https://app.opal.dev/mcp/end-user", "headers": { "Authorization": "Bearer ${OPAL_API_TOKEN}" } } } } ``` To learn more, see Gemini [documentation](https://geminicli.com/docs/tools/mcp-server/). MCP is an open protocol supported by many other clients, and your specific client documentation can advise you how to connect. Key inputs: * Transport `http` * Endpoint `https://app.opal.dev/mcp/end-user` * Add the header `Authorization: Bearer ${OPAL_API_TOKEN}`. ## Available Tools | Tool | Description | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `opal_get_resources` | Returns a list of resources for your organization. | | `opal_user` | Retrieves detailed user information from Opal. This endpoint is designed to fetch user details by either user ID (UUID) or email address. | | `opal_get_users` | Returns a list of users for your organization. | | `opal_get_resource_nhis` | Gets the list of non-human identities with access to this resource. | | `opal_create_request` | Create an access request. | | `opal_get_resource_users` | Gets the list of users for this resource. | | `opal_get_requests` | Returns a list of requests for your organization that is visible by the admin. | | `opal_get_request` | Returns a request by ID. | | `opal_get_group_users` | Gets the list of users for this group. | | `opal_get_groups` | Returns a list of groups for your organization. | | `opal_get_group_containing_groups` | Gets the list of groups that the group gives access to. | | `opal_get_group_resources` | Gets the list of resources that the group gives access to. | | `opal_get_group` | Returns a `Group` object. | | `opal_get_bundle_groups` | Returns a list of `Group` objects in a given bundle. | | `opal_get_bundle_resources` | Returns a list of `Resource` objects in a given bundle. | | `opal_get_apps` | Returns a list of `App` objects. | | `opal_get_bundles` | Returns a list of `Bundle` objects. | | `opal_get_bundle` | Returns a `Bundle` object. | # Legacy MCP Source: https://docs.opal.dev/docs/mcp/gram-mcp-servers Learn how to install and use Opal's Gram-hosted MCP servers. As of May 26, 2026, Opal has moved to **self-hosted MCPs**. Any previously Gram-hosted MCPs are no longer maintained — please update your configurations accordingly. Opal's legacy MCP servers are generated using [Speakeasy Gram](https://www.speakeasy.com/product/gram), which transforms OpenAPI specifications into MCP servers optimized for AI interactions. ## Available MCP Servers ### Admin User Provisioning **Purpose:** Manage user access in Opal. View user permissions and add or remove access to resources and groups. **Use cases:** * Generate an overview of a user's access across resources and groups * Add or remove users from resources and groups * Update user access levels and durations * Manage group memberships **Installation:** [Admin User Provisioning MCP Server](https://app.getgram.ai/mcp/opal-user-provisioning/install) ### Admin Access Investigation and Auditing **Purpose:** View Opal events and syncs to investigate access and audit changes. **Use cases:** * Investigate historical access patterns and anomalous access * Audit changes in organizational structure or role assignments * Review sync errors and propagation status * Monitor access requests and approvals * Track user access reviews and compliance activities **Installation:** [Admin Access Investigation and Auditing MCP Server](https://app.getgram.ai/mcp/opal-auditing/install) ### End User Self-Service **Purpose:** Request access to Opal resources, groups, and bundles. **Use cases:** * Browse available resources, groups, and bundles * Create access requests for yourself or as part of a workflow * Check request status and view your current access * Use in combination with other MCP servers (e.g., request access to Retool, then use Retool's MCP server to query data) **Installation:** [End User Self-Service MCP Server](https://app.getgram.ai/mcp/opal-end-user/install) ### Legacy Server (Full API Access) For users who need access to the complete Opal API surface or prefer local-only execution, the general-purpose MCP server remains available. **Installation:** [opal-mcp on GitHub](https://github.com/opalsecurity/opal-mcp/) ## Requirements To set up any MCP server, you'll need: * Opal Admin permissions (for Admin servers) or a standard Opal user account (for End User Self-Service) * An Opal [API key](/reference/authentication#generating-an-api-token) * An MCP-compatible client such as [Claude Desktop](https://claude.ai/download), [Cursor](https://cursor.com/en/downloads), or [VS Code](https://code.visualstudio.com/) For servers that create or modify objects, set the API key scope to **Full access**. ## Installation Each MCP server has its own installation page with step-by-step instructions for different clients: 1. Visit the installation page for the server you want to use 2. Select your client (Cursor, Claude Desktop, VS Code, etc.) 3. Follow the installation instructions 4. Configure your Opal API key when prompted The installation pages provide client-specific configuration details and raw MCP configuration for clients not listed. ## Audit requests performed by MCP agents Requests and actions performed by MCP agents are logged the same as requests performed by users—you can filter the [events](/docs/event-types) table by API key to see requests made by the given agent. You may want to add context to your request reasons to indicate they're created by an MCP agent. For example, with Cursor, you could add a [rule](https://docs.cursor.com/en/context/rules) to include a message in all request reasons that the request was created by an MCP agent. # Model Context Protocol (MCP) Source: https://docs.opal.dev/docs/mcp/overview Let your AI Agents interact with Opal by using our MCP server. As of May 26, 2026, Opal has moved to **self-hosted MCPs**. Any previously Gram-hosted MCPs are no longer maintained — please update your configurations accordingly. Opal hosts our own specialized [Model Context Protocol (MCP) servers](https://modelcontextprotocol.io/introduction) that allow your AI agents to interact with Opal through Opal's REST API. Self-hosted Opal deployments can host MCP servers as of version v1.1135.0. With these MCP servers, AI assistants can help you manage access through natural language. Natural language queries include *"Show me all users in the Engineering group."*, *"Create an access request for the Production database."*, *"What access changes happened to our AWS resources this week?"* Opal provides three specialized MCP servers, each designed for specialized end user or admin use cases. Enable self service by requesting access through our MCP. Manage user access through our MCP. Investigate access and audit changes with our MCP. ## Authentication Opal MCP servers support two ways to authenticate. **OAuth is recommended** for interactive clients: you sign in through your browser and Opal issues a short-lived token scoped to your account, so no long-lived secret is stored in your client config. Use an **API token** for scripts and other non-interactive clients. ### OAuth OAuth support is in early beta and may change. If you hit issues, use an [API token](#api-token) instead. Most MCP clients discover and complete the OAuth flow automatically. Add the server URL **without** an `Authorization` header and trigger your client's sign-in step; your client will redirect you to Opal to approve access, then receive a scoped access token it refreshes for you. Opal uses the OAuth 2.1 authorization code flow with PKCE. Per-client commands are on each server's installation page. If your client asks you to configure OAuth manually, use these values: | Field | Value | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Client ID | Find it under **User → Settings → OAuth Grants** | | Resource / audience | Read the `resource` value from your server's protected-resource metadata at `/.well-known/oauth-protected-resource/` (e.g. `https://app.opal.dev/.well-known/oauth-protected-resource/mcp/end-user`) | | Server URL | The MCP endpoint you're connecting to, e.g. `https://app.opal.dev/mcp/end-user` | Opal uses a single pre-registered public client and does not support Dynamic Client Registration. Find your **Client ID**, and review or revoke the tokens issued to your account, in Opal under **User → Settings → OAuth Grants** (`app.opal.dev/user/settings/oauth-grants`). #### Connecting from a platform (Databricks, Glean, and similar) Some platforms — Databricks, Glean, and similar tools that broker connections to external services — let you register one of Opal's MCP servers as an external tool through their own OAuth connection UI, rather than through an MCP client's built-in OAuth discovery. Since these platforms have you configure OAuth manually instead of performing full MCP discovery, a few settings need to be set explicitly: 1. **Allowlist the platform's OAuth callback URL.** In Opal, go to **Settings → Authentication → MCP OAuth callback URLs** and add the platform's callback URL under **Custom callback URLs**. For Databricks, this is `https:///login/oauth/http.html`; check your platform's own documentation for its equivalent. 2. **Use OAuth User-to-Machine (per-user) authentication**, if your platform distinguishes between OAuth types. Opal's OAuth broker only supports the interactive authorization-code-with-PKCE flow — there's no machine-to-machine/client-credentials support. 3. **Set the credential exchange (or client authentication) method to send the client ID in the request body**, not in an `Authorization` header, if your platform asks. Opal's token endpoint reads `client_id` from the request body. 4. **Client Secret doesn't apply.** Opal's OAuth client is a public client with no secret (see the note above) — leave this field blank, or enter a placeholder if your platform requires a non-empty value. 5. **If your platform asks for a resource or audience value**, use the value from this server's protected-resource metadata (see the table above). Opal also accepts a request that omits this value entirely, so it's fine to leave it blank if your platform has no field for it. 6. **Register the MCP server as a tool in your platform.** Setting up the OAuth connection isn't enough on its own — most platforms require a separate step to register or enable the MCP server as a usable tool before agents can call it. 7. **If your platform asks you to choose a transport protocol, choose Streamable HTTP.** Some platforms, like Glean, let you pick between SSE and Streamable HTTP when registering the MCP server — Opal's MCP servers support Streamable HTTP; SSE is not supported. Register Opal as an external MCP server in Unity Catalog: 1. Go to **Catalog → Connections → Create connection**, and choose type **HTTP**. 2. Set the server URL to one of Opal's MCP endpoints, e.g. `https://app.opal.dev/mcp/end-user` (swap in `/mcp/admin-user-provisioning` or `/mcp/admin-auditing` for the other servers). 3. Choose authentication **OAuth U2M per-user**, with OAuth provider **Manual configuration**: * Authorization endpoint: `https://app.opal.dev/authorize` * Token endpoint: `https://app.opal.dev/token` * Client ID: your value from **User → Settings → OAuth Grants** * Client secret: not used — leave blank or use a placeholder * Credential exchange method: **body\_only** 4. Log in to complete the OAuth flow, then go to **AI Gateway → MCPs → Register MCP Server** and select this connection so its tools become available to agents. See Databricks' [Register an external MCP server](https://docs.databricks.com/aws/en/ai-gateway/register-mcp-service) documentation for more detail. The steps above — allowlisting the callback URL, per-user OAuth, sending the client ID in the request body, no client secret, then registering the tool — apply generally. Check your platform's own documentation for the equivalent of each step. ### API token [Create an API Token](/reference/authentication#using-the-api-with-personal-access-tokens) in your Opal environment and pass it to your client as an `Authorization: Bearer ${OPAL_API_TOKEN}` header, as shown in the installation steps for each server. # Metrics dashboard Source: https://docs.opal.dev/docs/metrics Learn about Opal's built-in analytics and metrics. Opal offers a metrics Dashboard to organization admins with graphs and data on request and access patterns, as well as the ability to filter the data. ## Metric types 1. **Request status spark lines** - See trends of number of requests **approved**, **denied**, and **canceled** over time. 2. **Average time to request approval** - See how quickly your organization's requests are getting approved. Includes manual, admin, and auto approvals. 3. **Average length of access granted** - See how much access time requests are granting. This does not include admins directly adding users to resources and groups. 4. **Resource/Group request volume** - See which resources and groups are being requested the most. 5. **Break glass usage** - See how often break glass access is being used, and for which groups. 6. **Closed requests** - See what methods are being used to close requests. Includes **auto approval**, **manual approval**, **admin override approval**, **denied**, and **canceled**. ## Time range and aggregates Change the time range of the data with the date picker in the top right. Select one of the preset ranges, or choose Custom to select a custom start and end dates. Choose from three duration options—1 day, 7 days, 30 days—to aggregate the data by. ## Filters Apply filters to scope down the data to specific apps, entities, or tags. # Nested groups Source: https://docs.opal.dev/docs/nested-groups Learn how to set up nested groups in Opal. In Opal, you can use nested groups (also called “group-group propagation”) to add a group to another group and automatically sync memberships between the groups. ## Use cases ### Connect role and resource groups Use nested groups to easily manage access to resources while maintaining existing user group structures. You can: * Define requestable groups containing resources; for example, a group called *Prod Resources*, composed of prod AWS and PagerDuty resources * Define non-requestable groups containing users; for example, a group called *Software Engineers* pulled from an Okta group rule * From the *Prod Resources* detail page under **Group Access**, add the *Software Engineers* group as a nested group, and set an access duration In this example, users in the *Software Engineers* group are then automatically granted access to resources within the *Prod Resources* group, and changes in group membership are automatically pulled from Okta. ### Connect disparate groups You can also use nested groups to connect disparate groups that require access to the same resources, e.g., a Google Group and Okta group, two Okta groups, etc. ## Configuration To set up a nested group: 1. Go to **Inventory** > **Apps** and find the group you’d like to serve as the containing group. The *containing group* contains the *member group* you’ll add. 2. Go to the **Group Access** tab on the containing group. 3. Select **Add Groups** and search for the *member group(s)* to add. 4. Optionally, set the access to be timebound. 5. Select **Add Groups** to save the group. Users in the *member group* are now automatically granted access to any resources or groups in the *containing group.* # Notifications Source: https://docs.opal.dev/docs/notifications Learn about the types of notifications sent by Opal. The following are notifications sent by Opal, based on certain events or at a specified frequency. Users can unsubscribe to email or Slack notifications from **Settings > Notification Preferences** in the lower left corner. | Notification type | Recipients | Email | Slack | Google Chat | Configuration | | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ----- | ----- | ----------- | ------------------------------------------------------------------------------- | | Access review assigned to reviewers | Reviewers | ✔️ | ✔️ | ✔️ | [Access review settings](/docs/access-campaigns#notifications) | | Incomplete access review reminders | Managers if enabled, reviewers | ✔️ | ✔️ | ✔️ | [Access review settings](/docs/access-campaigns#notifications) | | Access reviews require access to be revoked on end systems | Auditors | ✔️ | ✔️ | | | | Access revoked due to access reviews | Revoked users | ✔️ | ✔️ | | | | Access expired | Users with expired access | ✔️ | ✔️ | | | | Resources imported to Opal | Owners of connection/app containing resources | ✔️ | ✔️ | | | | User invited to Opal | Invited user | ✔️ | | | | | Access requested | Reviewers | ✔️ | ✔️ | ✔️ | | | Access requested on behalf of another user | Requestor, affected user, and reviewers | ✔️ | ✔️ | | | | User comments on access requests | Reviewers | ✔️ | ✔️ | | | | Access request approved or denied | Requestors | ✔️ | ✔️ | | | | Access request canceled | Reviewers | ✔️ | ✔️ | | | | Access is expiring soon | Users with expiring access, Group leaders | ✔️ | ✔️ | | **Configuration Settings** > **Advanced** > **Access expiration notifications** | | Access propagation failed or [owner groups are empty](/docs/configure-reviewers#empty-owner-notifications) | Admins and/or Owners | ✔️ | ✔️ | | **Configuration Settings** > **Advanced** > **Error notification setting** | | Manual ticket propagation successful | Requestors | | ✔️ | | | # OIDC Provider Setup for Opal Actions Source: https://docs.opal.dev/docs/oidc-provider-setup-for-opal-actions You can use OpenID Connect (OIDC) to authenticate users for actions that require additional authentication, configured at the resource level. This includes requesting access, approving an access request, and/or connecting to a session. ## OIDC Provider Setup Prerequisite: You must register a new OIDC Provider with your IDP. Use the callback URL `https://{YOUR_OPAL_BASE_URL}/callback/oidc`, substituting in your Opal base URL (e.g. `https://app.opal.dev/callback/oidc`) .For more information about obtaining these credentials, refer to your IdP's documentation: [Okta OIDC docs](https://help.okta.com/en-us/Content/Topics/Apps/Apps_App_Integration_Wizard_OIDC.htm), [Google OIDC docs](https://developers.google.com/identity/openid-connect/openid-connect) When registering your OIDC provider with Opal, you must have the following information available: **Client ID, Client Secret,** and **Issuer URL**. The OIDC provider must be configured to require MFA for every sign-in attempt. Password authentication is not required. ## Configure settings in Opal 1. In Opal, go to **Configuration > Settings** in the left sidebar. 2. Click **Authentication**, then **MFA settings for gated Opal Actions**. Click **Configure**. 3. Select **OIDC MFA** and fill in the Client ID, Client Secret, and Issuer URL from your IdP. # Group aliasing Source: https://docs.opal.dev/docs/okta-group-aliasing You can hide, rename, and add groups in Opal that you imported from your IDP, without affecting the group in your underlying IDP. This can be useful to clarify to end users which apps they should request to access, and helps simplify the Catalog experience. ## Hide groups in Opal You may want to hide groups that certain users should belong to, but which you don’t intend for other users to access—for example, an engineering group **Engineering Birthright** may not make sense for users outside of that organization. Hiding a group does not alter its display in your IDP—Okta, Azure, Workday, etc.—and hidden groups are still visible to admins and owners in the **Inventory**. To hide a group in Opal, go to the **Inventory** page and find the group. Select the eye icon in the far right column, then confirm the modal. ## Rename groups in Opal Renaming a group can help you clarify to end users the effects of requesting access. For example, you may have an Okta group named **Design Tooling**, which you don’t want to rename in Okta but only want to use in Opal for Figma access. Select **Edit Catalog Name** to rename the group—in this example, to *Figma Editor*—so users know what they’re requesting. The name in the **Inventory** then remains the same. ## Add groups as resources in Opal You may want to create groups in Opal which do not have access relationships in your IDP. For example, Okta [push groups](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-about-group-push.htm) may need to be requested by end users to associate the group and app, but the group should not have direct access to Okta apps. For these cases, you can add groups as resources from the app's **Inventory** page by selecting **Add Resources**. # Okta Multifactor Authentication Source: https://docs.opal.dev/docs/okta-multifactor-authentication ## Using Okta MFA for Opal logins For logins, you must do 3 things: 1. Setup Okta as your SAML provider. [For instructions, see here.](/docs/okta-saml-setup) 2. In Okta, configure your Opal SAML app to [require MFA for login](https://help.okta.com/en-us/content/topics/security/mfa/mfa-home.htm). 3. In Opal, ensure that the **Require Opal MFA for logins** setting is off. 1592 ## Using Okta MFA for Opal actions: requesting, approving, connecting \[Legacy] * Okta Verify TOTP * Okta Verify Push If you'd like to use WebAuthn (Yubikey, TouchID) in addition to Okta Verify, follow the instructions to use an OIDC Provider: [https://docs.opal.dev/docs/oidc-provider-setup-for-opal-actions](/docs/oidc-provider-setup-for-opal-actions) First, go to the resource(s) you want to require MFA for and click "Edit." Then, in the left pane, toggle on the desired setting: * **MFA to approve requests** requires reviewers to have completed an MFA in the past 5 minutes prior to approving a request. * **MFA to connect** (applies to select resource types) requires the user to have completed an MFA in the past 5 minutes prior to connecting to a resource. 884 Then, in your organization's settings, configure the following setting in the **Authentication** section: 960 ### Requirements for Okta Verify Push When a push notification is sent to the Okta Verify app, the location associated with the user agent is included to prevent phishing. Here is an example: Opal's [IP ranges](/docs/ip-ranges) must be added to the allowlist in your org's network security settings as a trusted proxy to forward the user agent's original IP address with the `X-Forwarded-For` HTTP header. Note: If running Opal self-hosted, please use the public IP ranges for your infrastructure. Navigate to the **Configuration** section in Okta: * Under **Security**, click **Networks**. * Edit the allowlist IP zone. * Add the IP ranges under **Trusted proxy IPs**, like so: 1718 *** # Okta SAML Setup Source: https://docs.opal.dev/docs/okta-saml-setup Learn how to configure Opal to authenticate users via Okta SAML SSO. You can set up Opal to allow or require users to login via Okta SAML SSO. ## Setup 1. Log in to Okta as an administrator, then from the left sidebar, select **Applications** > **Create App Integration** > **SAML 2.0** > **Next**. On the **General Settings** page, fill in the following fields, then click **Next**. * **App name:** Opal * **App logo:** [Download](https://files.readme.io/54e771f-logo-black.png) and use Opal's logo. 2. In a new tab, log in to the Opal dashboard, then go to **Configuration > Settings > Authentication > SAML SSO Settings > Setup**. You should see the following screen: 2262 3. Back in Okta, go to the next page. On the **Configure SAML** screen, fill in the following fields, then click **Next**. Leave all other fields in their default state. * **Single sign on URL:** Use the **ACS URL** from the Opal modal. * Leave **Use this for Recipient URL and Destination URL** checked. * **Audience URI**: Use **Entity ID** from the Opal modal. * **Attribute Statements:** * `given_name` > `user.firstName` * `family_name` > `user.lastName` * `email` > `user.email` 2070 4. On the next page in Okta, select **I'm an Okta customer adding an internal app**, then click **Finish**. 5. On the new Okta app page, click the **Assignments** tab and assign any users or groups who you want to grant access to Opal via Okta SAML SSO. Each email in Okta must match the email of the Opal account in order for the user's SAML login to succeed. 2048 6. Next, in the **Sign On** tab select **View SAML setup instructions**. 2076 7. Back in Opal, enter the following information, then click **Save**: * **Identity Provider SAML 2.0 SSO URL:** Use **Identity Provider Single Sign-On URL** from Okta. * **Identity Provider Public Certificate**: Download the **X.509 Certificate** from Okta and upload it to Opal. 2262 Your SAML SSO setup should now be complete. For more options on configuring your SAML connection, see [the SSO SAML guide](/docs/sso-with-saml). ## Test SAML login To test SAML login to Opal, log out of your Opal account and try to log in again. The next time you log in, you should see the following screen, which lets you choose to log in via SAML. 2408 # Okta SCIM: Provision Opal Users Source: https://docs.opal.dev/docs/okta-scim-integration For most use cases, you probably don't need to use Okta SCIM provisioning. Opal will automatically sync new users and profile updates from Okta. SCIM provisioning is useful if you cannot wait for the [hourly or a manual sync](/docs/sync-schedules-and-triggers) to retrieve new users. This guide provides the steps required to configure User Provisioning from Okta to Opal. The following provisioning features are supported: * **Push new users**. New users created through Okta will also be created in Opal. ## Configuration steps in Opal You must first connect [Okta as an IDP](/integrations/okta-idphris-integration) to use Okta SCIM provisioning. Next, generate an Opal API token with admin level privileges. As an admin, go to **Configuration > Settings > API Access Tokens**. Select the **+API Access Tokens** button. Generate a token with the **Full-access** role: Save the generated token. ## Configuration steps in Okta 1. In Okta, go to **Applications** and select the Opal application. Under **General**, ensure **Enable SCIM provisioning** is selected. 2. Go to the **Provisioning** tab, then **Integration** on the left sidebar. 3. In the **SCIM connector base URL** field, enter the base URL of your Opal instance. For example, the Base URL field for the Opal Cloud instance is [https://app.opal.dev/scim/v2](https://app.opal.dev/scim/v2). Be sure to append `/scim/v2` to the base domain name. 4. Enter a unique identifier field for users, e.g., `userName`. 5. Under **Supported provisioning actions**, enable your preferred features. 6. Choose **HTTP Header** in the **Authentication Mode** section. In the **Token** field, enter the API token generated from Opal from the previous step. 7. Click **Save**. 8. In the **To App** tab, make sure Create New Users is enabled ## Troubleshooting We do not support propagation of updates of the username/email of an Okta user to Opal. *** # Concepts Source: https://docs.opal.dev/docs/opal-101 Learn about fundamental concepts and objects in Opal. ## Resources A **Resource** is an object from a remote system that a user can request to access. For example, you might want to request access to an RDS database, customer impersonation tool, or a popular SaaS application. In the **Catalog**, Opal lists all the resources at your organization, so they're easy to both discover and request to access. Admins can manage resources from the **Inventory**. ## Roles **Roles** are permissions that you can request access to within a resource. Different **Roles** give you the ability to take different actions. For example, you might request access to **read-only** role to a RDS database or an **admin** role to a SaaS application. ## Apps **Apps** are the system that Opal uses to import resources. For example, an individual AWS account is an app. From that app, you can import EKS clusters, SSH instances, RDS databases, or IAM roles as resources. Additionally, an Okta account is an app. From that app, you can import Okta apps and Okta groups. Admins can manage apps from the **Inventory**. ## Owners **Owners** are specified groups of users you can set as the **Admin** or **Required Reviewers** for **Resources** and **Groups**. **Owners** are used to decentralize access management. * **Admins** can manage approval and security configurations. * **Required Reviewers** can approve or reject access requests. Admins can manage owners from the **Inventory**. ## Groups **Groups** are resources which grant a collection of other resources to users. Groups can grant member users [access to other groups](/docs/nested-groups), and both member users and member resources can be configured for just-in-time access. Existing groups from identity providers, such as Google Groups, Okta, Active Directory, can be imported into Opal. You can also create groups directly in Opal, under the **Opal** app in the Inventory. Groups can be synced to on-call schedules. This enables privileged access to be granted if users are on-call and removed if users are off-call. ## Tags **Tags** are key-value pairs that can be associated with Users, Groups, and Resources. Tags can be imported from end systems or natively created within Opal, and they are particularly useful for attaching metadata to objects. In the following example, the imported Tag `security:green` applies to Resource `opal-dev-sandbox` from AWS. Similarly, a Tag`department:engineer` that applies to User `Jane Doe` can be imported from an IDP or HR system like Okta or Workday to reflect a User's attribute. ## Non-human identities (NHIs) In Opal, non-human identities (NHIs) are identities that interact with an end system's resources or infrastructure and have some access rights. [GCP Service Accounts](/integrations/gcp-service-accounts), [Azure Service Principals](/integrations/azure), and [Databricks Service Principals](/integrations/databricks) are classified as NHIs and found in the **Inventory > NHIs** page after you import them. You can grant NHIs access to resources and groups, the same way you'd grant users access. [AWS IAM Roles](/integrations/adding-an-iam-role) are not currently treated as NHIs, but can be imported as resources. You can grant users and groups access to IAM roles. # Opal architecture Source: https://docs.opal.dev/docs/opal-architecture Learn about components in Opal's identity management platform. Use this guide for an overview of Opal's architecture and system components. ### End user experience **End users** request access to your resources using Opal's [web UI](/docs/end-user-faq), [Slack](/integrations/slack), [Google Chat](/integrations/google-chat), or the [Opal CLI](/docs/opal-cli). AI agents can request access using Opal's [MCP server](/docs/mcp-server). ### Orchestration layer In the **Orchestration layer**, admins can: * Configure [approval workflows](/docs/configure-reviewers) to customize how resources are requested and approved * Use the [Risk Center](/docs/least-privilege-posture-management) to view insights on anomalous access and proactively remediate vulnerabilities * Create [user access reviews](/docs/access-campaigns) to streamline compliance * Use [Access Rules](/docs/access-rules) to assign access to resources based on user attributes (ABAC), enabling Joiner-Mover-Leaver (JML) workflows ### Data fabric layer The **Data fabric** is composed of an access graph, where Opal tracks and exposes all direct and indirect access paths. Opal's integration catalog lets you easily connect to your [HR, identity, cloud, data, SaaS, and custom systems](/docs/set-up-connections). Custom connections provide the flexibility to combine and connect additional systems. Remote systems connect to the data fabric layer through a robust **Bidirectional sync**, which propagates all access changes to and from your end systems. Okta, AWS, and Azure also support real-time syncs, so access stays up-to-date. See individual integrations guides—e.g., [AWS](/integrations/setting-up-your-aws-organization-in-opal) and [GCP](/integrations/google-cloud-platform)—for details on capabilities. #### Additional data sources Additionally enrich your user data by syncing attributes from **User directories** and systems of record, such as Okta. #### On-call schedules Sync Opal groups with **On-call schedules** pulled from services such as [PagerDuty](/integrations/pagerduty-oncall) and [Opsgenie](/integrations/opsgenie). ### Orchestration tools To scale and configure your Opal deployment, you can: * [Set up Terraform](/docs/use-terraform-with-opal) to programmatically manage your Opal configuration and orchestrate your configuration * Use Opal's [REST API](/reference/) to connect to Opal objects * Connect to Opal's [MCP server](/docs/mcp-server) to allow your AI agents to interact with Opal ### SIEM and logging tools Opal's integrations with SIEM providers and logging tools allow you to stream Opal events directly to your logging providers, such as DataDog and SumoLogic. Set up [events streaming](/docs/events-streaming) to get started. ### ITSM tools Opal can connect to your [ticketing providers](/docs/ticket-propagation)–Jira, Linear, or ServiceNow—to propagate access using tickets, create audit tickets, and link requests to existing tickets in Opal. ### Deploy Opal Deploy Opal with your preferred method: * Use [Opal Cloud](/docs/set-up-cloud) to quickly connect to Opal's cloud instance * Set up self-hosted Opal on [AWS EKS](/docs/self-host-opal-aws-guide) or [GKE](/docs/self-host-opal-gke-guide) using [Helm](/docs/install-opal-using-helm) or KOTS to configure deployments * Configure [airgapped deployments](/docs/setup-an-airgapped-opal-environment) for self-hosted Opal *** # Opal CLI Source: https://docs.opal.dev/docs/opal-cli Learn how to configure and use the Opal CLI. Developers can use the Opal CLI to: * Request access to assets in Opal, and view outgoing requests * Assume IAM roles, Kubernetes roles, start database sessions, and SSH sessions * Get information on a particular resource * Use SCP to copy files to and from a compute instance ## Requirements Before you begin, install the following: * [Homebrew](https://brew.sh/) * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) * [AWS CLI - Session Manager Plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) * [Node v18 or later](https://github.com/nvm-sh/nvm) ## Installation ### OSX Install directly from npm: ```shell Shell theme={null} npm install -g opal-security ``` Alternatively, install the Opal CLI using Homebrew: ```shell Shell theme={null} brew install opalsecurity/brew/opal-security ``` To upgrade the CLI using Homebrew: ```shell Shell theme={null} brew update && brew upgrade opal-security ``` ### Linux and Windows On these platforms, you can install directly from npm: ```shell Shell theme={null} npm install -g opal-security ``` ## Usage See the Opal CLI [npm page](https://www.npmjs.com/package/opal-security) for a list of all available CLI commands. ### Authentication Use `opal login` to authenticate to the Opal CLI. This creates an authentication session token and stores it securely for future commands. This token expires based on your organization settings, found in **Organization Settings > Authentication > Login session lifetime**. On OSX, the keychain stores this token. On Linux and Windows, the CLI requires a separate password to store this token securely. Use a password that differs from your Opal and existing passwords. You'll be prompted for this password on all subsequent CLI calls. ### Airgap Setup If you have an [airgapped, self-hosted deployment of Opal](/docs/setup-an-airgapped-opal-environment), you will need to run this command after installing the CLI: ```shell Shell theme={null} opal set-auth-config --clientID= --issuerUrl= ``` Use the Client ID and Issuer URL of [the OIDC provider you created specifically for the CLI](/docs/setup-an-airgapped-opal-environment#cli-specific-oidc-provider). This only needs to be run once, after which the client ID and issuer URL will be stored locally. ### Create requests You must be on version 3.2.1 or later of the CLI to use the `request` commands. The CLI includes the following [commands](https://www.npmjs.com/package/opal-security#opal-request-create) for creating access requests. `opal request create`: Create an access request following an interactive prompt. `opal request create --assets [asset_id]`: Create an access request for a given group or resource. `opal request get --id [request_id]`: Retrieve details about an access request. `opal request ls --n`: List `n` most recent outgoing requests. `opal request list`: Retrieve all access requests created by the calling user. #### Examples 1. Request a resource or group by ID, where the ID can be found in the URL, e.g., `app.opal.dev/resources/{resource_id}` or, for admins, the resource or group **Details** tab. ``` $ opal request create --assets {resource_id} $ opal request create --assets {group_id} $ opal request create --assets ac4833298-8f5e-4e46-a032-73fc946dff6a ``` 2. Request a group or resource with a given role. ``` $ opal request create —-assets {resource_id}:{roleName} $ opal request create —-assets ac4833298-8f5e-4e46-a032-73fc946dff6a:readonly ``` ## Troubleshooting If you run into errors from Node (e.g. `incompatible architecture`), make sure your local environment is using the Node version that you installed the Opal CLI on. If you're using `nvm`, you can view a list of installed Node versions via `nvm ls` and switch using `nvm use `. If you have conflicting packages named `opal`, you may need to set an alias: `alias op="npx opal-security"`. # Overview Source: https://docs.opal.dev/docs/opal-overview Opal is the centralized authorization platform for modern businesses, offering a self-service app catalog for employees to browse and request access to critical applications quickly on via web, Slack, or Google Chat. System owners can easily set up approval and security configurations to ensure that all access is secure and logged for compliance standards. ## Why should organizations use Opal? **1. Employees can get access to applications in minutes, not days or weeks**. Instead of relying on centralized teams, Opal delegates approvals to system owners and managers with the most context. In addition, Opal uses powerful automation via Slack to easily provision (and de-provision) access **2. Companies can easily adopt least privilege and bolster security posture.** With Opal, companies can reduce access footprint with granular and short-lived just-in-time access requests. **3. Organizations can be compliance-ready without the manual overhead.** Opal automates time-consuming spreadsheet based workflows for user access reviews, which are necessary for SOX, SOC-2, ISO, and HITRUST, and more. # Overview Source: https://docs.opal.dev/docs/opal-query Explore and analyze access across your organization with OpalQuery. OpalQuery — found under **Queries** in the admin sidebar — is an interactive tool for admins to explore access across your organization. Use it to understand who has access to what, audit access patterns across connections and entity types, and export results. OpalQuery answers two kinds of questions: * **Node queries** return **entities** — a list of users, resources, or groups that match your filters. * **Access queries** return **access** — each result is a *principal → asset* relationship with the **access level (role)**, showing who can reach what and how. Use OpalQuery to: * Find entities with certain attributes (e.g. type, tags) * See who has access to a resource or group, and trace how that access is granted * Surface risky access — orphaned grants, standing privilege, or toxic combinations * Save and share queries with other admins * Export query results for reporting csrfAuthenticationKey: databaseEncryptionKey: opalApiEncryptionKey: secureCookieHashkey: # You may alternatively point to an existing secret for these, like so: # authTokenEncryptionKey: # secretKeyRef: # name: "mySecretName" # key: "authTokenEncryptionKey" redisHost: opal-web-redis-master redisPort: 6379 # A DNS name you own that you will use to access Opal. hostname: # Optional. Set this to have pods use an existing service account in your cluster. Otherwise, omit this. serviceAccount: # This configures an SMTP provider for your instance of Opal to use for sending email notifications # If set to false, Opal would use its default external service for sending emails smtpEnable: true smtpServer: smtpPort: # Set smtpEncryptionEnabled to true to enable SMTP encryption. serverPort 465 uses SMTPS and serverPort 587 uses STARTTLS smtpEncryptionEnabled: false # Optional. Enables async data exports backed by an S3-compatible bucket. Omit this block entirely to leave the feature off. # In an airgapped environment, point this at an in-cluster store such as MinIO — cross-account AWS S3 is not reachable. # See https://docs.opal.dev/docs/configure-async-exports-storage for setup details. exportStorage: bucketName: endpoint: accessKey: secretKey: # This is what tells Opal to run as an Airgapped instance fullAirGap: enabled: true # Use the Client ID and Client Secret of the *main* OIDC provider you created - not the one customized for the CLI. authClientID: authClientSecret: # This is a human-readable name you can provide as a label for your auth provider # When signing in, your users will see a button that reads "Signin with " authProviderName: # The private image repository you setup for Opal images image: repository: # Set these `registry` values to the same as `image.repository` above redis: image: registry: redis-sentinel-event-streaming: image: registry: sentinel: image: registry: # Setting `enabled` to true will deploy an Ingress resource you can use to route traffic to Opal # If you disable this, you will need to create your own ingress that routes traffic to the `opal-web` service ingress: enabled: true # Always leave `override=true` override: true overrideAnnotations: # Here, you should fill in annotations that will link your ingress to a load balancer in your cloud provider # For AWS, this will look something like: # kubernetes.io/ingress.class: alb # alb.ingress.kubernetes.io/scheme: internet-facing # alb.ingress.kubernetes.io/certificate-arn: # For GCP, this will look something like # ingress.gcp.kubernetes.io/pre-shared-cert: opal-certificate # kubernetes.io/ingress.global-static-ip-name: opal # This controls RelayProxy, our replacement service for LaunchDarkly that provides feature flags to your cluster ld-relay: image: # You may also set a `tag` attribute here if you need to override the version of the proxy to run # Otherwise, it'll default to the same version as your application repository: /opal-ld-relay-proxy # Providing "false" here will prevent your instance of Opal from exporting application logs log-forwarder: enabled: false ``` Once your values are configured, you can install Opal using the install command in your download portal ``` ``` # SSO with SAML Source: https://docs.opal.dev/docs/sso-with-saml Learn how to set up SAML SSO to authenticate users to Opal. You can configure Opal to authenticate users via SAML SSO by setting up a SAML provider. Okta and Google are officially supported, but you should be able to configure other providers. Use the following guides to set up SAML SSO for Okta or Google. * [Okta SAML Setup](/docs/okta-saml-setup) * [Google SAML Setup](/docs/google-saml-setup) ## Restrict logins to SAML only By default, when you set up SAML SSO in Opal, Opal allows users to log in using either SAML SSO or Opal's traditional sign-in options (e.g. Google, Microsoft 365). You can alternatively require that users must login to Opal via SAML. To do this: 1. Go to **Settings** > **Authentication** > **SAML SSO Settings**. 2. Set up a SAML connection using [Okta SAML Setup](/docs/okta-saml-setup) or [Google SAML Setup](/docs/google-saml-setup). 3. Toggle **Restrict logins to SAML only** to **ON**. ## SAML breakglass users Even if you've restricted your organization to log in via SAML, it can still be useful to allow a subset of users to log in with other methods. In Opal, these are called *SAML breakglass users*. To edit this list, ensure that **Restrict logins to SAML only** in **Settings > Authentication > SAML SSO Settings** is turned **ON**. Then: 1. Click the **Users** button to edit the list of SAML breakglass users. 2. Add or remove users to the list. All users on this list will be able to login to Opal via both SAML and non-SAML methods. # Sync schedules and triggers Source: https://docs.opal.dev/docs/sync-schedules-and-triggers Learn how Opal syncs apps, groups, resources, and users from remote systems. Real-time sync, which syncs objects on an event-driven basis as they're updated in end systems, is supported for Okta, Azure, and AWS. Follow the guides to enable real-time sync for [Azure](/integrations/azure-real-time-sync) or [AWS](/integrations/aws-real-time-sync). [Okta real-time sync](/integrations/okta#real-time-sync) is automatically enabled. Learn about the different ways Opal supports syncing items from your end system. Note that syncs, both automatic and manual, may be throttled to preserve service. ## Automatic syncs Opal periodically syncs resources, apps, groups, and users from your end systems. **Managed** resources are apps, groups, and resources you’ve imported to Opal. **Unmanaged** resources exist in your end system but are not imported to Opal. See the guide to [import items to Opal](/docs/import-and-configure-resources-and-groups) to learn more. ### Periodic syncs Multiple times per day, [user attributes](/docs/add-additional-idphr-providers#import-attributes) from IDP/HRIS data, and managed resources and groups are synced. These syncs may be skipped if they overlap with other syncs. Multiple times per week, all apps, groups, and resources are synced, including unmanaged ones, to reconcile any discrepancies between the end system and objects in Opal. These syncs may be delayed given high volume. Both of these syncs may create and remove users in Opal if they've changed in the remote app. ### High-priority syncs The following tasks are considered high-priority and are executed soon after they’re triggered: * On-call schedules synced from [productivity integrations](/docs/connect-your-productivity-tools) * [Access rule](/docs/access-rules) membership updates * Notification sends for escalated request or access reviews * [Ticket propagation](/docs/ticket-propagation) ## Trigger manual syncs ### Managed resources and groups To sync resources, groups, and users for an individual app, go to the detail page for the app in the **Inventory** and select the **Sync item** button on the upper right. To sync all apps, resources, groups, and users, go to **Inventory** > **Apps** and select the **Sync Inventory** button the upper right. Both of these syncs may create and remove users in Opal if they've changed in the remote app. These syncs do **not** sync user attributes. ### User attributes To sync user attributes from your IDP/HRIS—title, manager, etc.—go to the **Inventory** > **Users** tab and select the **Sync Users** button on the upper right. This does not create new users, but it populates attributes for existing users. ## View sync status Use the **Sync Status** modal by clicking the last synced date on the upper right to see when and how objects were last synced—this includes manual, high-priority, hourly, and weekly syncs. Scheduled syncs show the **Synced by** field set to **Opal System**. You can also use the `GET sync_errors` [API endpoint](/api-reference/apps/get-sync_errors) to programmatically retrieve errors since the last successful sync. # Manage access with ticketing Source: https://docs.opal.dev/docs/ticket-propagation Sometimes after a user’s access is approved or revoked in an end system, additional manual work is necessary to provision or update access. Opal lets you use ticketing systems—[Jira](/integrations/jira), [Linear](/integrations/linear), or [ServiceNow](/integrations/servicenow)—to propagate access and gain visibility into what requires manual provisioning. In Opal, you can: * [Reference](#reference-existing-tickets-on-access-requests) existing tickets—require that every access request includes a link to an existing ticket * [Audit](#create-audit-tickets) access by creating a ticket for every request or revocation * [Propagate](#propagate-access-with-tickets) access using tickets—Opal can automatically file tickets when access is approved, revoked, or both, and sync access to end systems when tickets are closed ## Requirements To integrate with your ticketing provider in any form, you must first connect it as a [Productivity Integration](/docs/connect-your-productivity-tools). Opal supports [Jira](/integrations/jira), [Linear](/integrations/linear), and [ServiceNow](/integrations/servicenow) as ticketing providers. You can connect to your ticketing provider to any resource, including [custom](/docs/how-to-create-your-own-connector) resources, that a user can request to access. ## Reference existing tickets on access requests To require users to connect to existing tickets when they request access, first go to the **Inventory**, then the **Edit** page for the resource in question. In the **Request configuration** section under **Request Information**, set **Must link ticket that auto-expires access setting** to **Required**. When users request access to the resource, they must now select a ticket to submit the request. Access is automatically revoked and propagated when the ticket closes or when the selected time interval has elapsed. You can also enable this setting for your entire organization. Go to **Settings** > **Access Requests**, then select **Requests must link ticket that auto-expires access**. ## Create audit tickets To create an audit ticket for every access request in Opal, go to **Settings** > **Access Requests** and select **Create audit ticket for all access requests**, then enter your [ticket provider](/docs/connect-your-productivity-tools) details. Whenever a user requests access in Opal, Opal creates a ticket in your ticketing provider, allowing you to audit every access request in your own system. Audit tickets are auto-closed by Opal when access expires. You can manually close audit tickets in your provider, but this has no effect in Opal. ## Propagate access with tickets When ticket propagation is enabled, Opal files a ticket on your ticket provider when users are granted access, have access revoked, or both. When tickets are completed or marked **Done** in the ticketing provider, Opal propagates the access state to any connected remote systems. ### Example Suppose Josh requests access to a custom resource called **My Role**. The required reviewers for **My Role** are notified in Slack that they have a request to review. When the request is approved, Opal creates a Jira ticket to assign and track the task of making the access change on the end system. In Jira, you can see a reference to the ticket from the **My Role** resource page on the **Users** tab. If you click the ticket's identifier, a modal displays with a link to the ticket. The ticket appears on the ticketing provider as follows. When the access change has been made on the end system, the assignee should mark the ticket as **Done**. Opal then records the access change as complete and syncs the change to any remote systems. This appears as a green sync icon next to the user's access. Note that it may take several minutes for Opal to sync the ticket's status. If you're the admin of the resource, you can also force a sync by clicking the sync button on the top right of the resource's page. ### Configure ticket propagation To enable ticket propagation, from the **Inventory** page, edit the resource. Toggle on **Ticket provider for access propagation** in the left panel and specify the provider, project, and conditions to create tickets. 2312 If you don’t see the **Ticket provider for access propagation** option, ensure you’re editing the resource, not the app. *** # Update your Opal installation Source: https://docs.opal.dev/docs/update-self-hosted-opal Learn how to update your self-hosted Opal installation. New releases of Opal are made available on a regular basis—see the [changelog](/changelog) for details. To update your installation: * Connect to your Opal on-premise dashboard: `https://:8800` * Click **Check for update**, which fetches the latest available version from Replicated * Click **Deploy** to deploy, which automatically updates all necessary components You can also go to the **Version History** in your on-prem dashboard, which has a list of all available versions. From there, you can roll back to previous versions. Deploying a new release should take less than a minute and does not cause downtime. However, you should always monitor the upgrade in case issues arise. ## Security Updates: Kots To update KOTS and the admin console, follow these instructions: 1. Log in to the cluster 2. Install the latest version of kots ```Text Shell theme={null} curl https://kots.io/install | bash ``` 3. Upgrade your cluster ```Text Shell theme={null} kubectl kots admin-console upgrade -n NAMESPACE ``` ## Troubleshooting updates ### Blank page after update If you see a blank page after updating Opal, you can try the following to troubleshoot: **Clear your browser cache:** 1. Follow instructions for [Chrome](https://support.google.com/accounts/answer/32050?hl=en\&co=GENIE.Platform%3DDesktop), [Safari](https://www.macrumors.com/how-to/clear-safari-cache/), or your chosen browser. 2. After clearing the cache, refresh the page to see if the issue is resolved. **Clear your Cloudflare cache:** 1. Log in to your Cloudflare dashboard and go to **Caching > Configuration**. 2000 2. Click **Purge Everything**. Wait up to 30 seconds, then refresh your Opal page. 2292 # Use Terraform with Opal Source: https://docs.opal.dev/docs/use-terraform-with-opal Learn how to set up Opal's Terraform provider. You can use Opal’s Terraform provider to manage your Opal infrastructure with code. Use the following instructions to install and get started with Terraform in Opal. ## Overview Most functionality in the Opal UI is also supported using Terraform. Refer to the provider [documentation](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/resources/access_rule) to see available resources, and reach out if you have specific requests. You should generally use Terraform to manage stateless settings, e.g., request configurations or adding users as permanent members of groups. Once you start using Terraform to manage settings, try to minimize modifications to the same settings in the Opal UI. Terraform is not suited for managing settings which depend on actions occurring at a specific moment in time—e.g., converting users to timebound access—because Terraform maintains its own internal state. You also cannot create [custom apps](/docs/how-to-create-your-own-connector) with Terraform. ## Requirements Before you begin, you must: * Create a full-access [API token](/reference/authentication) in Opal * [Install Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) ## Install Opal’s Terraform provider To install Terraform for Opal: 1. Go to [Opal’s Terraform provider](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs). 2. Select **Use Provider** and copy the dropdown code block. Ensure you use the latest version. 3. Create a configuration file and paste in the code block. 4. If you self-host Opal, in the `provider` section, add `server_url = “"`, replacing `SERVER_URL` with your self-hosted domain and the suffix `/v1`—for example, `https://company-name-opal.dev/v1`. If you use cloud Opal, skip this step. 5. Save your Opal API key in an environment variable named `OPAL_AUTH_TOKEN`. Alternatively, or if you're on a version earlier than 3.0.2, add `bearer_auth = "”` to the `provider` section, replacing `API_KEY` with your Opal API key. 6. Save the configuration file. 7. Run `terraform init`. 8. Run `terraform apply`. ## Use Opal’s Terraform provider See the [Terraform provider](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs) documentation for example usage. To create certain resources, you may need to provide IDs, which you can find for most objects on the **Detail** tab in the **Inventory** in the Opal dashboard. For example, to [create a group](https://registry.terraform.io/providers/opalsecurity/opal/latest/docs/resources/group) with the Terraform provider, you must provide an `app_id` to contain the group. You can find this ID from the app's **Detail** tab. ### Example configuration and usage The following is an example resource block used to create a new Opal group. ```json example.tf theme={null} terraform { required_providers { opal = { source = "opalsecurity/opal" version = "3.0.12" } } } provider "opal" { // Define OPAL_AUTH_TOKEN as an environment variable, otherwise include the following line: // bearer_auth = 'API_KEY_HERE' } resource "opal_group" "tf_test" { name = "tf_test_group" group_type = "OPAL_GROUP" app_id = "3ecff5db-e3fc-40c7-bb36-ff202f033095" // Retrieved from Opal dashboard visibility = "GLOBAL" request_configurations = [ { priority = 0 require_mfa_to_request = false allow_requests = false require_support_ticket = false auto_approval = false } ] } ``` After running `terraform plan` and `terraform apply`, the group is created. # User provisioning Source: https://docs.opal.dev/docs/user-provisioning Learn about user provisioning and deprovisioning in Opal. Opal supports automatic user provisioning and deprovisioning for some integrations. Feel free to [reach out](mailto:support@opal.dev) to request user provisioning or deprovisioning on additional integrations. ## User provisioning User provisioning is triggered when users are granted access to a group or resource in Opal, or from the POST `/users` [endpoint](https://docs.opal.dev/docs/api-spec#post-%2Fusers) for custom connections. When provisioning is enabled on an integration, users are created in the end system if they don't already exist. Provisioning is supported in the following integrations: * Custom Connectors * Salesforce * Snowflake Enable user provisioning when you edit a connection. For example, on custom connectors, use the **Provision custom connector accounts** setting. ## User deprovisioning User deprovisioning deactivates a user in an end system. For custom connectors, you should implement the `DELETE /users/{user_id}`[endpoint](https://docs.opal.dev/docs/api-spec#delete-%2Fusers%2F\{user-id}) to deactivate your users. When enabled, user deprovisioning is triggered: * Manually, by selecting a user in **Inventory** > **Accounts** for the connection * In access reviews, when access is revoked * When a user is deprovisioned in an IdP/HRIS Deprovisioning is supported in the following integrations: * Custom Connectors * Okta * Duo * Google Workspace * Salesforce * Pagerduty Enable user deprovisioning when you edit a connection. In the custom connector example, use the **Deprovision custom connector accounts** setting. *** # Webhooks Source: https://docs.opal.dev/docs/webhooks Learn how to configure webhooks to push changes on access requests. In this guide, you'll learn how to use **Webhooks** to integrate a custom app with Opal. See the [Custom Integrations overview](/docs/custom-integrations-overview) to learn when to use webhooks. You can currently only configure one webhook per organization. ## Setup Under **Configuration** > **Organization Settings** in the left panel, go to **Webhooks**, enter the URL for your webhook endpoint, and select **Save Changes**. 2312 A signing secret will be generated. Your system should use this secret to validate the webhook's origin. Please see [this section](/docs/webhooks#verifying-requests-from-opal) for more details. Next, create or edit a **Push-only app** from the **Inventory**, then toggle **Webhook for access changes**. The webhook endpoint is expected to reply to the HTTP POST request with an HTTP 200 response within 10 seconds of receiving the payload. ## Payload samples Opal sends different payloads depending on the event type. The following event types are supported. ### Add users to resource ```json json theme={null} { event_ts: 1643355931, organization_id: 'eec5dcd9-fb0a-4726-b283-4379b524a772', events: [ { event_type: 'USER_ADDED_TO_RESOURCE', resource_type: 'CUSTOM', user_id: '[email protected]', resource_id: '0eb35db24007c8148', access_level: 'admin' } ] } ``` ### Remove users from resource ```json json theme={null} { event_ts: 1643355980, organization_id: 'eec5dcd9-fb0a-4726-b283-4379b524a772', events: [ { event_type: 'USER_REMOVED_FROM_RESOURCE', resource_type: 'CUSTOM', user_id: '[email protected]', resource_id: '0eb35db24007c8148', access_level: 'admin' } ] } ``` ### Add users to group ```json json theme={null} { event_ts: 1643346009, organization_id: 'eec5dcd9-fb0a-4726-b283-4379b524a772', events: [ { event_type: 'USER_ADDED_TO_GROUP', group_type: 'CONNECTOR_GROUP', user_id: '[email protected]', group_id: '00g1835ro5hxRmpel5d7' } ] } ``` ### Remove users from group ```json json theme={null} { event_ts: 1643355811, organization_id: 'eec5dcd9-fb0a-4726-b283-4379b524a772', events: [ { event_type: 'USER_REMOVED_FROM_GROUP', group_type: 'CONNECTOR_GROUP', user_id: '[email protected]', group_id: '00g1838z8y1VJcoTG5d7' } ] } ``` ### Add resources to group ```json json theme={null} { event_ts: 1643356058, organization_id: 'eec5dcd9-fb0a-4726-b283-4379b524a772', events: [ { event_type: 'RESOURCE_ADDED_TO_GROUP', group_type: 'CONNECTOR_GROUP', resource_type: 'CUSTOM', group_id: '00g1838z8y1VJcoTG5d7', resource_id: '0eb35db24007c8148' } ] } ``` ### Remove resources from group ```json json theme={null} { event_ts: 1643356091, organization_id: 'eec5dcd9-fb0a-4726-b283-4379b524a772', events: [ { event_type: 'RESOURCE_REMOVED_FROM_GROUP', group_type: 'CONNECTOR_GROUP', resource_type: 'CUSTOM', group_id: '00g1838z8y1VJcoTG5d7', resource_id: '0eb35db24007c8148' } ] } ``` ## Verify requests from Opal Verify requests from Opal with confidence by checking signatures using your signing secret. Opal includes an `X-Opal-Signature` HTTP header on each HTTP request sent. The signature is created by combining the signing secret with the body of the request we're sending using a standard HMAC-SHA256 keyed hash. Here is an example with Node to compute the signature using your signing secret. You can compare it against the value retrieved from the `X-Opal-Signature` header. ```js js theme={null} const timestamp = request.header('X-Opal-Request-Timestamp') const signingSecret = 'SIGNING_SECRET' const sigBaseString = 'v0:' + timestamp + ':' + JSON.stringify(request.body) const hmac = crypto.createHmac('sha256', signingSecret); hmac.write(sigBaseString) console.log(hmac.digest('hex')) ``` # Quickstart Source: https://docs.opal.dev/docs/welcome Explore our guides to enable Opal in your organization ### Common Use Cases Set up a self-service catalog for employees to request access. Automate, delegate and manage user access reviews. Use access rules to enforce access policies at scale. ### Get Started Sign up and register your organization with Opal Cloud. Deploy Opal on your own infrastructure. Sync your organization's identities. Connect to our out-of-the-box integrations. Connect Opal with any end system. ### For Developers Get familiar with the Opal CLI. Manage Opal Infrastructure with code. # Overview Source: https://docs.opal.dev/integrations/active-directory Connect your Active Directory server to use Opal to manage and review access. Opal's integration with Active Directory supports the following, and more: * Users can **request time-bounded access** to your AD groups. * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to AD groups. * Admins can **add resources from other Opal integrations** to an AD group so an AD group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * All access changes are tracked as [events](/docs/event-types) that can be logged to a Slack channel or be exported to your favorite tools. ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ----------------------- | ---- | ----------------------- | ------------------------ | | Active Directory Groups | ✔️ | ✔️ | ✔️ | ## Create an Active Directory app To get started, go to the **Inventory** > **Apps** page, then select **+App**. Select the Active Directory tile. You will see a form to be completed. Opal requires the following credentials in order to manage access to your AD groups. ## Step 1 - Configure an Active Directory service account for Opal In order for Opal to manage your Active Directory server on your behalf, you need to create an Active Directory service account for your server with proper permission scopes. * Connect to a Domain Controller or to a computer with Active Directory Remote Server Administration Tools installed. * Click **Start**, type **"dsa.msc"**, then press **Enter**. * Navigate to the Organizational Unit where the Opal Service Account will be located. * Right-click the Organizational Unit, select **New** > **User**. * Optional: Type **"Opal"** into the **First Name** field and **"Service Account"** into the **Last Name** field. * Type **"OpalServiceAccount"** into the **User logon name** field. Click **Next**. * Configure a password based on your organization's password policy requirements, uncheck the **User must change password at next logon** checkbox, and check the **Password never expires** checkbox. Click **Next**. Click **Finish**. * Double click on the newly created service account user. On the **Member Of** tab, add the **Domain Admins** group (or if you're using AWS Managed AD, then add **AWS Delegated Administrators** instead). Then save the account and click **OK**. ## Step 2 - Fill out Opal form Back in the Create App form, fill in details about your Active Directory server and service account: * For **Server hostname** and **Server port**, input the hostname and port of your Domain Controller. * Please ensure your AD hostname is reachable from the instance that is hosting the Opal app. * For **Base distinguished name**, enter the Distinguished Name (DN) of the OU that Opal should begin directory searches from. * For **Root username** and **Root password**, enter the credentials of the AD service account that you created above. If this step is successful, you have completed setting up the Active Directory server connection. # Active Directory IDP/HRIS Integration Source: https://docs.opal.dev/integrations/active-directory-idphris-integration If your organization uses Active Directory as an Identity Provider, you can additionally designate it as an [IDP/HRIS Integration](/docs/add-your-first-idphr-provider). Doing so allows Opal to sync your Active Directory identities and their attributes, on top of syncing and managing access to entitlements. ## Getting started If you use self-hosted Opal, you must be on version 1.1017.0 or later to use Active Directory as an IDP. Before you set up Active Directory as your IDP, you must first create an [Active Directory integration](/integrations/active-directory) in Opal. Next, set up Active Directory as your IDP by following instructions here: * [Add Your First IDP/HR Provider](/docs/add-your-first-idphr-provider) * [Add Additional IDP/HR Providers](/docs/add-additional-idphr-providers) # Add an EC2 instance Source: https://docs.opal.dev/integrations/adding-an-ec2-instance Add your AWS EC2 instances to Opal to allow your developers to request temporary access. This guide assumes you've already [configured your AWS organization in Opal](/integrations/setting-up-your-aws-organization-in-opal). The following diagram illustrates how Opal connects to AWS EC2. Use this guide to learn how you can add EC2 instances to Opal. 1920 With Opal, you can grant `ssh` access to any EC2 instance running on Amazon to your developers in minutes. We make this easy by using AWS's Systems Manager API. To make this available for your organization, you'll have to enable a few things. ## Add an EC2 instance ### 1. Enable Secure Session Manager (SSM) By default, EC2 instances don't allow `ssh` sessions using Secure Session Manager. You'll need to attach the `AmazonSSMManagedInstanceCore` AWS-managed policy to your EC2 instance profile. If an instance profile doesn't exist on that instance you'll have to create one. To determine if your EC2 instance already has a role attached to it, you can check in the AWS Console using the following instructions: #### 1a. Check the AWS Console First navigate to your running [EC2 instances using this link](https://console.aws.amazon.com/ec2/v2/home#Instances:instanceState=running)\*. Now click on the instance ID of the EC2 in question, and verify whether a role is attached already. If a role already exists then skip to Step 1c. Otherwise, proceed to Step 1b. #### 1b. Create an IAM role If you already had a role attached skip to the next section. Otherwise, create a new IAM role using the steps below: Attach the `AmazonSSMManagedInstanceCore` and `CloudWatchAgentServerPolicy` policies to your new role. Finally, you should attach your newly created role to your EC2 instance. Since your instance didn't originally have a role attached, **you'll need to restart it**. You can now skip to step 2! #### 1c. Add the SSM policy to your existing role Click on the role in the EC2 dashboard to attach a role to it. Now search and find the `AmazonSSMManagedInstanceCore` policy and attach it to your existing profile. ### 2. Tag your EC2 instance To have Opal automatically import your EC2 instance, you'll need to tag it. You can do this using the AWS Console, CLI, or Terraform below: #### AWS Console Navigate to your EC2 instance in the EC2 Dashboard. Select "Manage tags" and add the `opal` tag as seen below. #### AWS CLI or Terraform ```shell AWS CLI Commands theme={null} aws ec2 create-tags \ --resources "i-0000000000" \ --tags "Key=opal,Value=" ``` ```text Terraform (aws_instance) theme={null} # If you are using `aws_instance` in Terraform to provision EC2 nodes, # add the following `tags` argument to the `aws_instance`. tags = { opal = "" } ``` ```text Terraform (aws_eks_node_group) theme={null} # If you are using an `aws_eks_node_group` to launch EC2 instances, # add the following launch template to your Terraform file. resource "aws_launch_template" "ec2_launch" { instance_type = YOUR_INSTANCE_TYPE tag_specifications { resource_type = "instance" tags = { opal = "" } } } # Then, reference the launch template in your EKS node group by # adding the following argument to your EKS node group. launch_template { id = aws_launch_template.ec2_launch.id version = aws_launch_template.ec2_launch.latest_version } ``` ### Optional: Enable KMS Encryption #### 1. Create an Opal KMS key To enable KMS encryption, first create a KMS key with the following alias: `opalssmkms`. Under advanced settings, make sure to make this key multi-regional. #### 2. Enable encryption You can enable encryption in the Session Manager console in AWS by going to **Systems Manager > Session Manager > Preferences > KMS Encryption** and selecting the key created in the previous step. ## Access your instance in Opal If you followed the above steps to configure your EC2 instance, it should now show up in Opal. Permissions to EC2 instances are session-based, meaning they require your developers to initiate a session when they want to access that instance. They can do so by clicking the "**Connect**" button. 2312 Once they're connected, they can SSH instance using an in-browser command line or in their own terminal! # Add an EKS cluster Source: https://docs.opal.dev/integrations/adding-an-eks-cluster Add your AWS EKS clusters to Opal to allow your developers to request temporary access. This guide assumes you've already [configured your AWS organization in Opal](/integrations/setting-up-your-aws-organization-in-opal). The following diagram illustrates how Opal connects to AWS EKS. Use this guide to learn how you can add EKS clusters to Opal. 3840 To set up Opal to grant access to your AWS EKS cluster roles, follow the steps below. ## Add an EKS cluster ### 1. Create an IAM role First, you need an IAM role that can be mapped a Kubernetes role that you want to make available in Opal. You can use an existing IAM role or create a new one - at the minimum, the role must have the `eks:DescribeCluster` permission on the EKS cluster(s) you want to manage. If creating an IAM role, we've provided two ways to do this below, via AWS CLI commands or Terraform: ```shell AWS CLI commands theme={null} # Add your AWS account ID to an environment variable ACCOUNT_ID= # Add your IdP issuer URL to an environment variable IDP_ISSUER_URL= # Add your Opal Client ID to an environment variable OPAL_CLIENT_ID= # Create the IAM role naming it something your developers will understand ROLE_NAME= # Add your cluster ARN to an environment variable CLUSTER_ARN= # Create the role trust policy locally TRUST="{ \"Version\": \"2012-10-17\", Statement\": [ { \"Effect\": \"Allow\", \"Principal\": { \"Federated\": \"arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${IDP_ISSUER_URL}\" }, \"Action\": \"sts:AssumeRoleWithWebIdentity\", \"Condition\": { \"StringEquals\": { \"${IDP_ISSUER_URL}:aud\": \"${OPAL_CLIENT_ID}\" } } } ] }" echo "{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Effect\": \"Allow\", \"Action\": \"eks:DescribeCluster\", \"Resource\": \"${CLUSTER_ARN}\" } ] }" > /tmp/iam-role-policy # Create the IAM role aws iam create-role --role-name "$ROLE_NAME" --assume-role-policy-document "$TRUST" --output text --query 'Role.Arn' # Attach the policy to the role aws iam put-role-policy --role-name "$ROLE_NAME" --policy-name eks-admin --policy-document file:///tmp/iam-role-policy ``` Make sure to set the following variables when running the above code: * `ACCOUNT_ID=` * `IDP_ISSUER_URL=` * `OPAL_CLIENT_ID=` * `ROLE_NAME=` * `CLUSTER_ARN=` ### 2. Update the aws-auth Configmap The `aws-auth` Configmap exists on every EKS cluster and is what AWS uses to map IAM roles to Kubernetes roles. To map the role you created above to a `cluster-admin` level role in Kubernetes, please run the following commands: ```shell Shell commands theme={null} ROLE=" - rolearn: arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME\n username: eks-cluster-admin:{{SessionName}}\n groups:\n - system:masters" kubectl get -n kube-system configmap/aws-auth -o yaml | awk "/mapRoles: \|/{print;print \"$ROLE\";next}1" > /tmp/aws-auth-patch.yml kubectl patch configmap/aws-auth -n kube-system --patch "$(cat /tmp/aws-auth-patch.yml)" ``` The aws-auth Configmap lets you map IAM roles to different Kubernetes roles. You'll need to do this if you want to allow users to request access to custom access levels, like a "read-only" role. You can manually edit the Configmap by running the following command: `kubectl edit configmaps aws-auth -n kube-system` Then, you can map different roles to your IAM role ARN. See the following articles to learn how: * [Adding limited-access IAM users to EKS clusters](https://www.freecodecamp.org/news/adding-limited-access-iam-user-to-eks-cluster/) * [Kubernetes RBAC concepts](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) If this part of the setup is confusing, feel free to [reach out](mailto:support@opal.dev)—we're happy to help. ### 3. Tag your EKS cluster You'll need to tag your EKS cluster in two ways to properly set it up for Opal. First, you must tag the cluster with any IAM roles that you set up in Step 1. For each IAM role, create a tag whose key is prefixed with `opal:eks:role`, and whose value is the name of the AWS IAM role. Below, we show an example of a cluster tagged with 2 IAM roles: ```shell AWS CLI commands theme={null} aws eks tag-resource --resource-arn "$CLUSTER_ARN" --region $REGION —tags "opal:eks:role:1=ClusterAdmin,opal:eks:role:2=ClusterView" ``` ```text Terraform theme={null} module "eks" { # ... other configuration cluster_tags = { "opal:eks:role:1" = ClusterAdmin "opal:eks:role:2" = ClusterView } } ``` Each of these roles will be auto-imported as a Role on the cluster in Opal. Second, to have your EKS cluster auto-imported into Opal in Opal's hourly sync, tag the cluster with key `opal`: ```shell AWS CLI commands theme={null} aws eks tag-resource --resource-arn "$CLUSTER_ARN" --region $REGION --tags "opal=" ``` ```text Terraform theme={null} module "eks" { # ... other configuration cluster_tags = { # Note: the tag value can be empty; however currently terraform-aws-provider has an issue # adding tags with empty values # https://github.com/hashicorp/terraform-provider-aws/issues/21896 "opal" = "x" } } ``` ## Access your cluster in Opal Any EKS clusters tagged using with key `opal` will be auto-imported into the "Resources" page in the "Kubernetes" folder. Permissions to EKS clusters are session-based, meaning users must initiate temporary sessions to them. They can do so using the **Connect** button after clicking into an EKS cluster resource. Once they're connected, they'll be given temporary credentials to access the Kubernetes cluster. # Add an IAM role Source: https://docs.opal.dev/integrations/adding-an-iam-role Add your AWS IAM roles to Opal to allow your developers to request temporary access. This guide assumes you've already [configured your AWS organization in Opal](/integrations/setting-up-your-aws-organization-in-opal). ## Add a role Use the following steps to connect an IAM role to Opal. ### Trust policy You must use the following trust policy for your role, substituting as follows: * `${ACCOUNT_ID}`: The account ID of the account being configured. * `${IDP_ISSUER_URL}`: The Identity Provider's issuer URL * `${OPAL_CLIENT_ID}`: The Client ID assigned to Opal via your IdP. ```json trust_policy.json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${IDP_ISSUER_URL}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${IDP_ISSUER_URL}:aud": "${OPAL_CLIENT_ID}" } } } ] } ``` ### Create a role Here is an example invocation for creating a role with the above trust policy file: ```shell Creating an IAM role for Opal aws iam create-role \ --role-name theme={null} MyRoleWithPoliciesToBeManagedByOpal \ --assume-role-policy-document file://trust_policy.json \ --tags Key=opal,Value="" \ --description "My role containing policies to be managed by Opal" ``` ### Attach policies to a role Next, attach all the policies you want to show up in Opal under this role. You can do this in the AWS Console: ### Terraform If you use Terraform, for an existing `aws_iam_role`, you can use the following arguments for the role you want to manage with Opal: ```terraform theme={null} assume_role_policy = < **Import items** from the **Inventory**. After you've imported roles to Opal, users can request them from the **Catalog** and you can manage them from the **Inventory**. 2312 AWS IAM roles are session-based, so your end users need to initiate their role-based session by clicking on the **Connect** button on the resource. Once a session is started, you can access the AWS Console (the Amazon GUI) directly or update your CLI with this role's permissions. # Add an RDS database Source: https://docs.opal.dev/integrations/adding-an-rds-database Add your AWS RDS databases to Opal to allow your developers to request temporary access. This guide assumes you've already [configured your AWS organization in Opal](/integrations/setting-up-your-aws-organization-in-opal). The following diagram illustrates how Opal connects to AWS RDS. Use this guide to learn how you can add RDS databases to Opal. 1920 You can add RDS databases to Opal easily using [RDS IAM database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html). ## Supported RDS engines Amazon IAM database authentication is available for the following database engines: * MySQL 8.0, minor version 8.0.16 or higher * MySQL 5.7, minor version 5.7.16 or higher * MySQL 5.6, minor version 5.6.34 or higher * PostgreSQL 14, 13, 12, and 11, all minor versions * PostgreSQL 10, minor version 10.6 or higher * PostgreSQL 9.6, minor version 9.6.11 or higher * PostgreSQL 9.5, minor version 9.5.15 or higher Don't see what you want here? Let us know by suggesting an edit or [reaching out](mailto:support@opal.dev) and we'll make it happen. ## Add an RDS database ### 1. Enable IAM authentication You'll need to enable IAM database authentication for your RDS instance. You can do this using the AWS Console and CLI. You can read more on how to do this [here in the AWS official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Enabling.html). #### AWS Console #### AWS CLI ```shell shell theme={null} aws rds modify-db-instance \ --db-instance-identifier mydbinstance \ --apply-immediately \ --enable-iam-database-authentication ``` Enabling IAM Authentication **requires a brief outage** for your RDS database. ### 2. Create database users Connect to your database and create a database user with read-only access. Here is a snippet you can use for Postgres and MySQL. ```pgsql pgsql theme={null} -- Create a group CREATE ROLE readaccess; -- Grant access to existing tables GRANT USAGE ON SCHEMA public TO readaccess; GRANT SELECT ON ALL TABLES IN SCHEMA public TO readaccess; -- Grant access to future tables ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readaccess; -- Create a user with IAM authentication CREATE USER readonly; -- NOTE: THIS USERNAME IS REQUIRED GRANT readaccess TO readonly; GRANT rds_iam TO readonly; CREATE USER fullaccess; -- NOTE: THIS USERNAME IS REQUIRED GRANT rds_superuser TO fullaccess; GRANT rds_iam TO fullaccess; ``` ```mysql mysql theme={null} -- NOTE: The usernames readonly and fullaccess are required GRANT SELECT ON *.* TO 'readonly'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; GRANT SELECT ON *.* TO 'readonly'@'%' WITH GRANT OPTION; CREATE USER 'fullaccess'@'%' IDENTIFIED WITH AwsAuthenticationPlugin AS 'RDS'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER ON *.* TO 'fullaccess'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; ``` #### Custom Database Users If you'd like to instead define your own database users with custom amounts of access, you can do that easily in one of two ways: through Opal UI, or with tags. #### Opal UI 1. In the **Inventory**, go to the **Roles** tab of the RDS instance, then select **+ Create**. 2. Add a role name, and the database username as the **Role Remote ID**, then select **Create**. #### Tags 1. Create the user with any username that you like and grant them `rds_iam` with the following queries: ```pgsql pgsql theme={null} GRANT rds_iam TO myuser; ``` ```mysql mysql theme={null} CREATE USER myuser IDENTIFIED WITH AwsAuthenticationPlugin AS 'RDS'; ``` 2. Tag the RDS database with key `opal:user:1` and value `myuser`. You can manage multiple levels of access using more tags! Example: `opal:user:2` => `myseconduser`. To see how to tag your database check out Step 3. Now the custom database users will show up in Opal as access levels. AWS supports a maximum of 50 tags on a resource. ### 3. Tag the database Finally, tag the RDS database with `opal` so Opal knows which RDS databases you'd like Opal to automatically import: #### AWS Console `opal` #### AWS CLI ```shell shell theme={null} aws rds add-tags-to-resource \ --resource-name arn:aws:rds:::db: \ --tags "Key=opal,Value=" ``` #### Terraform Add the following `tags` argument to your AWS RDS instance: ``` tags = { opal = "" opal:database-name="my_db" // This is optional. By default, Opal will pull the database name from RDS's configuration } ``` ## Access an RDS database in Opal Once you've successfully tagged an RDS database, it will appear on the Resources page in the **Catalog**. If you followed the steps above, you'll be able to **Connect** with read-only and full access to your database. Access to RDS databases in Opal is session-based, meaning your developers won't have access unless they initiate a session. You can instantly open a connection to the database using your favorite viewer, like Postico or TablePlus, or connect manually. Your credentials can only initiate connections for 15 minutes. # Adding AWS Resources to Opal Groups via AWS Tags Source: https://docs.opal.dev/integrations/adding-aws-resources-to-opal-groups-via-aws-tags Amazon Web Services tags can be used to add AWS resources to one or more Opal groups. After adding the appropriate AWS tag, the AWS resource will be added to the Opal group, via automatic or manual syncing. To identify the Opal group you want to add, you will need its Group ID: this can be found by navigating to the group of interest and inspecting the URL. For example, an Opal group with the URL `https://app.opal.dev/groups/9a8ecbac-ccc9-47a1-9d0d-1325e8db60ed#overview` has a Group ID of `9a8ecbac-ccc9-47a1-9d0d-1325e8db60ed`. The following syntax for AWS tagging is used for each type of resource. Each tag consists of a key and a value. # IAM Role * Key: `opal:group` * Value: a colon-separated list of Opal Group IDs, e.g. `9a8ecbac-ccc9-47a1-9d0d-1325e8db60ed:4d05f3dd-e2e6-488a-8d67-aab1eccbc058` # RDS Database * Key: `opal:group:ACCESS_LEVEL` (e.g `opal:group:fullaccess`). `ACCESS_LEVEL` is the name of the database user that the Opal group should have access to. This database user must have been set up as per [this guide](/integrations/adding-an-rds-database#2-create-database-users). * Value: a colon-separated list of Opal Group IDs, e.g. `9a8ecbac-ccc9-47a1-9d0d-1325e8db60ed:4d05f3dd-e2e6-488a-8d67-aab1eccbc058` # EC2 Instance * Key: `opal:group` * Value: a colon-separated list of Opal Group IDs, e.g. `9a8ecbac-ccc9-47a1-9d0d-1325e8db60ed:4d05f3dd-e2e6-488a-8d67-aab1eccbc058` # EKS Cluster * Key: `opal:group:ACCESS_LEVEL` (e.g. `opal:group:ClusterAdmin`). `ACCESS_LEVEL` is the name of the IAM role that the Opal group should have access to. This IAM role must have been set up to map to a cluster role as per [this guide](/integrations/adding-an-eks-cluster#1-create-an-iam-role). * Value: a colon-separated list of Opal Group IDs, e.g. `9a8ecbac-ccc9-47a1-9d0d-1325e8db60ed:4d05f3dd-e2e6-488a-8d67-aab1eccbc058` *** # Add Azure Databases Source: https://docs.opal.dev/integrations/adding-azure-sql-databases Add Azure databases to grant users temporary access. Opal supports granting temporary read and write access to the following Azure databases: * Azure SQL Databases * Azure SQL Managed Databases ## Prerequisites Enable Microsoft Entra ID authentication for your database. ## 1. Enable Microsoft Entra ID Authentication on SQL For SQL Databases, go to its parent SQL Server and enable Microsoft Entra ID Authentication. For SQL Managed Databases, go to its parent SQL Managed Instance and enable Microsoft Entra ID Authentication. ## 2. Add Opal IP addresses to your firewall rules On the left hand side, under **Security** > **Networking**, add the following [IP addresses](/docs/ip-ranges) to your SQL's firewall rules. If you self-host Opal, these will be different. ## 3. Add your company addresses to your firewall rules If your company uses a VPN, add any IP ranges to the firewall rules. If you don't add them, each user will have to add their unique IP address and you can easily hit Azure's 128-firewall rule limit. ## 4. Assign the managed-identity permissions 1. Go to the SQL Server or SQL Managed Instance, `Security`-> `Identity`. Under `System Managed Identity`, toggle the status to `On` and save this setting. 2. Go to `Microsoft Entra ID`, on the left hand panel, `Manage` -> `Roles & Administrators`, find the`Directory Readers` role and click on it. 3. Click `Add Assignment` and add the SQL Server or SQL Managed Instance Managed Identity. This may take an hour to sync. ## 5. Add Opal as an Admin If you have an existing admin: 1. Create a new Azure Security Group: `SQL Admins` 2. Add your existing admin to the `SQL Admins` Security Group 3. Add Opal's service principal to the `SQL Admins` security group Using Microsoft Entra Security Groups may take Azure up to 2 hours to propagate the login changes to the databases. Once the admin settings have synced, users will be able to connect through `db_datareader` and `db_datawriter` roles. # Add a BigQuery Dataset Source: https://docs.opal.dev/integrations/adding-bigquery-dataset Opal lets your developers find and request access to GCP BigQuery Datasets. Follow these steps to add GCP BigQuery Datasets to your Opal dashboard. ## 1. Go to BigQuery Access the [BigQuery browser using this link](https://console.cloud.google.com/bigquery) or find it by navigating to BigQuery in the search bar. ## 2. Label your dataset Opal only adds permissions for datasets that you label with `opal`. To do this, add a label to your bucket in the BigQuery dashboard. ## Access a BigQuery Dataset in Opal If you successfully labeled your BigQuery Dataset in GCP, the new dataset resource appears in Opal in the **Inventory** under **Resources**. # Add a Compute Engine Instance Source: https://docs.opal.dev/integrations/adding-compute-instance Opal lets your developers find and request access to SSH into GCP Compute instances. Use the following steps to add Compute instances to your Opal dashboard. ## 1. Set up OS Login on the VM Instance Follow Steps 1 and 3 in the [following guide](https://cloud.google.com/compute/docs/instances/managing-instance-access#console). By the end of it, you should have added the `enable-oslogin` key with value `TRUE` in the VM instance metadata. ## 2. Configure osLoginExternalUser for users This step is optional and only applies to Opal users who you want to grant GCP Compute instance access and who are outside of your Google Cloud organization. Read this [guide](https://cloud.google.com/compute/docs/oslogin/manage-oslogin-in-an-org#external-user) to enable `osLoginExternalUser` for a user. The user email must correspond to the Opal user's email. ## 3. Grant the compute.Viewer permission for users The Opal user who you want to grant access to needs the `compute.Viewer` permission on the Project to be able to find and SSH into the instance. This can be done via the following: First, import the GCP Project corresponding to the Compute instance in Opal. Go to the **Inventory**, then your Google Cloud App, then select on the **...** on the top right, then **Import items**. There you can import your GCP Project as a Resource. 2312 Go to the Project resource you just added and select the **User Access** tab. You can click **+Add Users** on the right to browse and select a user to add to the project with the `compute.Viewer` role. 463 ## 4. Grant the compute.OsLogin permission First, import the Compute instance into Opal. Go to the **Inventory**, then your Google Cloud App, then select on the **...** on the top right, then **Import items**. There you can filter by type **GCP Compute** and import your Compute instance as a Resource. 463 Next, go to **Resources** on the left sidebar, then to the Compute resource you just added, and select the **User Access** tab of the resource. Add the user to the Compute instance with the `compute.OsLogin` access level. If no choice to set the access level is presented, add the user without specifying the access level. ## 5. If needed, install gcloud Install the `gcloud` binary, as described in this [guide](https://cloud.google.com/sdk/docs/install). This command will be used to SSH into the instance. ## 6. Log in to gcloud The user who was granted permission must perform `gcloud auth login` in the shell, logging in with an email matching the Opal user. ## 7. SSH into the VM instance A couple minutes or so after the user was granted the `compute.OsLogin` permission, the user should be able to SSH into the instance with a command of the form: `gcloud compute ssh --project=PROJECT_ID --zone=ZONE_ID COMPUTE_INSTANCE_NAME`. For example, `gcloud compute ssh --project=my-project --zone=us-central1-a my-compute-instance`. # Add a Cloud Storage bucket Source: https://docs.opal.dev/integrations/adding-gcp-bucket Opal lets your developers find and request access to GCP Cloud Storage buckets. Follow these steps to add GCP buckets to your Opal dashboard. ## 1. Go to Cloud Storage Access the [Cloud Storage browser using this link](https://console.cloud.google.com/storage/browser) or find it by navigating to Cloud Storage in the search bar. ## 2. Label your bucket Opal only adds permissions for buckets that you label with Opal. To do this, add a label to your bucket in the Cloud Storage dashboard. ## Access a bucket in Opal If you successfully labeled your bucket in GCP, the new bucket resource appears in Opal in the **Inventory** under **Resources**. 2312 # Add a folder Source: https://docs.opal.dev/integrations/adding-gcp-folder Learn how to add a GCP folder to Opal so you can manage temporary access to them. Opal lets your developers find and request access to GCP folders. Folders let you grant access to multiple GCP projects and their underlying resources. Follow these steps to add GCP folders to your Opal dashboard. ## 1. Go to the Resource Manager Access the [resource manager using this link](https://console.cloud.google.com/cloud-resource-manager) or find it by navigating to it: IAM & Admin > Manage Resources ## 2. Prefix your folder Opal only adds permissions for folders that you prefix with `opal`. To do this, go to your folder's settings and update the name to start with `opal`. ## Access a folder in Opal If you successfully labeled your folder in GCP, it shows in the **Resources** page after the next sync. If you already have access to the folder in GCP, through Opal or otherwise, you'll see a **Connected** label in the top right corner of the resource. The permissions to this folder are granted to the GCP user that matches your Opal email address. # Add an organization Source: https://docs.opal.dev/integrations/adding-gcp-organization Read about how to add a GCP organization to Opal so you can manage temporary access to them. Opal lets your developers find and request access to GCP organizations, letting you grant organization-level roles applied to every resource contained within them. ## Add an organization To import organizations, go to your GCP App under **Inventory**, select **Import Items** in the top right, and select the **GCP Organization** resource type. A list of available organizations will appear for you to select and import. # Add a project Source: https://docs.opal.dev/integrations/adding-gcp-project Read about how to add a GCP project to Opal so you can manage temporary access to them. Opal lets your developers find and request access to GCP projects. You can currently request `viewer` and `editor` roles to projects. Use the following steps to add a project. ## 1. Go to the Resource Manager Access the [resource manager](https://console.cloud.google.com/cloud-resource-manager) or find it by navigating to it: **IAM & Admin > Manage Resources**. ## 2. Label your project Opal only adds resources for projects that you label with `opal`. To do this, add a label to your project in the Resource Manager dashboard. ## Access a project in Opal If you successfully labeled your project in GCP, the project shows on the **Resources** page after the next sync. If you already have access to the project in GCP, through Opal or otherwise, you'll see a **Connected** label in the top right corner of the resource. The permissions to this project are granted to the GCP user that matches your email address. # Add a Cloud SQL Instance Source: https://docs.opal.dev/integrations/adding-sql-instance Opal lets your developers find and request access to GCP Cloud SQL instances. Use the following steps to add SQL instances to your Opal dashboard. ## 1. Enable the Cloud SQL Admin API Opal requires the [Cloud SQL Admin API](https://console.cloud.google.com/apis/library/sqladmin.googleapis.com) to list and manage Cloud SQL instances. You must enable this API in **both**: * The project where the Opal service account lives * Every project that contains Cloud SQL instances you want to manage To enable the API: 1. Visit the [Cloud SQL Admin API page](https://console.cloud.google.com/apis/library/sqladmin.googleapis.com). 2. Use the project selector in the top navigation bar to select the correct project. 3. Click **Enable**. 4. Repeat these steps for every project that has Cloud SQL instances you want to surface in Opal. ## 2. Go to SQL in GCP Access the [SQL browser using this link](https://console.cloud.google.com/sql/instances) or find it by navigating to SQL in the search bar. ## 3. Configure your SQL instance Select your instance and click **Edit**. * Under **Customize your instance**, click **Flags**. * Click **Add Flag**. * Select cloudsql.iam\_authentication. * Click **Done**. Opal only adds resources for SQL instances that you label with `opal`. * Scroll down to **Labels **and add the opal label. * Click **Done**. * Click **Save** ## Access a SQL instance in Opal If you successfully labeled your SQL instance in GCP, the new SQL resource shows in the **Resources** tab. 2312 Access to this instance is granted to the GCP user that matches your email address. Opal [adds IAM users](https://cloud.google.com/sql/docs/mysql/add-manage-iam-users#creating-a-database-user) to the Cloud SQL instance, so users can log in, but by default accounts do **not** have any database privileges. Use the [Google guide](https://cloud.google.com/sql/docs/mysql/add-manage-iam-users#grant-db-privileges) to learn how to grant users database privileges. *** # Anthropic Platform Source: https://docs.opal.dev/integrations/anthropic Learn how to connect your Opal instance with Anthropic. Opal's integration with Anthropic allows you to manage access to Anthropic Workspaces and users. ## Supported resources | Resource | Read | Grant and revoke access | | -------------------- | ---- | ----------------------- | | Anthropic Workspaces | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version 1.1045.0, if you self-host Opal ## 1. Retrieve an API token in Anthropic To allow Opal to access to your Anthropic Workspaces, you must first create an [admin API key](https://console.anthropic.com/settings/admin-keys). Save the token, as you won't be able to retrieve it later. ## 2. Create Anthropic app in Opal In Opal, go to **Inventory** > **+App** and select **Anthropic**. Name your Anthropic app, provide a description, and enter the token you generated in step 1. After you save the app, you can import roles from **...** > **Import items**. *** # Astra Source: https://docs.opal.dev/integrations/astra Learn how to connect your Opal instance with Astra. Opal's integration with Astra allows you to manage access to DataStax Astra DB roles and users. ## Supported resources | Resource | Read | Grant and revoke access | | ----------- | ---- | ----------------------- | | Astra Roles | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version 1.1045.0, if you self-host Opal ## 1. Retrieve an API token in Astra To allow Opal to access to your Astra roles and grant permission to assign and remove roles from users, you must first create an [application token](https://docs.datastax.com/en/astra-db-serverless/administration/manage-application-tokens.html#org-token). In Astra, go to **Settings** > **Token**. Give the token the **Organization Administrator** role and a suitable description and select **Generate Token**. Save the token, as you won't be able to retrieve it later. The Organization Administrator role is necessary for Opal to assign and remove roles. A more restrictive role will restrict the types of roles that can be assigned through this token. ## 2. Create Astra app in Opal In Opal, go to **Inventory** > **+App** and select **Astra**. Name your Astra app, provide a description, and enter the token you generated in step 1. After you save the app, you can import roles from **...** > **Import items**. *** # AWS IAM Identity Center Workflows Source: https://docs.opal.dev/integrations/aws-iam-identity-center-formerly-aws-sso Manage your permission sets, users, and groups in Opal # Admin Workflows After you've created and synchronized the app, you should see that your AWS accounts are populated under the **Resources** tab. Additionally, you should see that each permission set has a resource nested under every AWS account, regardless of whether that permission set is already provisioned to some users in that account. Because this Opal resource more accurately represents the AWS-managed role that would be in that account instead of the actual permission set, the resource is referred to as an **"AWS IAM Identity Center Role"**. 2312 As an example, if a user is added to the **DatabaseAdministrator** IAM Identity Center Role under the **eng-prod** account resource, then Opal will create that user assignment in IAM Identity Center, automatically provisioning the AWS-managed role if it doesn't already exist (this will happen if the account does not already have any groups or users provisioned to it for a given permission set). This duplication across accounts is necessary for Opal to provide you greater granularity into setting request and reviewer policies on a permission set. In a highly sensitive account, you will likely have more stringent policies on the same permission set as compared to an account with less sensitive access, such as a playground environment. Opal also supports managing relationships with your **IAM Identity Center groups**. Like users, groups can also be assigned to IAM Identity Center Roles, and Opal will create the relevant assignment in AWS. Users can also be added to and removed from groups, and Opal will make the appropriate membership updates in AWS. If you use an external IdP source to provision your IAM Identity Center groups, Opal may be unable to synchronize group memberships between IAM Identity Center and the IdP. For Okta, IAM Identity Center group memberships do not automatically refresh when another party updates them. In this case, we recommend only updating user-to-group relationships in Okta and group-to-permission-set relationships in Opal's IAM Identity Center app to avoid inconsistencies. ## Scoping down sensitive access Opal automatically creates a resource for each of your permission sets under every AWS account, but you may have some permission sets with particularly sensitive permissions that you want to hide from being requested. There are a couple of ways to achieve this: 1. You can adjust the **visibility setting for the IAM Identity Center Role** to be one or many privileged groups. If you do this, then only the members in those groups can view or request this role. 2. You can adjust the **visibility setting for the parent account**, which will also apply to any permission sets nested underneath it. Note accounts can only be set to "Global" or "Admin Only" visibility. By setting the latter, it also makes all IAM Identity Center Roles under the account "Admin Only" and hide it even from users with access. # End User Workflows As with any other resource, **IAM Identity Center Roles** can be requested by users either through Opal's web UI or through Slack. **AWS Account** resources serve as containers and *cannot* be requested or assigned directly to users or groups. Once a user gains access to a role, they can click the **Connect** button to directly link to the AWS Access Portal from which they can gain console access or CLI access credentials for the desired role. # Troubleshooting *** ### Opal user is not an AWS IAM Identity Center user This means that the user in Opal does not exist as a user in AWS IAM Identity Center. Currently, Opal does not have permissions to create new IAM Identity Center users, so you must do this either in the AWS Console or your external IdP source. # Configure AWS for self-hosted Opal Source: https://docs.opal.dev/integrations/aws-integration-on-prem-setup This guide assumes you've started to [configure your AWS integration in Opal](/integrations/setting-up-your-aws-organization-in-opal#2-configure-your-aws-management-account). Use the following steps to set up the trust policy for the **OpalIngester** IAM role, then return to the integration guide. We support the following options for Opal Service Principals: * IAM Role for Service Account * EC2 Instance Role * IAM User Credentials We recommend using the IAM Role for Service Account as this is the most secure option, with permissions granted at the scope of the Opal pods only. Note: IAM Role for Service Account and EC2 Instance Role are only supported if your Kubernetes cluster is running in EKS. ## IAM Role for Service Account ### Step 1: Kubernetes service account Configure a service account on your Kubernetes cluster. This requires creating an IAM OIDC provider for your cluster, if not already existing. Please follow [these instructions](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). Let's now configure a Kubernetes service account to assume an IAM role: 1. Create a Kubernetes service account, if you don’t have one already. Copy the following contents to your device. Replace `my-namespace` with the namespace where Opal is installed. ```shell shell theme={null} cat > my-service-account.yaml < 2. Set your AWS account ID to an environment variable with the following command. ```shell shell theme={null} ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) ``` 3. Set your cluster's OIDC identity provider to an environment variable with the following command. Replace `my-cluster` with the name of your cluster. ```shell shell theme={null} OIDC_PROVIDER=$(aws eks describe-cluster --name my-cluster --region $AWS_REGION --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///") ``` 4. Set variables for the namespace and name of the service account. Replace `my-namespace` with the namespace where Opal is installed. ```shell shell theme={null} export NAMESPACE=my-namespace export SERVICE_ACCOUNT=opal-service-account ``` 5. Run the following command to create a trust policy file for the IAM role. You can add multiple entries in the StringEquals or StringLike conditions to allow multiple service accounts or namespaces to assume the role. ```json json theme={null} cat > trust-relationship.json < 6. Create the role. Replace `my-role-description` with a description for your role. ```shell shell theme={null} aws iam create-role --role-name OpalServiceAccount --assume-role-policy-document file://trust-relationship.json --description "my-role-description" ``` 7. Create a file that includes the permissions for the AWS services that are needed for the Opal pods to access. ```json json theme={null} cat >my-policy.json < 8. Create the IAM policy. ```shell shell theme={null} aws iam create-policy --policy-name my-policy --policy-document file://my-policy.json ``` 9. Attach an IAM policy to your role. Replace `my-role` with the name of your IAM role and `my-policy` with the name of an existing policy that you created. ```shell shell theme={null} aws iam attach-role-policy --role-name OpalServiceAccount --policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/my-policy ``` 10. Annotate your service account with the ARN of the IAM role that you want the service account to assume. Replace `my-role` with the name of your existing IAM role. ```shell shell theme={null} kubectl annotate serviceaccount -n $NAMESPACE $SERVICE_ACCOUNT eks.amazonaws.com/role-arn=arn:aws:iam::$ACCOUNT_ID:rol/OpalServiceAccount ``` ### Step 2: OpalIngester role trust policy Find the **OpalIngester** role and add the following to the trust policy. Replace `${IAM_ROLE_FOR_SERVICE_ACCOUNT_ARN}` with the ARN of the IAM role for service account we created. ```json json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "${IAM_ROLE_FOR_SERVICE_ACCOUNT_ARN}" }, "Action": ["sts:AssumeRole"], "Condition": { "StringEquals": { "sts:ExternalId": "${EXTERNAL_ID}" } } }, { "Effect": "Allow", "Principal": { "AWS": "${IAM_ROLE_FOR_SERVICE_ACCOUNT_ARN}" }, "Action": ["sts:TagSession"] } ] } ``` ### Step 3: Enable service account Let’s now configure Opal pods to use a Kubernetes service account. * Open the Opal Admin console. * Click “Config”. * Check “AWS integration - Enable IAM role for service account as Opal Service Principal" * Enter the service account name. * Click “Save”. * Deploy the config change. ### Step 4: Continue the setup Head over to the [main page for next steps](/integrations/setting-up-your-aws-organization-in-opal#2-configure-your-aws-management-account). ## EC2 Instance Role ### Step 1: Instance role policy You’ll need to update the permissions of your EC2 instance role to enable this role to take the appropriate actions on your behalf. Go into IAM service in the AWS console and add the following policy to your EC2 instance role. Make sure to substitute `${YOUR_ACCOUNT_NUMBER}` with the account number of your AWS management account. ```json json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sts:AssumeRole", "sts:TagSession"], "Resource": "arn:aws:iam::*:role/OpalIngester" }, { "Effect": "Allow", "Action": ["account:ListRegions"], "Resource": "arn:aws:account::${YOUR_ACCOUNT_NUMBER}:account" } ] } ``` The `account:ListRegions` action enables Opal to ensure sessions are created for the enabled regions on your AWS accounts. ### Step 2: OpalIngester role trust policy Find the **OpalIngester** role in IAM and set the following trust policy. Make sure to substitute `${EC2_INSTANCE_ROLE_ARN}` with the ARN of your EC2 instance role. ```json json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "${EC2_INSTANCE_ROLE_ARN}" }, "Action": ["sts:AssumeRole"], "Condition": { "StringEquals": { "sts:ExternalId": "${EXTERNAL_ID}" } } }, { "Effect": "Allow", "Principal": { "AWS": "${EC2_INSTANCE_ROLE_ARN}" }, "Action": ["sts:TagSession"] } ] } ``` ### Step 3: Continue the setup Head over to the [main page for next steps](/integrations/setting-up-your-aws-organization-in-opal#step-1-configure-your-aws-management-account). ## IAM User Credentials ### Step 1: Create IAM User * Head over to IAM in the AWS console and create a new user. * Generate an access key / secret access key pair for the user. These will be needed in the Opal form. * Attach the following policy to the IAM user: ```json json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["sts:AssumeRole", "sts:TagSession"], "Resource": "arn:aws:iam::*:role/OpalIngester" } ] } ``` ### Step 2: OpalIngester role trust policy * Update the trust policy on the **OpalIngester** role with the following. Please substitute `${IAM_USER_ARN}` with the ARN of that IAM User. ```json json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "${IAM_USER_ARN}" }, "Action": ["sts:AssumeRole"], "Condition": { "StringEquals": { "sts:ExternalId": "${EXTERNAL_ID}" } } }, { "Effect": "Allow", "Principal": { "AWS": "${IAM_USER_ARN}" }, "Action": ["sts:TagSession"] } ] } ``` ### Step 3: Continue the setup Head over to the [main page for next steps](/integrations/setting-up-your-aws-organization-in-opal#2-configure-your-aws-management-account). # AWS Real Time Sync Setup Source: https://docs.opal.dev/integrations/aws-real-time-sync With **Opal Real Time Sync**, admins can see access changes to an application's Resources and Groups in near real-time and on an event-driven basis. Instead of waiting for Opal's scheduled syncs, each time an access change event occurs in the remote system (e.g. User added to a Group), Opal automatically syncs and reflects that change. For our AWS integration, you can expect to see changes in Opal as quickly as 3 minutes once you set up this feature. ## Prerequisites * A cross-organizational CloudTrail that stores events in an S3 bucket. * AWS Administrator access ## Deployment types ### S3 bucket notifications This method relies on "all object create event" notifications sent from your S3 logging bucket. Note that only one topic can be set up to receive these notifications per bucket. If you are already using "all object create event" notifications from your S3 buckets you must either use CloudTrail notifications instead (see below), or use the existing SNS topic. ### CloudTrail notifications CloudTrail notifications rely on notifications sent directly from your CloudTrail. Unlike S3 bucket notifications, multiple topics can subscribe these notifications. ## AWS Setup We provide Terraform and CloudFormation templates for easy setup. ### CloudFormation 1. Download the [CloudFormation template file](https://downloads.opal.dev/eds-templates/aws/cloudformation/cf-v1.json). 2. In the AWS Console, navigate to [CloudFormation > Stacks](https://console.aws.amazon.com/cloudformation/home/stacks). 3. Navigate to **Create Stack > With new resources** 4. Under **Specify template**, choose **Upload a template file** and upload the template from above. 5. Continue under **S3 Notifications** or **CloudTrail notifications** below depending on your choice of deployment type. 6. Fill out the form's fields depending on whether you prefer to use S3 notifications or CloudTrail notifications: 1. **S3 notifications:** 1. **NotificationType**: Select "**s3**" 2. **OpalIngesterRole**: Enter your OpalIngester role ARN 3. **CloudTrailBucketArn**: Enter the ARN of your S3 bucket that stores CloudTrail logs. 4. **ExistingTopicArn**: If using an existing SNS topic, enter its ARN here, otherwise leave this empty. Ensure that your topic is already preconfigured to receive "All object create event" notifications from the S3 bucket. 5. **CloudTrailArn**: Leave empty. 2. **CloudTrail notifications:** 1. **NotificationType**: Select "**cloudtrail**" 2. **OpalIngesterRole**: Enter your OpalIngester role ARN 3. **CloudTrailBucketArn**: Enter the ARN of your S3 bucket that stores CloudTrail logs. 4. **ExistingTopicArn**: If using an existing SNS topic, enter its ARN here, otherwise leave this empty. Ensure that your topic is already preconfigured to receive "All object create event" notifications from the S3 bucket. 5. **CloudTrailArn**: Enter the ARN of your cross-organizational CloudTrail. 7. Click **Next** 8. Tick the **I acknowledge that AWS CloudFormation might create IAM resources** checkbox at the bottom of the page and click **Next** 9. Review your configuration and click **Submit** once you are ready to spin up the resources. 10. (**CloudTrail notifications ONLY**): 1. In the AWS console, navigate to **CloudTrail > Trails**. Open your cross-organizational trail. Click **Edit**. 2. Under **SNS Notification Delivery**, check **Enabled**. 3. Under **SNS Topic**, select **Existing**, and choose the newly created SNS topic. ### Terraform Follow the instructions below for S3 or CloudTrail notifications depending on your preference. #### S3 Notifications 1. Create a new `.tf` file and paste the following: ```json json theme={null} provider "aws" {} module "opal_aws_events" { source = "https://downloads.opal.dev/eds-templates/aws/terraform/tf-v1.zip" notification_type = "s3" opal_ingester_role_arn = "arn:aws:iam::123456789012:role/OpalIngester" cloudtrail_bucket_arn = "arn:aws:s3:::aws-cloudtrail-logs-123456789012-a2531774" existing_sns_topic_arn = "" // optional for s3 notifications cloudtrail_arn = "" // not required for s3 notifications } ``` 2. Replace `opal_ingester_role_arn` and `cloudtrail_bucket_arn`with your OpalIngester role ARN and your CloudTrail S3 bucket respectively. 3. If using an existing SNS topic, ensure that it is already preconfigured to receive "All object create event" notifications. Enter your topic's ARN on the `existing_sns_topic_arn` field. 4. Save the file and deploy the Terraform schema. #### CloudTrail Notifications 1. Create a new `.tf` file and paste the following: ```json json theme={null} provider "aws" {} module "opal_aws_events" { source = "https://downloads.opal.dev/eds-templates/aws/terraform/tf-v1.zip" notification_type = "cloudtrail" opal_ingester_role_arn = "arn:aws:iam::123456789012:role/OpalIngester" cloudtrail_bucket_arn = "arn:aws:s3:::aws-cloudtrail-logs-123456789012-a2531774" existing_sns_topic_arn = "" // not required for cloudtrail notifications cloudtrail_arn = "arn:aws:cloudtrail:us-east-2:123456789012:trail/management-events" } ``` 2. Replace `opal_ingester_role_arn`, `cloudtrail_bucket_arn`, and `cloudtrail_arn` with your OpalIngester role ARN, your CloudTrail S3 bucket ARN, and your CloudTrail ARN respectively. 3. Save the file and deploy the Terraform schema. 4. In the AWS console, navigate to **CloudTrail > Trails**. Open your cross-organizational trail. Click **Edit**. 5. Under **SNS Notification Delivery**, check **Enabled**. 6. Under **SNS Topic**, select **Existing**, and choose the newly created SNS topic. ## Opal setup The AWS setup above should have yielded a new SQS queue with a name formatted like `opal-cloudtrail-queue-1234abcd`. Save its URL. In Opal, go to the **Setup** tab in your AWS connector. Paste the SQS Queue URL into the **CloudTrail Events SQS Queue URL** field. Click Save. You should now be all set up. # Azure Source: https://docs.opal.dev/integrations/azure Learn how to connect Opal to Azure to manage access. With Opal's Azure integration: * Users can **request time-bounded access** to [supported Azure resources](#supported-resources) * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to Azure resources * Admins can **add resources from other Opal integrations** to an Azure AD group so the group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * Access changes are tracked as [events](/docs/event-types) that can be logged to a Slack channel or exported to your favorite tools ## Supported resources | Resource | Read | Grant and revoke access | Connect Opal user sessions to resource | Available in Risk Center | | ------------------------------------------------------------------- | ---- | ----------------------- | -------------------------------------- | ------------------------ | | Azure Users (as IDP) | ✔️ | ✔️ | | ✔️ | | Azure Enterprise Applications | ✔️ | ✔️ | | ✔️ | | Azure Management Groups | ✔️ | ✔️ | | ✔️ | | Azure Resource Groups | ✔️ | ✔️ | | ✔️ | | Azure SQL Managed Databases and Instances | ✔️ | ✔️ | | ✔️ | | Azure User-Assigned Managed Identities | ✔️ | ✔️\* | | ✔️ | | Azure SQL Servers | ✔️ | ✔️ | ✔️ | ✔️ | | Azure Storage Accounts and Containers | ✔️ | ✔️ | | ✔️ | | Azure AD groups, including Security Groups and Microsoft 365 Groups | ✔️ | ✔️ | | ✔️ | | Azure Virtual Machines | ✔️ | ✔️ | | ✔️ | | Azure Subscriptions | ✔️ | ✔️ | | ✔️ | | Microsoft 365 Groups | ✔️ | ✔️ | | ✔️ | | Microsoft Entra ID Roles | ✔️ | ✔️ | | ✔️ | | Microsoft Entra ID Security Groups | ✔️ | ✔️ | | ✔️ | \*You can grant Azure User-Assigned Manage Identities access to resources and groups. ## Requirements * You must be an Azure and Opal admin. * Opal associates Azure AD users to Opal users through their primary email address in Azure AD. ## Set up Azure app registration ### 1. Create app registration In your [Azure portal](https://portal.azure.com), go to **Azure Active Directory** > **App registrations** > **New Registration**. Use the following settings. | Field | Value | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | `Opal` | | Supported account types | Choose the option that fits your needs. Typically, you can use the default value. | | Redirect URI | If you use a Cloud Opal instance, use `https://app.opal.dev/apps/create/azure_ad/callback`. If you use self-hosted Opal, `https\:///apps/create/azure_ad/callback`. | | Type | Web | Once the app registration is created, save the **Application (client) ID** and **Directory (tenant) ID** on the Overview page. You'll use these values in the following step. ### 2. Generate a client secret On the app registration page, go to **Certificates & Secrets** > **New client secret**. Choose a name and expiration. Note that you need to create a new client secret at the expiration interval for your Azure AD connection to continue working. The maximum allowed by Microsoft is 2 years. Save the secret value, which you'll use in the following step. You cannot fetch the secret value after leaving this page. ### 3. Add permissions In the sidebar, go to `API Permissions` and select `Add a permission`. Choose `Microsoft Graph` -> `**Application Permissions**` and add the following permissions: * `Directory.ReadWrite.All` * `CustomSecAttributeAssignment.ReadWrite.All` * `RoleManagement.ReadWrite.Directory` These permissions allow Opal to manage the user membership in groups on your behalf as well as import user attributes as your IDP. ### 4. Create and assign Opal Service Role To manage access to Azure Subscriptions and Resource Groups, follow the instructions [here](/integrations/azure-infrastructure-setup) before continuing. Note that this step can be skipped if you only want to manage Azure AD groups with Opal. ## Set up Opal connection ### 1. Create the Azure connection In the Opal dashboard, navigate to Apps, click on the + icon, and find the Azure App. Fill out the details for the integration, using the secret from the previous step. The Tenant ID and Client ID are available from the Azure AD app registration page. ### 2. Click the Authorize & Create button This step will open a pop-up to authorize your newly created app registration with Azure AD. Once the permissions have been accepted, the connection will be created. From here, the connection is complete. ### Run app validation checks After you save your app, you can view existing sync issues from the **Setup** tab on the app detail page. Missing permissions and sync issues show in the **App Validations** section. Select the refresh icon to rerun validation checks. You can hover over the validation icons to learn why Opal needs a given permission. To correctly sync your app to Opal, ensure you address any sync errors, marked with the red ! icon. Inspect warnings on a case-by-case basis: warnings might impact features you’re not using and may be safely ignored, but this depends on your use case. # Azure Entra IDP/HRIS Integration Source: https://docs.opal.dev/integrations/azure-entra-idphris-integration Learn how to configure Azure Entra as an IDP/HRIS Integration. If your organization uses Azure Entra as an Identity Provider, you can additionally designate it as an [IDP/HRIS Integration](/docs/add-your-first-idphr-provider). This allows Opal to sync your Azure Entra identities and their attributes, on top of syncing and managing access to entitlements (e.g. Azure Entra Security Groups, Azure VMs, Azure DBs, etc). ## Getting started Before you set up Azure Entra as your IDP, you must first [create an Azure Entra App in Opal](/integrations/azure). Next, set up Azure Entra as your IDP using the following instructions: * [Add Your First IDP/HR Provider](/docs/add-your-first-idphr-provider) * [Add Additional IDP/HR Providers](/docs/add-additional-idphr-providers) ## Custom attributes **Note:** Opal only supports `string` type Custom Security Attributes. 1. Opal's Azure app must have the `CustomSecAttributeAssignment.ReadWrite.All` application permission assigned. * Go to `App Registrations`. * In the sidebar, go to `API Permissions` and select `Add a permission`. Choose `Microsoft Graph` > **Application Permissions** and add `CustomSecAttributeAssignment.ReadWrite.All` 2. Opal tags should have the format `.`. ex. `Student.IsFallIntern` in order to properly match the Azure attributes. These are case-sensitive. # Azure Infrastructure Setup Source: https://docs.opal.dev/integrations/azure-infrastructure-setup If you have an existing Azure app in Opal that has not been configured to manage infrastructure resources, you can enable infrastructure management under the app's **Setup** tab in Opal. To manage access to Azure Subscriptions and Resource Groups, you must grant additional permissions to the Opal application. ## Requirements You must have admin access to the Azure root management group. ## 1. Create Opal Service Role 1. In the Azure Portal, navigate to **Tenant Root Management Group** -> **Access Control (IAM)** -> **Add** -> **Add custom role**. 2. Go to **JSON** > **Edit**. Replace the default definition with the following snippet in the **Definition** tab, substituting in your management group ID. **Note**: The `roleName` must be `Opal Service Role`. Use the **Explanation** tab to see why permissions are required, and the **Definition** tab for a valid definition to copy and paste. ```json Explanation theme={null} // NOTE: This snippet documents why Opal requires each of these permissions, // allowing you to customize based on your needs. For a valid pasteable policy // document, use the "Definition" tab. { "properties": { "roleName": "Opal Service Role", "description": "Contains the necessary permissions for Opal to provision access", "assignableScopes": [ "/providers/Microsoft.Management/managementGroups/" ], "permissions": [ { "actions": [ // Required. Used to import management groups and subscriptions. "Microsoft.Management/getEntities/action", // Required. Used to import management groups. "Microsoft.Management/managementGroups/read", // Required. Used to import resource groups. "Microsoft.Resources/subscriptions/resourceGroups/read", // Required. Used to import resources under resource groups. "Microsoft.Resources/subscriptions/resourcegroups/resources/read", // Required. Used to view IAM access to Azure resources. "Microsoft.Authorization/permissions/read", // Required. Used to view IAM access to Azure resources. "Microsoft.Authorization/roleAssignments/read", // Required. Used to push access to Azure resources. "Microsoft.Authorization/roleAssignments/write", // Required. Used to remove access to Azure resources. "Microsoft.Authorization/roleAssignments/delete", // Optional. Used to import user-assigned identities. "Microsoft.ManagedIdentity/userAssignedIdentities/read", // Optional. Used to import virtual machines. "Microsoft.ClassicCompute/virtualMachines/read", // Optional. Used to import storage accounts. "Microsoft.Storage/storageAccounts/read", // Optional. Used to import SQL servers. "Microsoft.SQL/servers/read", // Optional. Used to import SQL managed databases. "Microsoft.Sql/managedInstances/databases/read", // Optional. Used to import SQL databases. "Microsoft.Sql/servers/databases/read", // Optional. Used to import storage containers. "Microsoft.Storage/storageAccounts/blobServices/containers/read" ], "notActions": [], "dataActions": [], "notDataActions": [] } ] } } ``` ```Text Definition theme={null} { "properties": { "roleName": "Opal Service Role", "description": "Contains the necessary permissions for Opal to provision access", "assignableScopes": [ "/providers/Microsoft.Management/managementGroups/" ], "permissions": [ { "actions": [ "Microsoft.Management/getEntities/action", "Microsoft.Management/managementGroups/read", "Microsoft.Resources/subscriptions/resourceGroups/read", "Microsoft.Resources/subscriptions/resourcegroups/resources/read", "Microsoft.Authorization/permissions/read", "Microsoft.Authorization/roleAssignments/read", "Microsoft.Authorization/roleAssignments/write", "Microsoft.Authorization/roleAssignments/delete", "Microsoft.ManagedIdentity/userAssignedIdentities/read", "Microsoft.ClassicCompute/virtualMachines/read", "Microsoft.Storage/storageAccounts/read", "Microsoft.SQL/servers/read", "Microsoft.Sql/managedInstances/databases/read", "Microsoft.Sql/servers/databases/read", "Microsoft.Storage/storageAccounts/blobServices/containers/read" ], "notActions": [], "dataActions": [], "notDataActions": [] } ] } } ``` 3. Click **Next**, and then **Create** to create the role. ## 2. Create Role Assignment 1. In the Azure portal, navigate to **Tenant Root Management Group** -> **Access control (IAM)** -> **Add role assignment**. 1. Under Role, select the Opal Service Role (found under "Privileged administrator roles"). 2. Select the **Members** tab. Add the Opal application as a member. 3. Select the **Conditions** tab -> **Select roles and principals**. 5. Select **Open advanced condition editor**. Toggle **Editor type** from "Visual" to "Code". 6. Paste in the following code, substituting in your Opal app's Object ID, and save. This condition prevents the Opal application from having the ability to escalate its own access by assigning roles to itself. ```json json theme={null} ( ( !(ActionMatches{'Microsoft.Authorization/roleAssignments/write'}) ) OR ( @Request[Microsoft.Authorization/roleAssignments:PrincipalId] GuidNotEquals ) ) ``` 7. Go to **Review + assign**. Complete assigning the role by clicking **Review + assign**. ## 3. Allow sessions for SQL Databases \[Optional] Follow instructions [to add Azure Databases](/integrations/adding-azure-sql-databases) if you want to enable Opal to manage SQL Database logins. # Azure Real Time Sync Setup Source: https://docs.opal.dev/integrations/azure-real-time-sync With **Opal Real Time Sync**, admins can see access changes to an application's Resources and Groups in near real-time and on an event-driven basis. Instead of waiting for Opal's scheduled syncs, each time an access change event occurs in the remote system (e.g. User added to a Group), Opal automatically syncs and reflect that change. If you use Opal's **Azure (Entra) Integration**, you can expect to see changes in Opal as quickly as 3 minutes and no later than 10 minutes once you set up this feature. ## Architecture diagram ## Requirements * The Azure CLI configured. If you have not already, follow the instructions below. * Follow the instructions [here](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)to install the Azure CLI * Follow the instructions [here](https://learn.microsoft.com/en-us/cli/azure/authenticate-azure-cli) to log in to the Azure CLI * An Azure Subscription * The following providers must be registered in the Azure Subscription. If they are not registered, do so using: `az provider register --namespace --subscription ` * `Microsoft.Insights` * `Microsoft.EventHub` * Administrative access to an Azure Subscription, specifically the permissions listed below. Required Permissions * `Microsoft.EventHub/checkNameAvailability/action` * `Microsoft.EventHub/register/action` * `Microsoft.EventHub/namespaces/write` * `Microsoft.EventHub/namespaces/read` * `Microsoft.EventHub/namespaces/authorizationRules/read` * `Microsoft.EventHub/namespaces/authorizationRules/write` * `Microsoft.EventHub/namespaces/eventhubs/write` * `Microsoft.EventHub/namespaces/eventhubs/read` * `Microsoft.EventHub/namespaces/eventhubs/authorizationRules/read` * `Microsoft.EventHub/namespaces/eventhubs/authorizationRules/write` The [Azure Event Hubs Data Owner](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/analytics#azure-event-hubs-data-owner) role will provide these permissions. ## Using Terraform ### 1. Initialize the Azure EDS Terraform Module We provide a Terraform module that configures most of the required resources for you. To get started, copy the following Terraform snippet. ```Text terr theme={null} terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = "=4.8.0" } } } provider "azurerm" { features {} } module "opal_azure_eds" { source = "https://downloads.opal.dev/eds-templates/azure/terraform/tf-v1.zip" root_management_group_name = "ExampleRootManagementGroup" resource_group_name = "ExampleResourceGroup" subscription_id = "00000000-0000-0000-0000-000000000000" } # Outputs configuration information necessary to perform the rest of setup output "opal_eventhub_id" { value = module.opal_azure_eds.opal_eventhub_id } output "opal_eventhub_name" { value = module.opal_azure_eds.opal_eventhub_name } output "opal_eventhub_namespace_id" { value = module.opal_azure_eds.opal_eventhub_namespace_id } output "opal_eventhub_authz_rule_id" { value = module.opal_azure_eds.opal_eventhub_authz_rule_id } output "opal_entra_diagnostic_setting_id" { value = module.opal_azure_eds.opal_entra_diagnostic_setting_id } ``` ### 2. Create a diagnostic setting at the root management group For this step, we will need to use the Azure REST API, as this functionality is not available anywhere else. You can trigger this using any REST client you prefer. An authorization token is required, which you can retrieve from the Azure CLI using the following command: ```Text bash theme={null} az account get-access-token --query accessToken --output tsv ``` To create the diagnostic setting, fill in the following fields in the command below. * ``: The root management group for your Azure directory * ``: Any name * ``: The subscription under which you created your event hub namespace * ``: The resource group under which you created your event hub namespace * ``: The name of your event hub namespace * ``: The name of the authorization rule configured above * ``: The name of the event hub configured above ``` token=$(az account get-access-token --query accessToken --output tsv) curl -X PUT -H "Authorization: Bearer $token" -H 'Content-Type: application/json' \ https://management.azure.com/providers/microsoft.management/managementGroups//providers/microsoft.insights/diagnosticSettings/\?api-version\=2020-01-01-preview \ -d '{"properties":{"eventHubAuthorizationRuleId":"/subscriptions//resourceGroups//providers/Microsoft.EventHub/namespaces//authorizationrules/","eventHubName":"","logs":[{"category":"Administrative", "enabled":true }, {}]}}' ``` This configures a [diagnostic setting](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings) to export any [Administrative](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema?source=recommendations#administrative-category) logs from the root management group, and any children, to your event hub. ### 3. Allow the Opal App Registration to read from your Event Hub Now we need to allow the Opal Service to read from your Azure Event Hub. Fill in the following command: * ``: The object ID of the App Registration you use for Opal. * ``: The ID of the created event hub that was output from your terraform apply ``` az role assignment create \ --assignee \ --role "Azure Event Hubs Data Receiver" \ --scope ``` ### 4. Connect Opal to your Event Hub Go to your Azure app in Opal, select **Setup**, and click **Edit** to configure your event hub: * Event Hub Namespace: This must be the **fully-qualified name** of your event hub namespace. It will be of the form `.servicebus.windows.net` * Event Hub Name: Your event hub's **name**, as returned from the terraform apply. ## Using Azure Portal ### 1. Create an Azure Event Hub Namespace Go to [Event Hubs](https://portal.azure.com/#browse/Microsoft.EventHub%2Fnamespaces) and click **+Create**. Configure the following properties on the first page: * Subscription: The subscription your event hub namespace is only affects where it is billed. * Resource Group * Namespace name * Location * Pricing Tier: Note that this affects the length of the data retention periods that you are eligible for * [Throughput Units](https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-scalability#throughput-units): 1 TU is recommended Under the following steps, you can configure any other setup required by your use-case. No further configuration for the event hub namespace is required for integration with Opal. Click "Review + create" Once created, your event hub will take a few minutes to deploy. ### 2. Create an Event Hub Once your event hub namespace is deployed, go to it in the Azure UI and click **+ Event Hub**. On the first page, you will be prompted to configure the following: * Name * Partition Count: We recommend setting the partition count to **1** * Cleanup Policy: Controls what happens when events reach their retention limit. We recommend using **Delete** * Retention Time: The maximum retention period available to you will differ based on the pricing tier you chose in step 1. We recommend using the longest retention period possible. On the **Capture** tab, if you have a premium-tier namespace, you can enable capturing the data that is streamed by your event hub in Azure Data Lake or Azure Blob Storage. We recommend you keep this off. Once complete, click **Review + Create**, confirm your configuration and create your event hub. ### 3. Set up authorization rules Authorization rules allow Azure to push audit and administrative logs to your event hub, [see here](https://learn.microsoft.com/en-us/azure/event-hubs/authorize-access-shared-access-signature) for more details. Fill in the following fields in the command below to create an authorization rule on your namespace * ``: Can be any name * ``: Name of the event hub namespace configured above * ``: Resource group under which the event hub namespace was created ```text bash theme={null} az eventhubs namespace authorization-rule create \ --name \ --namespace-name \ --resource-group \ --rights Manage Send Listen ``` ### 4. Create a diagnostic setting at the root management group For this step, we will need to use the Azure REST API. You can trigger this via any REST client you would like. An authorization token is required, which you can retrieve from the Azure CLI using the following command: ```Text bash theme={null} az account get-access-token --query accessToken --output tsv ``` To create the diagnostic setting, fill in the following fields in the command below to create a diagnostic setting * ``: The root management group for your Azure directory * ``: Any name * ``: The subscription under which you created your event hub namespace * ``: The resource group under which you created your event hub namespace * ``: The name of your event hub namespace * ``: The name of the authorization rule configured above * ``: The name of the event hub configured above ``` token=$(az account get-access-token --query accessToken --output tsv) curl -X PUT -H "Authorization: Bearer $token" -H 'Content-Type: application/json' \ https://management.azure.com/providers/microsoft.management/managementGroups//providers/microsoft.insights/diagnosticSettings/\?api-version\=2020-01-01-preview \ -d '{"properties":{"eventHubAuthorizationRuleId":"/subscriptions//resourceGroups//providers/Microsoft.EventHub/namespaces//authorizationrules/","eventHubName":"","logs":[{"category":"Administrative", "enabled":true }, {}]}}' ``` This configures a [diagnostic setting](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings) to export any [Administrative](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema?source=recommendations#administrative-category) logs from the root management group, and any children, to your event hub. ### 5. Enable Microsoft Entra Logging Go to [Microsoft Entra](https://entra.microsoft.com/#home) and click **Show More** on the left hand bar. Expand **Monitoring & health** and select **Diagnostic settings**. Select **Add diagnostic setting**. You will be prompted to configure the following: * Diagnostic setting name * Log Categories: Select **AuditLogs** * Destination details: Select **Stream to an event hub** * Select the subscription, event hub namespace, event hub and authorization rule created above Once complete, click **Save**. ### 6. Allow the Opal App Registration to read from your Event Hub Now we need to allow the Opal Service to read from your Azure Event Hub. Fill in the following command: * ``: The object ID of the App Registration you use for Opal. * ``: The ID of the created event hub ``` az role assignment create \ --assignee \ --role "Azure Event Hubs Data Receiver" \ --scope ``` ### 7. Connect Opal to your Event Hub Go to your Azure app in Opal, select **Setup** and click **Edit** to configure your event hub: * Event Hub Namespace: This must be the **fully-qualified** name of your event hub namespace. It will be of the form `.servicebus.windows.net` * Event Hub Name: Your event hub's name # Clickhouse Source: https://docs.opal.dev/integrations/clickhouse Learn how to connect Opal to Clickhouse to manage access. **User matching**: Opal matches ClickHouse users to Opal accounts by email. If the user's email in Clickhouse does not match the user's email in Opal, the account will appear as unlinked and can be manually associated through Clickhouse. With Opal's ClickHouse integration: * **Users** can request access to ClickHouse roles, databases, and tables directly from Opal, with time-limited grants that expire automatically * **Admins** can import ClickHouse resources into Opal's catalog, configure reviewers, and see a full audit trail of who has access to what * **Admins** can revoke access manually at any time — the change is applied immediately in ClickHouse ## Supported resources | **Resource** | **Read** | **Grant and revoke access** | | ------------------- | -------- | --------------------------- | | ClickHouse Database | ✔️ | ✔️ | | ClickHouse Table | ✔️ | ✔️ | | ClickHouse Role | ✔️ | ✔️ | | ClickHouse User | ✔️ | | ## Requirements To connect ClickHouse to Opal, you must first: * Be an **Opal Admin** * Have access to a running ClickHouse instance in ClickHouse Cloud * Have credentials for a [ClickHouse admin](https://clickhouse.com/docs/cloud/security/common-access-management-queries#admin-user) user able to create new users and grant permissions ## 1. Create a service account in ClickHouse Opal requires a dedicated service account in ClickHouse with read access to system tables and permission to manage grants. In your ClickHouse SQL Console (or via `curl`), run: ```sql theme={null} -- Create the service user CREATE USER IF NOT EXISTS opal_service IDENTIFIED WITH sha256_password BY ''; -- Grant read access to system tables GRANT SELECT ON system.users TO opal_service; GRANT SELECT ON system.roles TO opal_service; GRANT SELECT ON system.role_grants TO opal_service; GRANT SELECT ON system.grants TO opal_service; GRANT SELECT ON system.databases TO opal_service; GRANT SELECT ON system.tables TO opal_service; -- Grant access management permissions GRANT CURRENT GRANTS(ACCESS MANAGEMENT ON *.*) TO opal_service; ``` You must use `GRANT CURRENT GRANTS(ACCESS MANAGEMENT ON *.*)` instead of `GRANT ACCESS MANAGEMENT`. This is because the Cloud `default` user doesn't hold the full `ACCESS MANAGEMENT` bundle, so `CURRENT GRANTS` passes only the subset needed by Opal. ## 2. Add Clickhouse to Opal In Opal, go to **Inventory > + App**, then select **ClickHouse**. Fill in the connection form using the following details. | Field | Where to find it | Example | | ------------ | -------------------------------------------------- | --------------------------------------- | | **Hostname** | ClickHouse Cloud → your service → Connect tab | `abc123.us-east-1.aws.clickhouse.cloud` | | **Port** | Always `8443` on ClickHouse Cloud (HTTPS) | `8443` | | **Use TLS** | Always enabled on ClickHouse Cloud | - | | **Username** | The service account created in step 1 | `opal_service` | | **Password** | The password set for the service account in step 1 | | Upon clicking **Create**, Opal will validate the connection by running a test query. If validation fails, verify that the hostname, port, and credentials are correct and that your IP is allowed in ClickHouse Cloud's network settings. ## 3. Import resources After the connection is created, Opal will run an initial sync to discover your ClickHouse databases, tables, roles, and users. Go to **Assets** to see everything that was discovered. To make a resource or role requestable, import them and configure reviewers and access policies as needed. Only imported resources with “Allow Requests” toggled ON will appear in Opal's **Request Access** catalog. # Configure additional AWS accounts to manage in Opal Source: https://docs.opal.dev/integrations/configure-additional-aws-accounts-to-manage-in-opal Learn how to configure additional AWS accounts so you can manage and review access in Opal. If you haven't configured your AWS management account in Opal, get started with our [AWS integration guide](/integrations/setting-up-your-aws-organization-in-opal) before configuring additional accounts. For each additional AWS account—IAM role, RDS database, etc.—that you want Opal to manage, you must add an Identity Provider and 2 IAM Roles with different permission scopes. Use this guide to configure each additional AWS account to manage in Opal, then see steps 4-6 in our [AWS app integration guide](/integrations/setting-up-your-aws-organization-in-opal#4-configure-opal-app) to complete your AWS app integration. Perform steps 1-3 for each account you want Opal to manage. ## 1. Create Ingester Role Note: This step can be skipped for your management account, as you configured its ingester role in the previous step. The ingester role allows Opal to read your configuration and populate it within Opal. Create a role called **OpalIngester**, with the same trust policy as above: ```json Opal-hosted theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::602387580983:user/OpalIngester" }, "Action": [ "sts:AssumeRole" ], "Condition": { "StringEquals": { "sts:ExternalId": "${EXTERNAL_ID}" } } }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::602387580983:user/OpalIngester" }, "Action": [ "sts:TagSession" ] } ] } ``` Then, attach the following permissions policy to it. Use the **Explanation** tab to view which permissions you can customize and the **Policy** tabs for a version you can copy and paste. ```json Explanation theme={null} // IMPORTANT: This snippet is meant for documenting why Opal requires each of // these permissions, allowing you to customize based on your needs. For a valid // pasteable policy document, open the "Policy" tab above. { "Version": "2012-10-17", "Statement": [ { // Required to import IAM Roles, EC2 instances, EKS clusters and RDS databases into Opal. "Sid": "OpalRequiredToManageAccount", "Effect": "Allow", "Action": [ "iam:ListRoleTags", "iam:ListRoles", "iam:GetRolePolicy", "iam:GetPolicy", "iam:GetRole", "rds:DescribeDBInstances", "rds:DescribeDBClusters", "ec2:DescribeInstances", "eks:DescribeCluster", "eks:ListClusters", // Required to determine which AWS regions are enabled in this account. // Without this permission, Opal falls back to scanning all known AWS regions, // which may include regions not reachable from your network and can cause sync errors. "account:ListRegions" ], "Resource": "\*" } ] } ``` ```json Policy theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "OpalRequiredToManageAccount", "Effect": "Allow", "Action": [ "iam:ListRoleTags", "iam:ListRoles", "iam:GetRolePolicy", "iam:GetPolicy", "iam:GetRole", "rds:DescribeDBInstances", "rds:DescribeDBClusters", "ec2:DescribeInstances", "eks:DescribeCluster", "eks:ListClusters", "account:ListRegions" ], "Resource": "*" } ] } ``` ## 2. Register Identity Provider Add your OIDC identity provider to your AWS account—you can search for **Identity provider** in the AWS IAM console, then select **Add provider**. See the [AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html#manage-oidc-provider-console) for more detail or alternative methods. Set the following fields in AWS. | Field | Value | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Provider type | OpenID Connect | | Provider URL | The issuer URL from your OIDC provider. This should be the same value as the Issuer URL in **AWS Settings > OIDC Provider Settings** in Opal. For example, `https://companyname.oktapreview.com`. | | Audience | The Client ID from your OIDC provider. This should be the same value as the Client ID in **AWS Settings > OIDC Provider Settings** in Opal. | ## 3. Create User Role The User role is used to grant your authenticated users access to the resources that have been allocated to them. Create a role called **OpalUser** with the following trust policy, substituting in your management account ID, OIDC issuer URL, and the Client ID used for your OIDC configuration: * `${ACCOUNT_ID}`: The account ID of the account being configured * `${IDP_ISSUER_URL}`: The OIDC issuer URL * `${OPAL_CLIENT_ID}`: The Client ID you used in Step 3b ```json json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${IDP_ISSUER_URL}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${IDP_ISSUER_URL}:aud": "${OPAL_CLIENT_ID}" } } } ] } ``` This trust policy ensures that only users authenticated with your identity provider can be granted access to the role. Next, add the following permissions policy to the role. Use the **Explanation** tab to view which permissions you can customize and the **Policy** tabs for a version you can copy and paste. ```json Explanation theme={null} // IMPORTANT: This snippet is meant for documenting why Opal requires each of // these permissions, allowing you to customize based on your needs. For a valid // pasteable policy document, open the "Policy" tab above. { "Version": "2012-10-17", "Statement": [ { // Required to: // _ Revoke sessions in this account // _ Grant users session-based access to: // _ EC2 instances // _ RDS databases/clusters "Sid": "OpalRequiredToManageUserSessions", "Effect": "Allow", "Action": [ // Required to read/validate AWS configuration "access-analyzer:ValidatePolicy", "ec2:DescribeInstances", "eks:DescribeCluster", "eks:ListClusters", "iam:GetRole", "iam:ListRoles", "iam:ListRoleTags", "iam:GetRolePolicy", "rds:DescribeDBClusters", "rds:DescribeDBInstances", "ssm:DescribeInstanceProperties", "ssm:DescribeSessions", "ssm:GetConnectionStatus", // Required to tag roles and sessions "sts:TagSession", "iam:TagRole", // Required to revoke sessions before expiration, e.g. if a user's access // is removed by admin in Opal. "iam:DeleteRolePolicy", "iam:PutRolePolicy", // Required to grant end-users session credentials. "rds-db:connect", "ssm:SendCommand", "ssm:TerminateSession", "ssm:StartSession" ], "Resource": "\*" } ] } ``` ```json Policy theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "OpalRequiredToManageUserSessions", "Effect": "Allow", "Action": [ "access-analyzer:ValidatePolicy", "ec2:DescribeInstances", "eks:DescribeCluster", "eks:ListClusters", "iam:GetRole", "iam:ListRoleTags", "iam:GetRolePolicy", "iam:DeleteRolePolicy", "iam:PutRolePolicy", "iam:ListRoles", "iam:TagRole", "rds-db:connect", "rds:DescribeDBClusters", "rds:DescribeDBInstances", "sts:TagSession", "ssm:DescribeInstanceProperties", "ssm:SendCommand", "ssm:GetConnectionStatus", "ssm:TerminateSession", "ssm:StartSession" ], "Resource": "*" } ] } ``` ## What's Next After configuring your additional accounts, see the [AWS app configuration guide](/integrations/setting-up-your-aws-organization-in-opal#4-configure-opal-app) to complete your integration. *** # Atlassian Confluence Source: https://docs.opal.dev/integrations/confluence Learn how to connect your Opal instance with Confluence. Opal's integration with Confluence allows you to manage access to Confluence groups. ## Supported resources | Resource | Read | Grant and revoke access | | ----------------- | ---- | ----------------------- | | Confluence users | ✔️ | ✔️ | | Confluence groups | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version `1.1194.0`, if you self-host Opal ## 1. Create a Confluence admin API key To allow Opal to access your Confluence users and groups and grant permission to assign and remove users from groups, you must first create an [Admin API key](https://support.atlassian.com/organization-administration/docs/manage-an-organization-with-the-admin-apis/). In `admin.atlassian.com`, go to **Settings > Api Keys > Create API Key**. Select the following scopes: * `read:user:admin` * `read:directory:admin` ## 2. Create a Confluence API token In `id.atlassian.com`, go to **Security > Api Tokens > Create API Tokens**. Select the following scopes: * `read:group:confluence` * `write:group:confluence` * `read:user:confluence` ## 2. Create Confluence app in Opal In Opal, go to **Inventory** > **+App** and select **Confluence**. Name your Confluence app, provide a description, and enter the admin API key and API token you generated in step 1. Enter your Site Domain (e.g. yourcompany for yourcompany.atlassian.net). You will also need your Org ID, which is visible in the URL when you visit admin.atlassian.com: `https://admin.atlassian.com/o//...` And finally the admin email of the admin user that created the admin API key in step 1 After you save the app, you can import groups from **...** > **Import items**. *** # Coupa Source: https://docs.opal.dev/integrations/coupa Learn how to connect Opal with Coupa. Opal's integration with Coupa allows you to manage access to Coupa roles and users. ## Supported resources | Resource | Read | Grant and revoke access | | ----------- | ---- | ----------------------- | | Coupa Roles | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version 1.1050.0, if you self-host Opal ## 1. Create OAuth client in Coupa To allow Opal to access to your Coupa roles and grant permission to assign and remove roles from users, you must first [create an OAuth client](https://compass.coupa.com/en-us/products/product-documentation/integration-technical-documentation/the-coupa-core-api/oauth-2.0-and-oidc/openid-connect-clients). Select **Client credentials** as the **Grant type**, and set the following scopes: ```Text Scopes theme={null} core.user.read core.user.write core.user_group.read core.user_group.write core.common.read ``` Save the Client ID and Client Secret and return to Opal. ## 2. Create Coupa app in Opal In Opal, go to **Inventory** > **+App** and select **Coupa**. Name your Coupa app, provide a description, and enter the Client ID and Client Secret from step 1. After you save the app, you can import roles from **...** > **Import items**. *** # Cursor Source: https://docs.opal.dev/integrations/cursor Learn how to connect Opal with Cursor. Opal's integration with Cursor allows you to gain visibility over access to Cursor Organizations and monitor user roles within your development environment. With the Cursor integration, you can: * View all users who have access with specific roles to your Cursor Organization * Monitor usage ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ------------------- | ---- | ----------------------- | ------------------------ | | Cursor Organization | ✔️ | | ✔️ | ## Supported roles Opal syncs the following default Cursor roles: * **Admin**: Full administrative access to the Cursor Organization * **Member**: Standard member access to the organization * **Unpaid Admin**: Administrative access without billing privileges Opal also syncs any custom roles you have configured in your Cursor Organization. ## Requirements Before you begin, you must: * Be an Opal Admin * Have Admin permissions in your Cursor Organization ## 1. Retrieve an API token in Cursor To allow Opal to access your Cursor Organization, you must first create an API token in Cursor: 1. Navigate to your Cursor Organization settings 2. Go to the API or Integrations section 3. Generate a new API token with read permissions Save the token, as you won't be able to retrieve it later. ## 2. Create Cursor app in Opal In Opal, go to **Inventory** > **+App** and select **Cursor**. Name your Cursor app, provide a description, and enter the token you generated in step 1. After you save the app, you can import roles from **...** > **Import items**. *** # Databricks Source: https://docs.opal.dev/integrations/databricks Learn how to connect your Opal instance to Databricks. Self-hosted Opal deployments must be on version 1.949 or later to use the Databricks integration. With Opal's integration with Databricks: * Users can request time-bound access to your Databricks groups. * Auditors can initiate [access reviews](/docs/access-reviews) that assign managers or group admins to periodically review users with long-lived access to Databricks resources. * Admins can add resources from other Opal integrations to an Databricks group so a Databricks group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * All access changes are tracked in as [events](/docs/event-types) that you can log to Slack or export to your favorite tools. ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ----------------------------------------------------------------------------------------------------------------- | ---- | ----------------------- | ------------------------ | | Databricks account users | ✔️ | ✔️\* | ✔️ | | [Databricks account groups](https://docs.databricks.com/aws/en/admin/users-groups/groups) | ✔️ | ✔️\* | ✔️ | | [Databricks account service principals](https://docs.databricks.com/aws/en/admin/users-groups/service-principals) | ✔️ | ✔️\* | ✔️ | \*You can add Databricks users and service principals as members of groups, and grant users, service principals, and groups access to resources. The Databricks integration does not support managing identities at the workspace level, based on Databricks' guidelines for identity federation. ## Requirements To set up the Databricks integration, you must: * Be an Opal Admin * Have permission to create a service principal in Databricks ## 1. Configure fields in Databricks First, create a service principal in Databricks and create an OAuth secret for it: 1. Add a [Databricks service principal](https://docs.databricks.com/aws/en/admin/users-groups/service-principals#add-service-principals-to-your-account-using-the-account-console). 2. Assign the service principal the [Account admin role](https://docs.databricks.com/aws/en/admin/users-groups/service-principals#assign-account-admin-roles-to-a-service-principal). 3. [Create an OAuth secret](https://docs.databricks.com/aws/en/dev-tools/auth/oauth-m2m#step-2-create-an-oauth-secret-for-a-service-principal) for this service principal. Select **Generate secret** and specify any lifetime. You have to rotate this when it expires, so you might want to choose a long expiration. By default, secrets refresh every 2 years. 4. Save the **Secret** and **Client ID**, which you'll use in the next step. You'll also need the following from Databricks: * **Account Login URL**. Use the base URL you use to log in to Databricks. For example, `https://accounts.cloud.databricks.com`. * **Account ID**. Retrieve this from your avatar in the top left corner of Databricks. ## 2. Configure fields in Opal Go to **Inventory** > **+ App** and find the **Databricks** integration. Give the integration a name, [admin](/docs/configure-reviewers#owners), description, and specify its visibility. Enter the **Account Login URL**, **Account ID**, **Client ID**, and **Client secret** fields from the previous step, then select **Save**. ## 3. Import resources to Opal In the **Inventory** in the Databricks app, select **...** > **Import items** to add your Databricks resources to Opal. You can now manage access to Databricks resources in Opal. # Datadog Source: https://docs.opal.dev/integrations/datadog Learn how to connect your Datadog instance to Opal to manage and review access. You can connect Datadog to Opal to manage and review access to Datadog roles. ## Supported resources | Resource | Read | Grant and revoke access | | ----------------------------- | ---- | ----------------------- | | Roles | ✔️ | ✔️ | | Teams (Mapped to Opal Groups) | ✔️ | ✔️ | With Opal’s Datadog integration: * Users can request access to Datadog * Admins can provision/connect their Datadog instance to Opal * Approve requests or assign roles in Datadog to end-users ## Requirements To connect with Datadog, you must first: * Be an Opal Admin * Be on version `1.1152.0` or later if you self-host Opal ## 1. Create Datadog App in Opal In Opal, go to the **Inventory > + App**, then select **Datadog**. ## 2. Configure fields in the Datadog App In your Datadog tenant, go to **Settings > Access**. Create an API key and an Application Key, and take note of them. Back in Opal, fill in the app name, admin, description and visibility. Input your Datadog tenant's region under **Datadog Site**, and the API and Application key you created in the respective fields. ## 3. Create and sync Upon saving the app, you can begin import users and teams via **...** > **Import items**. # Devin AI Source: https://docs.opal.dev/integrations/devin Learn how to connect your Opal instance with Devin AI. Opal's integration with Devin AI allows you to manage access to Devin organizations, roles, users and groups. ## Supported resources | Resource | Read | Grant and revoke access | | ------------------- | ---- | ----------------------- | | Devin organizations | ✔️ | ✔️ | | Devin roles | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version 1.1091.0, if you self-host Opal ## 1. Create an API token in Devin To allow Opal to access to your Devin resources, you must first create a personal API key by going to https\://\[your-enterprise].devinenterprise.com/settings/api-keys. The owner of the API key must have the admin enterprise role. Sometimes this link redirects to the Devin Agent page. If it does, you can manually go to your enterprise account, https\://\[your-enterprise].devinenterprise.com 1. Click on settings on the bottom left page 2. Select your org 3. Click `Devin's API` on the left panel to create your Personal Access Token. ## 2. Create Devin app in Opal In Opal, go to **Inventory** > **+App** and select **Devin AI**. Name your Devin app, provide a description, and enter the token you generated in step 1. After you save the app, you can import roles from **...** > **Import items**. *** # Docusign Source: https://docs.opal.dev/integrations/docusign Learn how to connect Opal with Docusign to manage and review access. You can connect Docusign to Opal to manage and review access to your Docusign users, groups, and permission profiles. If you use self-hosted Opal, you must be on version 1.1195.0 or later to use the Docusign integration. ## Supported resources | Resource | Read | Grant and revoke access | | ------------------- | ---- | ----------------------- | | Users | ✔️ | ✔️ | | Groups | ✔️ | ✔️ | | Permission profiles | ✔️ | ✔️ | With Opal's Docusign integration: * **Users can** request time-bound access to Docusign groups and permission profiles, and have it granted automatically on approval. * **Admins can** sync users, groups, and permission profiles into Opal; provision and deprovision Docusign users; manage group membership; and assign the permission profile that governs what a user can do. * **Reviewers can** run periodic access reviews over Docusign access and revoke anything that is no longer needed. Opal grants and revokes access as follows: * **Groups** — granting adds the user to the Docusign group; revoking removes them. * **Permission profiles** — a Docusign user always has exactly one permission profile. Granting assigns the user to the requested profile. Revoking reassigns the user to the connection's default permission profile. See [Set the default permission profile](#set-the-default-permission-profile). * **Users** — provisioning creates the Docusign user; deprovisioning closes it. ## Requirements Before you begin, you must: * Be an Opal Admin. * Have a Docusign account that belongs to a **Docusign Organization** (organization-managed). Opal provisions users through the Docusign Admin API, which is only available to org-managed accounts. * Have a Docusign user who is an **organization administrator**. Opal impersonates this user, and organization administrator is a stronger role than account admin — the connection cannot be created without it. * Be able to create an integration key (app) in Docusign, generate an RSA keypair for it, and grant it one-time consent. ## 1. Create a Docusign integration key In Docusign, go to **Admin** > **Settings** > **Apps and Keys**, then: 1. Create an **integration key**. This is your client ID. For more detail, see Docusign's [JWT Grant documentation](https://developers.docusign.com/platform/auth/jwt/). 2. On the integration key, click **Generate RSA**. Docusign keeps the public key and shows the private key once — copy it, because you paste it into Opal. Opal never needs the public key. 3. Add `https://www.docusign.com` as a **redirect URI** on the integration key. 4. Note the **User ID** of the organization administrator that Opal impersonates, found under **My Account Information** > **User ID**. Note your **API Account ID** on the same screen if you want to pin the connection to a specific account. ## 2. Create the Docusign app in Opal In Opal, go to **Inventory** > **+App** and select **Docusign**. ## 3. Configure the connection in Opal In the Opal create form, enter: | Field | Notes | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | **App name** | An identifiable name for the connection. | | **App admin** | The Opal owner of this app. | | **Description** | A short description shown to people requesting access. | | **Visibility** | Global, or restricted to specific groups. | | **Environment** | **Production** (`account.docusign.com`) for a live org, or **Demo (sandbox)** (`account-d.docusign.com`) for a developer account. | | **Account ID** | Optional. Leave blank to use the impersonated user's default account. | | **Integration key** | The integration key (client ID) from step 1. | | **Impersonated user ID** | The organization administrator's User ID from step 1. | | **RSA private key** | The private key you generated in step 1, beginning with `-----BEGIN RSA PRIVATE KEY-----`. | ## 4. Grant consent JWT Grant requires the impersonated user to approve the integration key once. In the Opal form, once the required fields are filled in, click **Open the Docusign consent page**, sign in as the impersonated user, and click **Accept**. Consent is persistent, so you only do this once. If you skip this step, creating the connection fails with a `consent_required` error. ## 5. Test the connection and create the app Click **Test connection**. Opal verifies that it can read your directory and that the impersonated user is a Docusign organization administrator. When the check passes, the **Create** button is enabled. Editing any credential after a successful test re-runs the check. After you save the app, you can import your Docusign users, groups, and permission profiles from **...** > **Import items**. ## Additional information ### Set the default permission profile Because a Docusign user always has exactly one permission profile, Opal cannot simply remove one on revoke — it reassigns the user to a default permission profile that you choose. If this isn't set, permission profile revokes fail with a message asking you to configure one, so set it before you start managing permission profile access. To set the default permission profile: 1. Import your permission profiles first. The default is chosen from profiles Opal has already synced, so the dropdown is empty until an import has run. Open the connection in **Inventory**, then import (or wait for the first sync) so its permission profiles appear in Opal. 2. Open the Docusign connection in **Inventory** and go to its settings. 3. Click **Edit**. The **Default permission profile** field is read-only until you do. 4. In the **Default permission profile** dropdown, pick the profile that users should fall back to when their access to another permission profile is revoked. The dropdown is searchable and shows up to 100 synced profiles. 5. Save the connection settings. Choose a low-privilege profile, such as a basic sender or viewer profile, so that a revoke lands users in a least-privilege state rather than leaving them with anything elevated. You can change the default at any time from the same screen. ### Silent activation vs. activation emails Opal provisions users silently, with no activation email, when the user's email domain is a verified reserved domain in your Docusign Organization. Users whose email domain is not verified are created through Docusign's standard activation email flow instead. *** # Duo Source: https://docs.opal.dev/integrations/duo Connect your Duo instance to use Opal to manage and review access. Opal's integration with Duo supports the following, and more: * Users can **request time-bounded access** to your Duo groups. * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to Duo groups. * Admins can **add resources from other Opal integrations** to an Duo group so an Duo group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * All access changes are tracked in a **permanent audit log** that can be logged to a Slack channel or exported to your favorite tools. ## Supported resources and functionality | Resource | Read | Grant and revoke access | Available in Risk Center | | ---------- | ---- | ----------------------- | ------------------------ | | Duo Groups | ✔️ | ✔️ | ✔️ | The integration also supports user account [deprovisioning](/docs/user-provisioning). ## Requirements To set up your Duo app: * You must be an Opal Admin * Your users must also have the optional email field populated in Duo to be imported to Opal * You must have the ability to set up [Admin API credentials](https://duo.com/docs/adminapi#first-steps) in Duo ## 1. Create a Duo app To get started, go to the **Inventory** page, click **+ App** at the top right. Then, click on the Duo tile. ## 2. Generate Admin API credentials Opal requires Admin API credentials in order to manage your Duo Groups on your behalf. To learn more about setting up Admin API credentials, [see the Duo documentation](https://duo.com/docs/adminapi#first-steps). The following permissions must be granted for Opal to successfully manage access to your Duo instance. Admin API permissions can be found in the Duo Admin Portal under **Applications > Admin API > Permissions**. * Grant read information * Grant read log * Grant read resource * Grant write resource ## 3. Fill out Opal form Back in the Opal app creation form, fill in the details about your Duo account and select **Create**. If this step is successful, you have completed setting up the Duo connection. *** # Freshservice Source: https://docs.opal.dev/integrations/freshservice Learn how to connect your Opal instance with Freshservice With Opal's Freshservice integration, you can: * Sync access requests with Freshservice tickets to grant access for the lifetime of the ticket * Create audit tickets for access requests * Propagate access to end systems using Freshservice tickets ## Configuration requirements To connect Freshservice with Opal, you must: * Be an Opal Admin * Have admin access to your Freshservice account to generate an API key * Be on version 1.1.065.1 and later, if you self-host Opal ## 1. Create a Freshservice API key To enable the Freshservice integration, an administrator of your Freshservice account should [create an API key](https://support.freshservice.com/en/support/solutions/articles/50000000306-where-do-i-find-my-api-key-): 1. Log in to your Freshservice account 2. Click on your profile icon in the top right corner 3. Go to **Profile Settings** 4. Navigate to the **API Key** section in the right sidebar 5. Copy your existing API key or generate a new one 6. Save the generated key—you'll use it in the next step ## 2. Add your Freshservice details to Opal In Opal, go to **Configuration** > **Organization Settings** > **Productivity Integrations** and select **Connect** next to **Freshservice**. Enter your Freshservice domain (e.g., if your Freshservice URL is `https://acme.freshservice.com`, enter `acme.freshservice.com`) and the API key from the previous step, then save the integration. ## Access requests with Freshservice tickets After you integrate Freshservice, when making an access request, you can enable the option to **Expire access when ticket is closed**, which allows Opal to [reference](/docs/ticket-propagation#reference-existing-tickets-on-access-requests) existing Freshservice tickets. When you select **Search for tickets**, a list of Freshservice tickets populates, showing tickets assigned to you in Freshservice based on the Freshservice account that corresponds to your Opal email address. Click on any of these support tickets to attach the ticket to the access request. By default, an expiration time bound is required when making any access request. After the request is approved, access expiration is determined by the combination of this expiration time bound and the closure of the support ticket. Access will expire according to the minimum of these two events: if the ticket is closed before the expiration time bound is reached, access will be revoked when the ticket is closed. Similarly, if the expiration time bound is reached before the closure of the ticket, access is revoked when the expiration time bound is reached. If you want to attach an access request to a support ticket without an expiration time bound, you may select **Indefinite** under the expiration options and bind the request to a support ticket. ## Create audit tickets To create [audit tickets](/docs/ticket-propagation#create-audit-tickets) in Freshservice for every access request, enable **Freshservice** as a ticketing provider under **Configuration** > **Organization Settings** > **Access Requests**. Whenever a user requests access in Opal, Opal creates a ticket in Freshservice, allowing you to audit every access request in your own system. Audit tickets are auto-closed by Opal when access expires. You can manually close audit tickets in Freshservice, but this has no effect in Opal. ## Propagate access with tickets See the [ticket propagation guide](/docs/ticket-propagation#propagate-access-with-tickets) to learn how to propagate access with tickets connected to your Freshservice account. This can be especially useful for systems that still require manual access propagation and require someone to be notified via a ticket. *** # GCP Service Accounts Source: https://docs.opal.dev/integrations/gcp-service-accounts Add and manage Google Cloud Platform (GCP) service accounts in Opal. Opal lets you view and manage your GCP service accounts as non-human identities (NHIs). This means you can have control over who has access to your service accounts, and also what resources your service accounts themselves have access to. ## Add a Service Account In order to begin importing Service Accounts into Opal, you need to update your Opal Service Account’s Role to have the following permission: ``` iam.serviceAccounts.get iam.serviceAccounts.getIamPolicy iam.serviceAccounts.list iam.serviceAccounts.setIamPolicy ``` Service Accounts that have access to your resources will be automatically imported into Opal as children of their associated GCP projects. Admins can also select additional Service Accounts to manually import into Opal: ## Manage Access In the "User Access" tab, admins can view all users that have access to a Service Account, including what role they have, when their access expires, and how they obtained the access. * In the below example, Cynthia has access to the Service Account Admin role through a group which expires in a year, whereas Roberto has direct access to the Token Creator role expiring in a day. Emanuel is an Owner of the entire GCP Organization, so he inherited that role onto the Service Account as well. In the "Resources" tab, admins can similarly view all resources that a Service Account has access to. * Here we see that the Service Account has access to the Chronicle Service Agent role on the Bigquery Dataset, which it has also inherited onto all of the Dataset’s tables. We can also see that permanent access has been directly granted to the Service Account on the Events table. In the "Non-human Access" tab of other resources, admins can view which service accounts have access to the resource. By clicking "Add Principals", more service accounts can be granted access to the resource as well: # GitHub Source: https://docs.opal.dev/integrations/github Connect Opal to your GitHub organization to manage and review access. Learn how to connect GitHub to Opal to manage access to your GitHub organization's repositories and teams. ## Supported resources Opal does not yet support personal repositories. Opal also does not yet support access management for GitHub users that are not members of your organization. | Resource | Read | Grant and revoke access | Available in Risk Center | | ------------------------------ | ---- | ----------------------- | ------------------------ | | GitHub teams | ✔️ | ✔️ | ✔️ | | GitHub repositories | ✔️ | ✔️ | ✔️ | | GitHub organization roles | ✔️ | ✔️ | ✔️ | | GitHub organization owner role | ✔️ | ✔️ | ✔️ | When users request access to GitHub repositories, they can also request to assume specific roles. ## 1. Create an Opal app To set up a new connection, go to the **Inventory** page and select the **+ App** button on the top right. Then select the GitHub tile. 2312 To upgrade an existing Opal app, go to the **Setup** tab in the **Inventory** and select **Register App**. ## 2. Create the GitHub App Ensure you have permissions to create a GitHub App in your GitHub organization. If you're creating the app for the first time, enter an app name, GitHub organization name, app admin, and description. If your organization uses SAML SSO, setting **Enable SAML SSO Ingestion** syncs users from your GitHub organization's SAML SSO identities. SAML SSO ingestion only includes users who have signed into GitHub at least once using your organization's SAML SSO. Users provisioned in your IdP (e.g., Okta) who have never completed a GitHub SSO login will not appear in Opal until they do so. If you need all IdP users synced automatically without requiring a GitHub SSO sign-in, consider setting up a GitHub Enterprise connection with SCIM provisioning configured in your IdP. After you create the Opal app or select **Register App** for an existing integration, you'll be directed to GitHub to create the [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). ## 3. Optional: Link GitHub identities to Opal accounts GitHub only makes the email address of a GitHub account available via its API if a user has elected to publicly display their email address. Thus, Opal needs another way to match GitHub identities with Opal accounts. If your organization does not use SAML SSO, each user must link their GitHub account to their Opal account. If you've enabled SAML SSO ingestion, users can still manually link GitHub identities, but SAML usernames will take precedence. For security reasons, we ask users to log in to both Opal and GitHub to link their accounts. For the following steps, the GitHub account you wish to integrate **must have a verified email address corresponding to your Opal email address**. 1. In the bottom left, click your **User** > **Account Settings**. 2312 2. Click **Identities** > **Connect** next to the GitHub integration. 2312 3. You will be redirected to a GitHub page, which will prompt you to log into your GitHub account. ### Programmatically map GitHub usernames If your end users cannot manually link accounts—e.g., if you're connecting service accounts to Opal—you can alternatively set the GitHub username as an attribute in your IdP and [import it as a user attribute](). ## Usage data and Risk Center Opal can ingest usage data from your GitHub organization to help identify usage patterns and surface unused access grants in the [Risk Center](/docs/least-privilege-posture-management). Usage data is collected from GitHub's organization audit log (`git clone`, `git fetch`, and `git push` events). This requires your organization to be on **GitHub Enterprise Cloud** and the Opal GitHub App to have **Organization permissions → Administration: Read**. The **Register App** flow requests this permission automatically. If your organization is not on GitHub Enterprise Cloud, Opal skips audit log collection — other sync functionality is unaffected. ## Github Organization Owner Support To leverage Github Organization Owner Support in your Opal environment, go to your **Github App** > import the **Github Organization** resource. The member / admin (owner) roles will be automatically populated and show up as access levels on the Organization in the request modal. ## Github Repository Custom Roles For organizations that have defined custom repository roles, these will appear in the roles tab for the organization resource. Upon first syncing a github connection the 5 default repository roles will appear in this tab: Admin, Maintain, Write, Triage and Read. These 5 roles cannot be reordered relative to one another. Underneath these, "unreviewed" roles will appear. An admin can drag and drop these into the hierarchy around the default roles. This ordering will determine what repository role is pushed for a user that has multiple grants. Granting repository access with roles whose ordering has not been defined is unsupported. Custom repository roles can also be viewed in the roles tab for each individual respository. However, in this view their ordering is read-only. This is because repository roles are universal to all repositories in an organization and can thus only be changed from the organization resource's roles tab. # GitHub Enterprise Source: https://docs.opal.dev/integrations/github-enterprise Connect Opal to your GitHub Enterprise account to manage and review access. Opal's Github Enterprise connection allows you to review and manage access to Github Enterprise teams and roles. ## Supported resources | Resource | Read | Grant and revoke access | | :----------------------------- | :--- | :---------------------- | | GitHub repositories | ✔️ | ✔️ | | GitHub teams | ✔️ | ✔️ | | GitHub Enterprise teams | ✔️ | ✔️ | | GitHub organization roles | ✔️ | ✔️ | | GitHub Enterprise roles | ✔️ | ✔️ | | GitHub organization owner role | ✔️ | ✔️ | | GitHub organization | ✔️ | | When users request access to GitHub repositories, they can also request to assume specific roles. ## Requirements To set up your Github Enterprise connection in Opal: * You must be an Opal Admin * You must be a Github Enterprise Admin * You must have a Github owner account for your enterprise ### How to create a Github owner account We recommend **not** to use your personal account as Opal needs this personal account token (PAT) to connect to your Github Enterprise. 1. Log into the Github enterprise you want to integrate with Opal. Ensure the account you are creating a PAT for is an owner of the enterprise. Appoint the account you just created as co-owner of the enterprise. 2. [Create a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) for the owner account you just created. When creating the personal access token, enable the **admin:enterprise** permission. Take note of this for creating your Github Enterprise app in Opal in [Step 3](#3-finish-connecting-github-enterprise-in-opal). ## Setup Instructions ### 1. Connect to Github Enterprise in Opal To set up a new connection, go to **Inventory > + App** and select **Github**. If you want to keep history of your old Github connection, you can migrate your existing connection to an enterprise connection. Simply go to **Setup > Migrate to Enterprise Account** in your existing connection, and continue following the steps below. Fill in the App Name, Enterprise Name, Admin and Description fields respectively. Ensure that you have indicated this to be an **Enterprise account** by checking the checkbox below App name. If your organization uses SAML SSO, you may Enable SAML SSO Ingestion to sync users from your Github organization's SAML SSO identities. SAML SSO ingestion for Enterprise connections uses GitHub's SCIM API, which automatically syncs all users provisioned in your IdP — no GitHub SSO sign-in required from end users. To enable this, two things must be configured: 1. **On GitHub**: SAML SSO must be enabled on your organization, and a GitHub org owner must generate a Personal Access Token (classic) with the `admin:org` scope, authorized for SAML SSO. See [About SCIM for organizations](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-saml-single-sign-on-for-your-organization/about-scim-for-organizations). 2. **On your IdP** (e.g., Okta): Configure the GitHub SCIM app using GitHub's SCIM endpoint (`https://api.github.com/scim/v2/organizations/{org}/`) and the PAT from step 1. See GitHub's [SCIM API reference](https://docs.github.com/en/enterprise-cloud@latest/rest/scim/scim?apiVersion=2022-11-28). Without both configured, SCIM provisioning will not work. Upon clicking continue, you will see a setup URL under **Create Github App**. Take note of this for the next step. ### 2. Create a Github App in Github Enterprise For Opal to manage your Github Enterprise's resources, you must [create a Github app](https://docs.github.com/en/enterprise-cloud@latest/apps/creating-github-apps/registering-a-github-app/registering-a-github-app#registering-a-github-app) within your enterprise. In Github Enterprise, go to Settings > Github Apps > New Github App. Fill in the following fields: | Field | Input | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Github App Name | A descriptive name | | Homepage URL | Your homepage URL | | Callback URL | [https://app.opal.dev/callback/github](https://app.opal.dev/callback/github) | | Setup URL | The setup URL you took note of in Step 1 | | Webhook | Inactive | | Repository Permissions | Administration: Read and Write | | Organization Permissions | Administration: Read and Write
Members: Read and Write | | Account Permissions | Email addresses: Read only | | Enterprise Permissions | Custom Enterprise Roles: Read and Write
Enterprise Organizations: Read and Write
Enterprise People: Read only
Enterprise Teams: Read and Write
Enterprise Organization Installations: Read only | After creating the Github app, generate a Client secret and Private key. ### 3. Finish configuring Opal's Github Enterprise connection Back in Opal, fill in the Client ID, Client secret and Private key from your Github App. In the Admin Token field, fill in the Personal Access Token generated for your Github Enterprise owner account. Refer [above](#how-to-create-a-github-owner-account) if you have not created one yet. Then, click create to the Github Enterprise app in Opal. ### 4. Install Github app Install the Github app you created in Step 2 for your enterprise by navigating to **Install App** and selecting your enterprise. You will also need to [install the app](https://docs.github.com/en/enterprise-cloud@latest/apps/using-github-apps/installing-your-own-github-app) in each organization you would like managed in Opal. ## Usage data and Risk Center Opal can ingest usage data from your GitHub organizations to help identify usage patterns and surface unused access grants in the [Risk Center](/docs/least-privilege-posture-management). Usage data is collected from GitHub's organization audit log (`git clone`, `git fetch`, and `git push` events). The **Organization permissions → Administration: Read** permission required for this is already included in the GitHub App setup in [Step 2](#2-create-a-github-app-in-github-enterprise). ## Github Repository Custom Roles For organizations that have defined custom repository roles, these will appear in the roles tab for the organization resource. Upon first syncing a github connection the 5 default repository roles will appear in this tab: Admin, Maintain, Write, Triage and Read. These 5 roles cannot be reordered relative to one another. Underneath these, "unreviewed" roles will appear. An admin can drag and drop these into the hierarchy around the default roles. This ordering will determine what repository role is pushed for a user that has multiple grants. Granting repository access with roles whose ordering has not been defined is unsupported. Custom repository roles can also be viewed in the roles tab for each individual respository. However, in this view their ordering is read-only. This is because repository roles are universal to all repositories in an organization and can thus only be changed from the organization resource's roles tab. # GitLab Source: https://docs.opal.dev/integrations/gitlab Connect Opal to your GitLab instance or group to manage and review access. Opal supports GitLab for all tiers (free, premium, and ultimate) for [GitLab Self-Managed](https://docs.gitlab.com/ee/subscriptions/self_managed/) and [GitLab.com](https://docs.gitlab.com/ee/subscriptions/gitlab_com/) (formerly GitLab SaaS). If you use GitLab Self-Managed, admins can import both group and personal repositories. If you use GitLab.com, admins can **only** import group repositories. ## Supported resources | Resource | Available with GitLab Self-Managed | Available with GitLab.com | Read | Grant and revoke access | Available in Risk Center | | ------------------------ | ---------------------------------- | ------------------------- | ---- | ----------------------- | ------------------------ | | GitLab groups | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | GitLab personal projects | ✔️ | | ✔️ | ✔️ | ✔️ | | GitLab projects | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ## 1. Create a GitLab service account for Opal ### GitLab.com Under your top-level group that represents your organization on GitLab.com, create a new user with an **Owner** **role**. Refer to GitLab [instructions](https://docs.gitlab.com/ee/user/profile/account/create_accounts.html) for this step. A new account is preferred because we will be using the personal OAuth access token corresponding to this account. ### GitLab Self-Managed Log into your self-managed GitLab instance as an admin, and go to the **Admin Area** section by from the **Main menu > Admin Area > Users**. Create a new user and appoint the new user with the access level **Administrator**. ## 2. Create a GitLab OAuth app Opal requires an Application to be set up on GitLab to handle projects and groups synchronization, as well as user pairing. ### GitLab.com Follow the [instructions in GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html#group-owned-applications) to create a new OAuth App in your top-level GitLab group. During the OAuth app creation process, for **Name**, you can enter **Opal** or any name you prefer. For **Redirect** **URL**, enter your domain name, followed by`/callback/gitlab/` e.g., `https://app.opal.dev/callback/gitlab/`. On a new line, enter your domain name, followed by `/callback/gitlab-connection/`, e.g., `https://app.opal.dev/callback/gitlab-connection/`. The backlashes at the end of these two *Redirect URLs* are very important to GitLab, so ensure you include them. Set the app as **Trusted** and **Confidential**. Under **Scopes**, select **api**, **profile**, and **email**. After creating your app, record the Application ID and copy the **secret**. These are used in the **Application ID** and **Application Secret** fields in Step 3. ### GitLab Self-Managed Follow the [instructions in GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html#instance-wide-applications) to create a new OAuth App in your GitLab Self-Managed instance. For **Redirect** **URL**, enter your domain name, followed by`/callback/gitlab/` e.g., `https://app.opal.dev/callback/gitlab/`. On a new line, enter your domain name, followed by `/callback/gitlab-connection/`, e.g., `https://app.opal.dev/callback/gitlab-connection/`. Set the app as **Trusted** and **Confidential**. Under **Scopes**, select **api**, **profile**, and **email**. The backlashes at the end of these two *Redirect URLs* are very important to GitLab, so ensure you include them. After your app is created, record the Application ID and copy the secret. These are used in the **Application ID** and **Application Secret** fields in Step 3. ## 3. Create an Opal app Go to the **Inventory** page and select **+ App**. Then select the **GitLab** tile. If you use **GitLab Self-Managed**, click on **Custom Domain** and enter the domain of your instance. For **App ID** and **App Secret**, use the generated credentials from Step 2. If this step is successful, you then need to create a sync token. Click on the **Setup** tab in the App overview page, and click on **Connect OAuth Admin Token**. This will redirect you to your Gitlab instance and you should use the GitLab account created in Step 1 to complete the OAuth flow. For **GitLab Self-Managed**, sync should start working automatically. For **GitLab.com**, every Opal user in your organization must complete the following step to permit access management to your repositories in Opal. ## 4. Link GitLab identities to Opal accounts (GitLab.com only) To enable Opal to manage access to GitLab.com, each user must link their GitLab account to their Opal account. Opal requires this step because GitLab only makes the email address of a GitLab account available via its API if a user has elected to publicly display their email address. Thus, Opal needs another way to match GitLab identities with Opal accounts. For security reasons, users must log in to both Opal and GitLab to link their accounts. The following steps require that the GitLab account you want to integrate has a verified email address corresponding to your Opal email address. 1. In the bottom left, click your **User** > **Account Settings**. 2. Click **Connect** next to the GitLab integration. 3. You will be redirected to a GitLab.com page, which will ask you to log into your GitLab account. ## Usage data and Risk Center Opal can ingest usage data from your GitLab projects to help identify usage patterns and surface unused access grants in the [Risk Center](/docs/least-privilege-posture-management). Usage data is collected from GitLab's project events API and works on **all GitLab tiers** — no Premium or Ultimate subscription is required, and no extra permissions beyond those the Opal app already uses. Opal records the following GitLab activity as usage of a project: | Activity | Recorded as | | ------------------------------------ | ------------------------- | | Commits pushed to an existing branch | Push to the branch | | Branch created | Push | | Branch deleted | Push | | Tag created | Push | | Tag deleted | Push | | Merge request merged | Push to the target branch | ### What is not recorded **Opal cannot see read-only activity.** GitLab does not expose these operations through the APIs Opal reads: * `git clone` * `git fetch` * `git pull` * Browsing files, commits, or merge requests in the GitLab UI * Downloading a repository as an archive On the wire, clone, fetch, and pull are the same operation, and GitLab records them only in audit event streaming, which Opal does not consume. **What this means:** a user who regularly pulls from a repository but never pushes will show as not having used it. Read GitLab "Last Used" as a signal of *write* activity, not of access overall. ### Attribution on GitLab.com On GitLab.com, usage is only attributed to users who have linked their GitLab account to Opal — see [Step 4](#4-link-gitlab-identities-to-opal-accounts-gitlab-com-only). Activity by users who have not linked their identity is not counted. On GitLab Self-Managed, Opal imports users directly from your instance, so no linking step is needed. # Google Source: https://docs.opal.dev/integrations/google Opal supports integrations with Google Groups and Google Workspace. Use the following guide for Google Groups and Google Workspace, and the [Google Cloud Platform integration](/integrations/google-cloud-platform) guide for GCP resources. ## Supported resources The Google Workspace integration supports the following: | Resource | Read | Grant and revoke access | Available in Risk Center | | ---------------------- | ---- | ----------------------- | ------------------------ | | Users | ✔️ | ✔️ | ✔️ | | User attributes | ✔️ | | | | Google Workspace Roles | ✔️ | ✔️ | ✔️ | The Google Workspace integration also supports user account [deprovisioning](/docs/user-provisioning). The Google Groups integration supports the following: | Resource | Read | Grant and revoke access | Available in Risk Center | | ------------- | ---- | ----------------------- | ------------------------ | | Users | ✔️ | ✔️ | ✔️ | | Google Groups | ✔️ | ✔️ | ✔️ | ## 1. Add a Service Account for Opal To connect to Google Groups or Google Workspace, you'll need to create a Google service account with proper permission scopes. 1. Open the [Service accounts page](https://console.developers.google.com/iam-admin/serviceaccounts). If prompted, select a project. 2. Select **+ Create Service Account**. Enter a name, ID, and description, then click **Done**. 3. Select your newly-created service account, and go to the **Keys** tab. 4. Select **Add key** > **Create new key**. 5. Select **JSON** as the **Key type** and click **Create**. Your new public/private key pair is generated and downloaded to your machine. ## 2. Configure Permission Scopes for the Service Account 1. Select your newly-created service account, and go to the **Details** tab. 2. Open the **Advanced Settings** section, look under **Domain-wide Delegation**, and follow [the instructions](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) for setting up domain-wide delegation for your service account. Alternatively, use the following instructions: 1. From your Google Workspace domain's [Admin console](https://admin.google.com/ac/owl), go to **Main menu > Security > Access and data controls > API controls**. 2. In the **Domain wide delegation** pane, select **Manage Domain Wide Delegation**. Click **Add new**. 3. In the **Client ID** field, enter the client ID under your service account's **Details** tab > **Unique ID**. 4. In the **OAuth Scopes** field, enter the desired scopes. Details for what scopes the [Google Groups](/integrations/google-groups) and [Google Workspace](/integrations/google-workspace) integrations need are on their setup pages. *** # Google Chat Source: https://docs.opal.dev/integrations/google-chat Learn how to connect Opal to Google Chat. You can connect Opal to Google Chat to send notifications to users about access reviews and requests. Use the following steps to set up an Opal Google Chat app that can send messages to your users. ## 1. Create a Project In the GCP console, [create a new project](https://console.cloud.google.com/projectcreate) where your chat app will live. ## 2. Set up Service Account Credentials [Create a service account](https://console.cloud.google.com/iam-admin/serviceaccounts/create) for the project, giving it an appropriate name. Then go to the **Keys** tab of your service account and create a new JSON key. The key will be downloaded after creation. Opal uses the non-sensitive [`https://www.googleapis.com/auth/chat.bot` scope ](https://developers.google.com/workspace/chat/authenticate-authorize)for most operations. However, the chat app also needs domain-wide delegation for the readonly directory scope in order to find users and send them direct messages. To do that, go to [domain-wide delegation](https://admin.google.com/ac/owl/domainwidedelegation), select **Add New**, and enter your service account's client ID, granting it the following scope: ``` https://www.googleapis.com/auth/admin.directory.user.readonly ``` ## 3. Set up Google Chat App Enable the Google Chat API for the project in the [APIs library](https://console.cloud.google.com/apis/library). Then configure the [chat app settings](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat). Set the Application Info with the following: * **App Name**: `Opal` * **Avatar URL**: `https://opal-logos.s3.us-east-2.amazonaws.com/opal-thumbnail-logo.png` * **Description**: Chat app for Opal notifications. ### Self-hosted For self-hosted customers, Opal supports interaction through Google Chat messages directly to take actions like approving or denying requests. To do so, we will use [pub/sub](https://developers.google.com/workspace/chat/quickstart/pub-sub). First, create a pub/sub topic in your project [here](https://console.cloud.google.com/cloudpubsub/topic/list). Keep **Add a default subscription** enabled and leave the other options as the defaults. * Name your topic `opal-chat`. * This creates a topic and an associated subscription named `opal-chat-sub`. * Edit the subscription and ensure that the retry policy is **Retry after exponential backoff**. Assign the **Pub/Sub Publisher** role on your project to the following service account :`chat-api-push@system .gserviceaccount.com`. Assign the **Pub/Sub Subscriber** role on the subscription to the service account you created for the chat app above. Go back to the Google Chat API configuration, toggle on **Enable Interactive Features** and enter the following settings: * Enable **Receive 1:1 messages** and **Join spaces and group conversations**. * Select **Cloud Pub/Sub** and enter your topic's name, e.g., `projects//topics/opal-chat`. Leave everything else default and save. ### Cloud For cloud customers, Opal's Google Chat integration does not yet support interactivity. To allow the app to be published and installed into your workspace, go to the Google Chat API configuration, toggle on **Enable Interactive Features** and enter the following settings: * Enable **Receive 1:1 messages** and **Join spaces and group conversations**. * Select **Cloud Pub/Sub** and enter the topic name, e.g., `projects//topics/opal-uninteractive-chat`. Leave everything else default and save. ## 4. Publish the Chat App Enable the Google Workspace Marketplace SDK\*\* API on your project. On the App configuration tab, enter the following: * **App Visibility**: `Private` * **Installation Settings**: `Individual + Admin Install` * **App Integration**: `Chat app` * For the OAuth scopes, enter `https://www.googleapis.com/auth/admin.directory.user.readonly`. For Developer info, enter: * **Developer Name**: `Opal` * **Developer Website URL**: `https://www.opal.dev/` * **Developer Email**: `support@opal.dev` Save the draft. There will be a prompt at the top of the App configuration section about setting up the OAuth consent screen. Use it to navigate to the [setup page](https://console.cloud.google.com/apis/credentials/consent). For the settings, enter: * **User Type**: `Internal` * **App Name**: `Opal` * **User Support Email**: Select an appropriate support contact from the dropdown. * **Developer Contact Information**: [support@opal.dev](mailto:support@opal.dev) * Select **Save and continue**, then skip the scope section for now by selecting it again. Go back to the `Google Workspace Marketplace SDK` API page, where you should no longer see the warning about the OAuth screen. Go to the **Store Listing** tab to publish the app. Enter the following: * **Category**: Communication * **Graphics Assets**: Download Opal's [logo](https://files.readme.io/5dec2a0fe3c00ceaa88ac200ab36823d09bc93c182a99bc0538389de5e87b0b7-opal-logo.png) and [banner](https://files.readme.io/d3cc5c572e555879c006fdc585858bf18e6ea5ea935acf86ed5025e8c2edafd6-opal-banner.png) * **Screenshots**: Use [this screenshot](https://files.readme.io/c5a5169b38d3748e1cee7c69293da7be1d3dc4814599c6b90e70940fce26dd8e-Screenshot_2025-01-23_at_2.45.45_PM.png) * **Terms of Service URL**: `https://www.opal.dev/tos` * **Privacy Policy URL**: `https://www.opal.dev/privacy-policy` * **Support URL**: `https://opal.instatus.com/` * **Regions**: Select `All Regions`. Save the draft, then publish. ## 5. Install the Chat App to your workspace Go to the [admin console](https://admin.google.com/ac/apps/gmail/marketplace/apps) to view apps installed in your workspace. Search for `Opal` and select the app. Select **Admin Install**, make sure **Everyone at your organization** is selected, and select **Finish**. ## 6. Connect Google Chat to Opal In the Opal dashboard, go to **Configuration** > **Settings** > **Productivity Integrations**. Select the option to connect a **Google Chat Integration**. You'll need two pieces of credentials: * The service account key JSON that you downloaded earlier * The email of an admin user which the chat app can authenticate as to use the domain wide delegated scopes you granted earlier After you select **Create**, you're done setting up the app. Users will begin to receive notifications about access requests and reviews and can toggle their notifications in their account settings. # Google Cloud Platform (GCP) Source: https://docs.opal.dev/integrations/google-cloud-platform Connect your GCP infrastructure to use Opal to manage and review access. Use Opal's Google Cloud integration to quickly grant your team temporary access to your Google Cloud resources. With the integration: * Users can request time-bounded access to your GCP resources. * Auditors can initiate access reviews that assign managers or group admins to periodically review users with long-lived access to GCP resources. * All access changes are tracked as [events](/docs/event-types) that you can log to Slack or export to your favorite tools. ## Supported resources | Resource | Read | Grant and revoke access | Included in [Risk Center](/docs/least-privilege-posture-management) | | ---------------------------- | ---- | ----------------------- | ------------------------------------------------------------------- | | GCP Organizations | ✔️ | ✔️ | ✔️ | | GCP Projects | ✔️ | ✔️ | ✔️ | | GCP Folders | ✔️ | ✔️ | ✔️ | | GCP Buckets | ✔️ | ✔️ | ✔️ | | GCP Cloud SQL instances | ✔️ | ✔️ | ✔️ | | GCP Compute Engine Instances | ✔️ | ✔️ | ✔️ | | GCP BigQuery Datasets | ✔️ | ✔️ | ✔️ | | GCP BigQuery Tables | ✔️ | ✔️ | ✔️ | | GCP Service Accounts | ✔️ | ✔️\* | ✔️ | | GCP GKE | ✔️ | ✔️ | ✔️ | | GCP Billing Accounts | ✔️ | ✔️ | ✔️ | \*You can assign give users access to GCP Service Accounts and grant GCP Service accounts access to resources. You cannot yet add GCP Service Accounts to groups. ## Create a service account To get started, create a service account with the proper permission scopes. * Open the [Service accounts page](https://console.developers.google.com/iam-admin/serviceaccounts). If prompted, select a project. * Ensure that the selected project has the [Cloud Resource Manager API](https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview) and the [IAM API](https://console.developers.google.com/apis/api/iam.googleapis.com/overview) enabled. * At the top of the page, click "**+ Create Service Account**". Enter a name and description for the service account. When done, click **Create**. * The **Service account permissions** section that follows is not required. Click **Continue**. * On the **Grant users access to this service account** screen, click **Done**. * Select the new service account. * Click the **Keys** tab. * Click the **Add key** drop-down menu, then select **Create new key**. * Select **JSON** as the **Key type** and click **Create**. * Your new public/private key pair is generated and downloaded to your machine. * Click **Close** on the **Private key saved to your computer** dialog, then return to the table of your service accounts. * Make a copy of the full email of the service account. Let's now [create a custom role in IAM](https://console.cloud.google.com/iam-admin/roles). * Select the organization level at the top: 762 * Click **+ Create Role**. * Give it a title, ID and set the launch stage to **General Availability**. * Click **+ Add Permissions**. 1142 * Add the following permissions. The `resourcemanager.organizations.get` permission is required, and the rest are optional. Use the **Explanation** tab to learn which permissions are necessary for your use case. ```Text Permissions theme={null} resourcemanager.organizations.get iam.roles.get iam.roles.list resourcemanager.folders.get resourcemanager.folders.getIamPolicy resourcemanager.folders.list resourcemanager.folders.setIamPolicy resourcemanager.projects.get resourcemanager.projects.getIamPolicy resourcemanager.projects.list resourcemanager.projects.setIamPolicy resourcemanager.organizations.getIamPolicy resourcemanager.organizations.setIamPolicy storage.buckets.get storage.buckets.getIamPolicy storage.buckets.list storage.buckets.setIamPolicy cloudsql.users.create cloudsql.users.delete cloudsql.users.list cloudsql.instances.get cloudsql.instances.list compute.instances.get compute.instances.getIamPolicy compute.instances.list compute.instances.setIamPolicy iam.serviceAccounts.get iam.serviceAccounts.getIamPolicy iam.serviceAccounts.list iam.serviceAccounts.setIamPolicy bigquery.datasets.get bigquery.datasets.update bigquery.datasets.getIamPolicy bigquery.tables.get bigquery.tables.getIamPolicy bigquery.tables.list bigquery.tables.setIamPolicy bigquery.jobs.create billing.accounts.get billing.accounts.getIamPolicy billing.accounts.list billing.accounts.setIamPolicy logging.privateLogEntries.list ``` ```c Explanation theme={null} // Required, used to import GCP organizations and their children resourcemanager.organizations.get // Optional, used to check the connection configuration iam.roles.get // Optional, used to check the connection configuration iam.roles.list // Optional, used to import GCP folders and their children resourcemanager.folders.get // Optional, used to view access to GCP folders resourcemanager.folders.getIamPolicy // Optional, used to import GCP folders and their children resourcemanager.folders.list // Optional, used to push access to GCP folders resourcemanager.folders.setIamPolicy // Optional, used to import GCP projects and their children resourcemanager.projects.get // Optional, used to view access to GCP projects resourcemanager.projects.getIamPolicy // Optional, used to import GCP projects and their children resourcemanager.projects.list // Optional, used to push access to GCP projects resourcemanager.projects.setIamPolicy // Optional, used to view access to GCP organizations resourcemanager.organizations.getIamPolicy // Optional, used to push access to GCP organizations resourcemanager.organizations.setIamPolicy // Optional, used to import GCP buckets storage.buckets.get // Optional, used to view access to GCP projects storage.buckets.getIamPolicy // Optional, used to import GCP buckets storage.buckets.list // Optional, used to push access to GCP buckets storage.buckets.setIamPolicy // Optional, used to add users to GCP SQL instances cloudsql.users.create // Optional, used to remove users from GCP SQL instances cloudsql.users.delete // Optional, used to view access to GCP SQL instances cloudsql.users.list // Optional, used to import GCP SQL instances cloudsql.instances.get // Optional, used to import GCP SQL instances cloudsql.instances.list // Optional, used import GCP compute instances compute.instances.get // Optional, used to view access to GCP compute instances compute.instances.getIamPolicy // Optional, used to import GCP compute instances compute.instances.list // Optional, used to push access to GCP compute instances compute.instances.setIamPolicy // Optional, used to import GCP service accounts iam.serviceAccounts.get // Optional, used to view access to GCP service accounts iam.serviceAccounts.getIamPolicy // Optional, used to import GCP service accounts iam.serviceAccounts.list // Optional, used to push access to GCP service accounts iam.serviceAccounts.setIamPolicy // Optional, used to import BigQuery datasets bigquery.datasets.get // Optional, used to push access to BigQuery datasets bigquery.datasets.update // Optional, used to view access to BigQuery datasets bigquery.datasets.getIamPolicy // Optional, used to import BigQuery tables bigquery.tables.get // Optional, used to view access to BigQuery tables bigquery.tables.getIamPolicy // Optional, used to import BigQuery tables bigquery.tables.list // Optional, used to push access to BigQuery tables bigquery.tables.setIamPolicy // Optional, used to efficiently import tagged BigQuery tables via INFORMATION_SCHEMA queries (falls back to per-table metadata calls if missing) bigquery.jobs.create // Optional, used to import GCP billing accounts billing.accounts.get // Optional, used to view access to GCP billing accounts billing.accounts.getIamPolicy // Optional, used to import GCP billing accounts billing.accounts.list // Optional, used to push access to GCP billing accounts billing.accounts.setIamPolicy // Optional, used to ingest usage events for GCP resources logging.privateLogEntries.list ``` * To manage GCP billing accounts, add all four `billing.accounts.*` permissions together. With `list` and `get` but without `getIamPolicy`, billing accounts import successfully but Opal cannot read who has access to them, so each one reports an error. * Click **Create**. Then open the [Resource Manager page](https://console.cloud.google.com/cloud-resource-manager). * Select the top level organization: * On the right side "Info Panel", click **Add Principal**: * Enter the service account email, and select the new custom role. Then click **Save**. 1090 Your service account now has organization wide access to the Google IAM API. ## Connect app to Opal and confirm app validations In Opal, go to the **Inventory** and select **+App**, then find the **Google Cloud Platform** tile. Fill out the form using the above steps. After you save your app, you can view existing sync issues from the **Setup** tab on the app detail page. Missing permissions and sync issues show in the **App Validations** section. Select the refresh icon to rerun validation checks. You can hover over the validation icons to learn why Opal needs a given permission. To correctly sync your app to Opal, ensure you address any sync errors, marked with the red ! icon. Inspect warnings on a case-by-case basis: warnings might impact features you’re not using and may be safely ignored, but this depends on your use case. ## Usage data and Risk Center Opal can ingest usage data from your GCP environment to help identify usage patterns and surface unused access grants in the [Risk Center](/docs/least-privilege-posture-management). You'll need to enable data access audit logs in your environment following [the GCP documentation](https://cloud.google.com/logging/docs/audit/configure-data-access). Opal currently ingests primarily read usage events, so the `ADMIN_READ` and `DATA_READ` permission types must be enabled. Enable these audit logs for the following services: * `Identity and Access Management (IAM) API` * `Google Cloud Storage` * `Compute Engine API` * `Cloud Resource Manager API` * `Cloud SQL` ## Update your Service Account Custom Role When you update your custom role permissions (e.g., add organization resource manager permissions), you may notice a delay before changes take effect. This is a known GCP issue that you can read about in the [GCP documentation](https://cloud.google.com/iam/docs/creating-custom-roles#edit-role). You can wait for the updates to take effect, which may take around a day or more. Alternatively, if your permissions are taking a long time to update or you want to have the new permissions immediately, you can work around this issue by creating a new custom role from scratch that includes all your desired permissions and assigning it to your service account at the organization level. # Google Groups Source: https://docs.opal.dev/integrations/google-groups Connect your Google Groups organization to Opal to manage and review access. Opal's integration with Google Groups supports the following, and more: * Users can **request time-bounded access** to your Google groups. * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to Google groups. * Admins can **add resources from other Opal integrations** to an Google group so an Google group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * All access changes are tracked in a **permanent audit log** that can be logged to a Slack channel or exported to your favorite tools. ## Create a Google Groups app To get started, go to the **Catalog** page, and click **+ App**. Then, click on the Google Groups tile. 2312 You will see a form to be completed. Opal requires the following credentials in order to manage your Google Groups. ## 1. Configure a service account for Opal For Opal to manage your Google Groups on your behalf, you'll need a Google service account with proper permission scopes to retrieve metadata, such as group name and description, as well as to update the group. Follow the instructions for [creating a service account](/integrations/google#setting-up-a-service-account-for-opal) if you haven't already, and ensure it is granted the following scope: ```text text theme={null} https://www.googleapis.com/auth/admin.directory.group ``` If you only want to view user's access to groups and not grant/revoke them, then you can use the `admin.directory.group.readonly` scope. ## 2. Create Opal Google Group Create an Opal Google Group. We will use this Google Group's domain to import groups during syncs. ## 3. Fill out Opal form Back in Opal, fill in details about your Google Groups service account: * For Opal group email, enter the email of the Google group created in Step 2. * For Google Workspace admin email, enter the email of someone in your organization with admin privileges. * For domain, optionally enter the domain of the Google Workspace. If your Google Workspace has multiple domains, Google Groups will only import Google Groups associated with the domain you enter. By default, if this is not filled out, it is the domain of the Google Workspace admin email. Then, click to upload the downloaded JSON file for the created service account. If this step is successful, you've completed setting up the Google Groups app. ## 3. Import Google groups into Opal You can import your Google groups into Opal manually or automatically. *Note: Opal does not currently support syncing entire Organizational Units (OUs) or OU sub trees.* ### Manual import You can use the Opal UI to manually select which Google Groups to import into Opal. To do this, click on the "..." -> "Import Items" and then select your groups. 2312 2312 This will query Google Groups to list all groups in your account. From here, you can select which Google Groups you'd like to import into Opal. ### Automatic import You can configure your Google Groups to be auto-imported into Opal each time the Google Groups app is synced. To automatically import your groups from Google Groups: * In Google Groups, create a Google group called **Opal**. Any groups that you add as members of this group will automatically be imported into Opal. * In Opal, navigate to **Catalog** > Google Groups App * Click the **Edit** button on the top right, then under **Import Settings**, toggle the setting to **Auto-import tagged**. 2312 # Google IDP/HRIS Integration Source: https://docs.opal.dev/integrations/google-idphris-integration If your organization uses Google as an Identity Provider, you can additionally designate it as an [IDP/HRIS Integration](/docs/add-your-first-idphr-provider). Doing so allows Opal to sync your Google identities and their attributes, on top of syncing and managing access to entitlements (e.g. Google Groups, Google Workspace). ## Getting started Before you set up Google as your IDP, you must first create a Google Workspace App in Opal. To do this, please [follow the instructions here](/integrations/google-workspace). Next, set up Google as your IDP by following instructions here: * [Add Your First IDP/HR Provider](/docs/add-your-first-idphr-provider) * [Add Additional IDP/HR Providers](/docs/add-additional-idphr-providers) # Google Kubernetes Engine (GKE) Source: https://docs.opal.dev/integrations/google-kubernetes-engine-getting-started Learn how to manage user access with Google Kubernetes Engine (GKE). This integration allows you to manage user access with Google Kubernetes Engine (GKE). This relies on the Google Groups integration with GKE. It enables you to grant roles to the members of a group in Google Groups. ## Add a Google Groups App To get started, go to the **Inventory**, select the **+App** button at the top right of the screen, and choose the Google Groups tile. Opal requires multiple credentials in order to manage your Google Groups. ## 1. Configure a service account for Opal For Opal to manage your Google Groups on your behalf, you need to create a service account with proper permission scopes. * Open the [Service accounts page](https://console.developers.google.com/iam-admin/serviceaccounts). If prompted, select a project. * Click **+ Create Service Account**. Enter a name and description for the service account. When done click **Create**. * The **Service account permissions** section that follows is not required. Click **Continue**. * On the **Grant users access to this service account** screen, click **Done**. * Select the new service account. * Click the **Keys** tab. * Click the **Add key** drop-down menu, then select **Create new key**. * Select **JSON** as the **Key type** and click **Create**. * Your new public/private key pair is generated and downloaded to your machine. * Click **Close** on the **Private key saved to your computer** dialog, then return to the table of your service accounts. We then need to enable G Suite domain-wide delegation with the following steps: * Locate the newly-created service account in the table. Under **Actions**, click **Manage details**. * In the service account details, click **Show domain-wide delegation**, then ensure the **Enable G Suite Domain-wide Delegation checkbox** is checked. * If you haven't yet configured your app's OAuth consent, you must do so before you can enable domain-wide delegation. Follow the on-screen instructions to configure the OAuth consent screen, then repeat the above steps and re-check the checkbox. * Click **Save** to update the service account, and return to the table of service accounts. A new column, **Domain-wide delegation**, can be seen. Click **View Client ID** to obtain and make a note of the client ID. Now let's delegate domain-wide authority to your service account: To access user data on a Google Workspace domain, the service account that you created needs to be granted access by a super administrator for the domain. To delegate domain-wide authority to a service account, follow those steps: * From your Google Workspace domain's [Admin console](https://admin.google.com/), go to **Main menu > Security > API controls**. * In the** Domain wide delegation** pane, select **Manage Domain Wide Delegation**. * Click **Add new**. * In the **Client ID** field, enter the client ID obtained from the service account creation steps above. * In the **OAuth Scopes** field, enter the following scope: ``` https://www.googleapis.com/auth/admin.directory.group ``` * Click **Authorize**. Your service account now has domain-wide access to the Google Admin Directory API for all the users of your domain with admin access. ## 2. Enable the Google Admin API * Go to the [Google Admin API overview](https://console.cloud.google.com/apis/api/admin.googleapis.com/overview) in Google Cloud. * Click **Enable API**. ## 3. Create Opal group * Create a Google Group called **Opal**. This group can be used later to automatically import groups into Opal (outside of GKE) by adding them as members of this group. ## 4. Complete Opal form Back in Opal, fill in details about your Google Groups service account: * For Opal group email, you should enter the email of the Google group created above. * For Google Workspace admin email, you should enter the email of someone in your organization with at least `Groups Admin` privileges. If the service account is used outside of GKE, then `Super Admin` privileges are required on the admin email. * Then click to upload the downloaded JSON file for the created service account. If this step is successful, you have completed setting up the Google Groups server connection. ## Set up your GKE clusters ### 1. Enable Google Groups for RBAC In the [GKE console](https://console.cloud.google.com/kubernetes/list/), select a cluster and click **Edit.** * Look for the setting Google Groups for RBAC and click the Edit button. * Check **Enable Google Groups for RBAC.** * Complete the security group name with your domain name. * Click **Save changes.** ### 2. Create Google Groups for access levels Go to [Google Groups](https://groups.google.com/). * Create a group with the name gke-security-groups. Make sure the gke-security-groups group has the **View Members** permission for **Group Members**. * Create a group for cluster admin access level. Please use the following format for naming: opal\_CLUSTER\_NAME\_ACCESS\_LEVEL. Each group must have the **View Members** permission for **Group Members**. - Example: if your cluster name is Acme-prod, and access level is admin, the group name should be opal\_Acme-prod\_admin. * In the gke-security-groups group, click **Add member **and add the group we just created. ### 3. Define cluster role bindings in Kubernetes Next, assign the Google Group to a Kubernetes RBAC role. * Run `kubectl edit clusterrolebinding cluster-admin` * Edit the subjects array so it contains the following: ```yaml yaml theme={null} subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:masters - apiGroup: rbac.authorization.k8s.io kind: Group name: opal\_Acme-prod\[email protected] ``` Save and exit. ## Access your clusters in Opal The groups that were added under the gke-security-groups group will show up as Roles for your clusters. Users are then able to request access to the Roles defined earlier. Once the access is granted users will be able to run kubectl commands after connecting to the cluster with gcloud. # Google Workspace Source: https://docs.opal.dev/integrations/google-workspace Learn how to connect Google Workspace with Opal. Opal natively supports an integration with Google Workspace. This integration enables organizations to manage access to default and custom admin roles. 2312 ## Configuration 1. Go to **Inventory** and select **+ App** to add the Google Workspace App. 2312 2. For Opal to manage your Google Workspace on your behalf, you'll need to [create a Google service account](/integrations/google#setting-up-a-service-account-for-opal) with proper permission scopes to retrieve role and user information. Grant the service account the following scopes: [https://www.googleapis.com/auth/admin.directory.user](https://www.googleapis.com/auth/admin.directory.user), [https://www.googleapis.com/auth/admin.directory.rolemanagement](https://www.googleapis.com/auth/admin.directory.rolemanagement) The `admin.directory.user` scope is used to import and manage users, and the `admin.directory.rolemanagement` scope is used to import and manage roles. If you only want to view user's access and not grant/revoke their access, you can use the `admin.directory.user.readonly` and `admin.directory.rolemanagement.readonly` scopes instead. 3. Enable the [Admin SDK API](https://console.cloud.google.com/apis/library/admin.googleapis.com) in the project that the service account was created in. In Google, [role assignment privileges](https://support.google.com/a/answer/7519580?hl=en\&ref_topic=9832445\&sjid=14153642454757397359-NC) are only available to super administrator role. Opal needs the Google Workspace admin email to be a super administrator in order to import admin roles. 4. Return to Opal to finish the app creation form. **Google Workspace admin email** should be a user with the **Super Admin Role** in order for the service account to read role assignments. You can find super admins in the [admin console](https://admin.google.com/) under **Admin Roles** > **Super Admin**. This email is **not** your service account email. Use this same account for the **Google Workspace customer ID** [field](https://support.google.com/a/answer/10070793?hl=en\&src=supportwidget0\&authuser=0). ## Run app validation checks After you save your app, you can view existing sync issues from the **Setup** tab on the app detail page. Missing permissions and sync issues show in the **App Validations** section. Select the refresh icon to rerun validation checks. You can hover over the validation icons to learn why Opal needs a given permission. To correctly sync your app to Opal, ensure you address any sync errors, marked with the red ! icon. Inspect warnings on a case-by-case basis: warnings might impact features you’re not using and may be safely ignored, but this depends on your use case. # Grafana Source: https://docs.opal.dev/integrations/grafana Learn how to connect Opal to Grafana to manage access to Grafana folders, dashboards, roles, and teams. With the Grafana integration, you can manage observability platform access through Opal: * Allow users to request just-in-time access to Grafana folders, dashboards, and roles from the web and Slack * Set the right resource owners to delegate approvals to those with the most context * Configure day-one access to Grafana resources with groups from your identity provider * Automatically escalate and revoke privileged access based on on-call schedules, e.g., [PagerDuty](/integrations/pagerduty-oncall) or [Opsgenie](/integrations/opsgenie) ## Supported resources | Resource | Read | Grant and revoke access | Notes | | ------------------ | ---- | ----------------------- | ------------------------- | | Grafana Folders | ✔️ | ✔️ | | | Grafana Dashboards | ✔️ | ✔️ | | | Grafana Roles | ✔️ | ✔️ | Enterprise and Cloud only | | Grafana Teams | ✔️ | ✔️ | | ## Requirements * You must be an Opal admin. * Your Grafana instance must be version 10 or higher. * You must have a Grafana service account with the ability to generate service tokens, because this is how Opal authenticates with Grafana. * Be on version 1.1143.0 or greater, if you self-host Opal. ## 1. Create a Grafana service account and token In Grafana, add a service account by navigating to **Administration** > **Users and access** > **Service accounts** > **Add service account**. Give it a name (e.g., `Opal`), and add the **Role** based on instance type. * For OSS instances, add `Admin` role. * For Cloud and Enterprise, the service account must have the following permissions. If you prefer not to use the `Admin` role, you can create a custom role with these specific permissions. | Permission | Type | | ------------------------------ | ----------------------- | | `org.users:read` | Required | | `roles:read` | Required | | `users.roles:read` | Required | | `teams:read` | Optional | | `teams.permissions:read` | Optional | | `folders:read` | Optional | | `folders.permissions:read` | Optional | | `dashboards:read` | Optional | | `dashboards.permissions:read` | Optional | | `teams.permissions:write` | Optional | | `folders.permissions:write` | Optional | | `dashboards.permissions:write` | Optional | | `teams.roles:read` | Optional | | `users.roles:add` | Optional | | `users.roles:remove` | Optional | | `teams.roles:add` | Optional | | `teams.roles:remove` | Optional | Optional permissions control which resources Opal can read and manage. Missing optional permissions will limit which resource types are available to import. Grafana uses the `permissions:type:delegate` scope for role assignment actions by default. This means the service account can only assign permissions that are attached to it (or a subset of them) to users or groups. Next, generate a service account token to be used in the following step. ## 2. Create a Grafana app in Opal In Opal, go to **Inventory > +App** and select **Grafana** . Fill in the following fields and hit create. | Field | Value | | -------------------- | --------------------------------------------------------------------------------------------------- | | App admin | The team or user that should manage the Grafana app in Opal. | | Description | Let your end users know what they're requesting access to. | | Grafana instance URL | The base URL of your Grafana instance (e.g., `https://grafana.mycompany.com`). | | Instance type | Select **OSS**, **Enterprise**, or **Cloud** based on your Grafana deployment. | | Service token | The service account token you generated in [Step 1](#1-create-a-grafana-service-account-and-token). | Stack ID is a required field for cloud instances. You can go to `https://grafana.com/orgs/org-name/stacks` to get the value. ## 3. Import Grafana resources After creating the app, you can import resources from **... > Import items**. Users will now be able to request access to Grafana resources through Opal. ## User provisioning User provisioning is only available for **Enterprise** and **Cloud** instances with SCIM enabled. Follow the [Grafana SCIM provisioning guide](https://grafana.com/docs/grafana/latest/setup-grafana/configure-access/configure-scim-provisioning/#configure-scim-using-the-grafana-user-interface) to enable it. `Enable User Sync` is the required setting. Opal can automatically provision and deprovision users in Grafana. When provisioning is enabled, users who don't already have a Grafana account are automatically created when they are granted access to a Grafana resource. Deprovisioning removes users from Grafana when their access is revoked. This is configurable in **Setup > Edit > Provision Grafana users** and **Deprovision Grafana users**. For more details, see [User provisioning](/docs/user-provisioning). # Hubspot Source: https://docs.opal.dev/integrations/hubspot Learn how to connect Opal to Hubspot to manage access. With Opal's integration with HubSpot, you can manage access to **HubSpot users, teams, and roles (permission sets)**. * **End users** can request just-in-time, time-bound access to HubSpot roles and teams from Opal's catalog, Slack, or the CLI. * **Admins** can grant and revoke access, provision and deprovision HubSpot users, and delegate approvals with full audit trails. * **Auditors and security teams** can run access reviews over HubSpot roles and team membership, and see every change as an event. ## Requirements * You must be an Opal admin to set up the integration. * You need a HubSpot **Super Admin** to create the legacy app and its access token. * For self-hosted deployments, you must be on version `1.1179.0` or later. * Opal associates HubSpot users with Opal users through their **primary email address**. ## 1. Create a legacy app in HubSpot In HubSpot, go to **Settings > Integrations > Legacy Apps** and select **Create a legacy app**. Give the app a name (for example, `Opal`). On the **Scopes** tab, add the following scopes: ``` settings.billing.write (to remove users from paid roles) settings.users.read settings.users.teams.read settings.users.teams.write (to add and remove users from teams) settings.users.write ``` These scopes let Opal read your users, teams, and roles, and (if enabled) provision users, manage team membership, and assign roles. If you only want read-only visibility and access reviews, you can omit `settings.users.write`. **\[Optionally]** Add `settings.billing.write` if you want Opal to be able to deprovision users who hold paid seats. Without this scope, deprovisioning such a user fails with a clear error telling you to unassign the seat first. These scopes cover HubSpot's user and account settings only. **Opal cannot read your CRM data** — contacts, companies, deals, tickets, and marketing content are outside what this token can access. Finally, select **Create app** and copy the access token. ## 2. Create the HubSpot app in Opal In Opal, go to **Inventory > +App** and select the **HubSpot** tile. | Field | Value | | ------------------------ | ----------------------------------------- | | App name | A display name, for example `HubSpot` | | App admin | The Opal owner responsible for this app | | Description | Optional description shown in the catalog | | Private app access token | The token from Step 1 | | Visibility | Who can see this app in the catalog | Fill in the fields, then select **Create**. Opal validates the token and each scope immediately; results appear under the app's **Setup** tab (App Validations), and you can re-run them there any time. ## 3. Import assets After the first sync completes, select the app's **... menu > Import items** to bring your HubSpot teams and roles into the Inventory. Your HubSpot users appear under the app's **Accounts** tab automatically. ## User provisioning With provisioning enabled on the app, granting access to a person who doesn't yet have a HubSpot account creates one for them (they receive HubSpot's standard invite). If an account with the same email already exists, Opal uses it rather than creating a duplicate. With deprovisioning enabled, removing a user's last access removes their HubSpot account. Provisioning and deprovisioning are both opt-in settings on the app's edit page. ## Teams HubSpot teams are mapped to Opal Groups: membership syncs into Opal, and adding or removing members in Opal updates HubSpot. HubSpot's API has no way to remove a user from a single team. When Opal removes a team membership, it briefly clears the user's team list and immediately rebuilds the remaining memberships. During that short window the user may appear team-less in HubSpot; the next sync reconciles any difference. We would recommend making team changes all in one place. In order to deprovision a Super Admin from Opal, they must first be demoted in Hubspot. ## Roles HubSpot roles (permission sets) are available on\*\* HubSpot Enterprise plans\*\*. Two behaviors to know: * A HubSpot user holds **one role at a time** — assigning a role through Opal replaces the user's existing role, matching how HubSpot's own admin UI behaves. * Opal **refuses any assignment that would demote your account's last Super Admin**. A HubSpot account with zero Super Admins cannot be recovered from the HubSpot UI, so Opal fails that request with a clear error instead. (Super Admin itself can't be granted through the API — that always happens in HubSpot's settings.) ## FAQ The person has no HubSpot account with their Opal primary email. Either create their HubSpot user (or enable provisioning on the app) or correct the email mismatch, then retry. Roles require HubSpot Enterprise. Hubspot's role API is not available on other plans. Opal takes this into consideration on connection and continues syncing users and teams normally. Create a new token in HubSpot (Private Apps > your app > rotate), then paste it in the app's **Setup** tab in Opal and re-run validations. Opal tracks named permission sets. Editing a user's individual permissions through HubSpot's **Edit access** flow creates custom per-user permissions, not a permission set, and HubSpot's API does not expose those — so they don't appear in Opal. Use **Assign permission set** in HubSpot for changes you want reflected in Opal. User IDs, names, and email addresses; team names and membership; role names and assignments — the minimum needed to show who has access to what. No CRM records are read or stored. # Incident.io Source: https://docs.opal.dev/integrations/incidentio Learn how to connect Incident.io to Opal. With our Incident.io integration, you can sync on-call schedules in Opal as a nested group. We will automatically add whoever is currently on-call to all the resources associated with that group so they can effectively diagnose and solve live issues. Once they're no longer on-call, Opal will automatically remove their privileges. ## Supported resources | Resource | Read | Grant and revoke access | | ----------------------------- | ---- | ----------------------- | | Incident.io on-call rotations | ✔️ | | Opal does not provision access in incident.io itself—the integration uses a read-only API token. Instead, Opal automatically grants and revokes access to the resources associated with the on-call schedule group for whoever is currently on-call. Opal always syncs access for whoever is currently on-call, including current overrides. ## 1. Create a Incident.io token Opal requires a "read-only" Incident.io API token. Go to your incident.io dashboard and [create an API token](https://help.incident.io/articles/3838115863-our-api#where-can-i-find-the-api-keys-16). ## 2. Create Incident.io App in Opal In your inventory, click **+ App** and search for **incident.io**. Connect to the **incident.io** native integration and add all necessary information, including the API token you created in step 1. ## 3. Sync your Incident.io schedules You can find your on-call schedules as a group under the **Assets** tab. To sync, click **Import assets** to add your schedules into Opal for management. If you're importing schedules after your initial sync, remember to click **Sync Items** in the top right hand corner of the **Import assets** tab. # Atlassian Jira Source: https://docs.opal.dev/integrations/jira Learn how to connect your Opal instance with Jira. Opal's integration with Jira allows you to manage access to Jira groups. ## Supported resources | Resource | Read | Grant and revoke access | | ----------- | ---- | ----------------------- | | Jira users | ✔️ | ✔️ | | Jira groups | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version `1.1194.0`, if you self-host Opal ## 1. Create a Jira admin API key To allow Opal to access your Jira users and groups and grant permission to assign and remove users from groups, you must first create an [Admin API key](https://support.atlassian.com/organization-administration/docs/manage-an-organization-with-the-admin-apis/). In `admin.atlassian.com`, go to **Settings > Api Keys > Create API Key**. Select the following scopes: * `read:user:admin` * `read:directory:admin` ## 2. Create a Jira API token In `id.atlassian.com`, go to **Security > Api Tokens > Create API Tokens**. Select the following scopes: * `read:group:Jira` * `write:group:Jira` * `read:user:Jira` ## 3. Create Jira app in Opal In Opal, go to **Inventory** > **+App** and select **Jira**. Name your Jira app, provide a description, and enter the admin API key and API token you generated in step 1. Enter your Site Domain (e.g. yourcompany for yourcompany.atlassian.net). You will also need your Org ID, which is visible in the URL when you visit admin.atlassian.com: `https://admin.atlassian.com/o//...` And finally the admin email of the admin user that created the admin API key in step 1 After you save the app, you can import groups from **...** > **Import items**. *** # Jira Source: https://docs.opal.dev/integrations/jira-ticketing Learn how to connect your Opal instance with Jira. With Opal, you can sync your engineers' access requests with your Jira support tickets in order to grant access for the lifetime of the support ticket. When the ticket is closed (marked "Done"), access is also revoked. Opal supports Jia Software and Jira Service Management (JSM) projects. For JSM projects, [additional setup](/integrations/jira#jira-service-management) is required. ## Jira Cloud Use the following steps to integrate Jira with Opal if you use Jira Cloud. ### 1. Create a Jira API token Opal requires an administrator of your Jira workspace to create a [Jira API token](https://id.atlassian.com/manage-profile/security/api-tokens). Click **Create API token** and record the generated token. You'll use this in the next step. ### 2. Add your Jira details to Opal To enable the Jira integration in Opal, go to **Configuration > Settings > Productivity Integrations** in the Opal dashboard. 1. Select **Jira** and enter your Jira workspace name—e.g., if your Jira workspace is `https://jiraworkspace.atlassian.net`, enter `jiraworkspace`. 2. Enter the name of the Jira project you want to integrate. 3. Finally, enter the API token from the previous step. Once complete, you've successfully integrated Jira with Opal. You can now load and bind Jira tickets from the project name from the Jira workspace you specified. ## Jira Data Center Jira 8.14 or later is required. Use the following steps to integrate Jira with Opal if you use Jira Data Center. ### 1. Create a Personal Access Token Opal requires the administrator of your Jira workspace to create a Personal Access Token. Select your profile picture at the top right of the screen, then click **Personal Access Tokens**. Select **Create token**. Give your new token a name. Finally, click **Create** and record the generated token. This will be input in the next step. ### 2. Add your Jira details to Opal To enable the Jira integration in Opal, go to **Configuration > Settings > Productivity Integrations** in the Opal dashboard. 1. Select **Jira**, then **Self-hosted**. 2. Enter the URL of your Jira instance, e.g., `https://jira.acme-corp.com`. 3. Enter the name of the Jira project you want to integrate. 4. Finally, enter the Personal Access Token from the previous step. Once complete, you've successfully integrated Jira with Opal. You will be able to load and bind Jira tickets from the project name from the Jira workspace you specified. ## How it works For any access request, binding to a support ticket is optional. After Jira is integrated, when making a request, the option to "Expire access when ticket is closed" can be enabled. 2408 By clicking "Search for tickets", a list of Jira tickets will appear, only including tickets which are assigned to you in Jira (the Jira account which corresponds to your Opal email address). Click on any of these support tickets to attach the ticket to the access request. By default, an expiration time bound is required when making any access request. After the request is approved, access expiration is determined by the combination of this expiration time bound and the closure of the support ticket. Access will expire according to the minimum of these two events: if the ticket is closed before the expiration time bound is reached, access will be revoked when the ticket is closed. Similarly, if the expiration time bound is reached before the closure of the ticket, access is revoked when the expiration time bound is reached. If you want to attach an access request to a support ticket, without an expiration time bound, you may select "Indefinite" under the expiration options and bind the request to a support ticket. ## Jira Service Management Most functionality is the same between Jira Software and Jira Service Managent projects. The key differences are: 1. Ticket links in Opal to Jira Service Management requests will show the requestor-facing URL rather than the agent URL. This requires setting up a request type `Access Change - Opal`. 2. If the access requestor in Opal does not have a Jira account, Opal will attempt to create a Jira customer account. ### Jira Service Management Project Setup 1. **Create an Issue Type: `Service Request - Opal`** 2. **Create a Request Type `Access Change - Opal`.** Ensure that the Issue Type is `Service Request - Opal` Opal sets tickets to this request type in order to generate the requestor-facing URL. We recommend hiding this request type from the portal in order to separate Opal requests. 3. **Enable the `Request Type/Customer Request Type` and `Reporter` field on the screens associated with your Issue Type** In your Project Settings, go to `Screens` and find the one associated with your Issue Type (`Service Request - Opal`). Each operation may be associated with a different screen. If they are different, you will need to update each screen to include both fields. For Jira Cloud customers, the field is `Request Type`. For Jira Data-Center customers, the field is `Customer Request Type`. 4. **Enable allowing creation of portal-only accounts.** If an Opal email does not exist in Jira, we will attempt to create a customer account. ### Jira Data-Center - Enable portal-only accounts In your admin settings, go to `Configuration` to enable Public signup. Enable one of `Customers who have an account on this Jira Site` or `Anyone can raise a request on the customer portal` ### Jira Cloud - Enable portal-only accounts In your admin settings, under `Products` -> `Jira Service Management` -> `Customer Access`. Enable `Allow portal-only accounts to be created for new customers` . This is required in case the email address in Opal does not have a Jira account but would still like to make a request. When creating a ticket, Opal will set the requestor in Opal as the reporter in Jira. This will allow the user to see all their tickets in Jira. In the JSM project settings, set `Channel Access` to `Open` 4. **Enable service accounts to modify the `Reporter` field on a JSM ticket** In your Jira admin settings, go to `Work Items` -> `Permission Schemes` -> Click on the `Permissions` tab of associated with your JSM project Ensure that the service account used in Opal is granted the `Modify Reporter`permission through a `Project Role` or `Group` *** # Lark Source: https://docs.opal.dev/integrations/lark Learn how to connect Opal to Lark to send notifications and manage access requests. You can connect Opal to Lark to send interactive notifications to requesters and reviewers about access requests, expirations, and reviews. Reviewers can approve or deny requests directly from a Lark card. ## Requirements To set up the Lark integration, you must: * Be an Opal Admin * Have permission to create custom apps in the [Lark Developer Console](https://open.larksuite.com/) * Be on versions `1.1187.0` or later * Ask Opal team to enable the feature for you Opal connects to Lark over an outbound long connection (WebSocket), so self-hosted Opal instances don't need to expose a public endpoint to receive Lark events. ## 1. Create a custom app in Lark Go to the [Lark Developer Console](https://open.larksuite.com/) and create a new custom app named `Opal` or whatever name you want your bot to be. ### *Permissions and Scopes* | Scope | Purpose | | -------------------------- | ------------------------------------------------------------------- | | `im:message:send_as_bot` | Send notification cards, update them in place, and reply in threads | | `contact:user.id:readonly` | Match Opal users to Lark accounts by email | Lark Developer Console Permissions & Scopes page showing the im:message:send_as_bot and contact:user.id:readonly scopes added to the app ### *Events and Callbacks* * Select the **persistent connection** for Event Configuration and Callback Configuration * Under Callback Configuration, add the **Card callback communication** callback (`card.action.trigger`), which lets reviewers act on requests from card buttons. Leave the legacy `card.action.trigger_v1` callback unchecked. Lark Developer Console Add callback dialog with the Card callback communication (card.action.trigger) callback selected * Publish a version of the app so the permissions and events take effect. From the app's credentials page, record the **App ID** and **App Secret**. You'll use these in the next step. ## 2. Connect Lark to Opal In Opal, go to **Configuration** > **Settings** > **Productivity Integrations** and select the option to connect Lark. Fill in the following fields: | Field | Value | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Lark Domain | The Lark open platform base URL: `https://open.larksuite.com` for international tenants or `https://open.feishu.cn` for Feishu tenants. | | App ID | The App ID of the custom app you created, e.g., `cli_a1b2c3d4e5f6g7h8`. | | App Secret | The App Secret of the custom app you created. | Opal validates the credentials upon saving. Once connected, Opal matches your Opal users to Lark accounts by email address. Users without a matching Lark account don't receive Lark notifications; these skipped notifications are recorded in the audit trail. ## 3. Enable Lark notifications Users control their own Lark notifications from their notification preferences in Opal. To receive Opal messages in Lark, users must enable the **Lark** notification type in their account settings. ## What users can do in Lark ### Act on requests from cards Opal sends interactive cards as direct messages: * **Reviewers** receive a card for each access request with the request details and buttons to **Approve** or **Deny** (and **Admin Approve**, where applicable). When a request is resolved—whether from Lark or the web app—the card updates in place to show the outcome. * **Requesters** receive status updates as their request is approved or denied, and comments on a request appear as threaded replies on the card. * **Users with expiring access** receive an expiration alert with an **Extend** button. ## Limitations * Linked reviewer channels and linked audit channels are not supported for Lark; Opal sends Lark notifications as direct messages only. * Admin-paused notification categories (such as access revocation, request cancellation, and access expiration) also apply to Lark notifications. # LDAP Source: https://docs.opal.dev/integrations/ldap Connect your LDAP server to use Opal to manage and review access. Opal's integration with LDAP supports the following, and more: * Users can **request time-bounded access** to your LDAP groups. * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to LDAP groups. * Admins can **add resources from other Opal integrations** to an LDAP group so an LDAP group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * All access changes are tracked in a **permanent audit log** that can be logged to a Slack channel or exported to your favorite tools. ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ----------- | ---- | ----------------------- | ------------------------ | | LDAP Groups | ✔️ | ✔️ | ✔️ | ## 1. Create an app in Opal To get started, go to the **Inventory** page, click **+ App** at the top right. Then, click on the **LDAP** tile. 2312 Opal requires the following credentials in order to manage access to your LDAP groups. ## 2. Configure an LDAP binder account for Opal In order for Opal to manage your LDAP server on your behalf, you need to create an LDAP service account for your server with proper permission scopes. In OpenLDAP, this is also known as a binder account. ## 3. Finish creating Opal app Back in Opal, fill in details about your LDAP server and binder account: | Field | Value | Note | | --------------------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Server hostname | The hostname of your LDAP server. | Ensure your LDAP hostname is reachable from the instance that hosts your Opal app. | | Server port | The port for your LDAP server. | | | Base distinguished name | The Distinguished Name (DN) of the OU from which Opal should begin directory searches. | | | Root username | The username for the LDAP binder account you created in the previous step. | | | Root password | The password for the LDAP binder account you created in the previous step. | | | Group attribute unique identifier | The name of the attribute that your LDAP server uses to uniquely identity groups. | This is often **entryUUID**. | Save the app. If this step is successful, you have completed setting up the LDAP server connection. # Linear Source: https://docs.opal.dev/integrations/linear Learn how to connect your Opal instance with Linear. Opal's integration with Linear allows you to manage access to Linear organizations, projects and teams. ## Supported resources | Resource | Read | Manage access levels | Grant and revoke access | Available in Risk Center | | ------------------- | ---- | -------------------- | ----------------------- | ------------------------ | | Linear teams | ✔️ | | ✔️ | ✔️ | | Linear projects | ✔️ | | ✔️ | ✔️ | | Linear organization | ✔️ | ✔️ | | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version `1.1215.0`, if you self-host Opal ## 1. Create a Linear admin personal API key To allow Opal to access and manage your Linear organization, teams and projects, you must first create a [Personal API key](https://linear.app/docs/api-and-webhooks#api-keys). You will only be shown the API key on creation, so save it for the next steps. In **Settings > Security & Access** under **Personal API keys** select the **New API key** option. Select the following permissions: * `Read` * `Write` * `Admin` ## 2. Create Linear app in Opal In Opal, go to **Inventory** > **+App** and select **Linear**. Name your Linear app, provide a description, and enter the personal API key you generated in step 1. After you save the app, you can import organizations, projects and teams from **...** > **Import items**. ## Organization management Once a user is synced from Linear into Opal, admins can manage the user's access level (admin, member or guest). Opal does not support user provisioning for Linear, so users cannot be added or removed from the Linear organization through Opal. # Linear Source: https://docs.opal.dev/integrations/linear-ticketing With Opal's Linear integration, you can sync access requests with your Linear support tickets to grant access for the lifetime of the support ticket. When the ticket is closed (marked *Done*, *Canceled*, or *Trashed*), access is also revoked. ## 1. Create a Linear Personal API key To enable the Linear integration, your Linear workspace administrator should [create a Personal API key](https://linear.app/settings/account/security). Enter a label for the key, click **Create Key** and save the generated key. You'll use it in the following step. ## 2. Add your Linear Personal API key to Opal In the Opal dashboard, go to the **Configuration** > **Settings** > **Productivity Integrations**. Select **Linear** and enter the API key from the previous step. Once complete, you've successfully integrated Linear with Opal. You can then load and bind Linear issues. ## Access requests with Linear tickets For any access request, binding to a support ticket is optional. After you integrate Linear, when making a request, you can enable the option to **Expire access when ticket is closed**. 2408 When you select **Search for tickets**, a list of Linear issues populates, only including issues assigned to you in Linear, based on the Linear account which corresponds to your Opal email address. Click on any of these support tickets to attach the ticket to the access request. By default, an expiration time bound is required when making any access request. After the request is approved, access expiration is determined by the combination of this expiration time bound and the closure of the support ticket. Access will expire according to the minimum of these two events: if the ticket is closed before the expiration time bound is reached, access will be revoked when the ticket is closed. Similarly, if the expiration time bound is reached before the closure of the ticket, access is revoked when the expiration time bound is reached. If you want to attach an access request to a support ticket, without an expiration time bound, you may select **Indefinite** under the expiration options and bind the request to a support ticket # NetSuite Source: https://docs.opal.dev/integrations/netsuite Learn how to connect your NetSuite instance with Opal to manage and review access. Opal natively supports an integration with NetSuite. This integration enables organizations to manage access to NetSuite roles. Opal cannot modify Administrator roles due to NetSuite API limitations. ## Supported resources and functionality | Resource | Read | Grant and revoke access | Available in Risk Center | | -------------- | ---- | ----------------------- | ------------------------ | | NetSuite Roles | ✔️ | ✔️ | ✔️ | Opal syncs all active NetSuite roles and employees who have login access enabled (`Give Access = true`). ## Getting Started In Opal, go to the **Inventory**, select the **+App** icon, and select **NetSuite**. ## 1. Generate a key pair The NetSuite integration uses OAuth 2.0 Client Credentials (machine-to-machine) with a certificate-based JWT. You need to generate a key pair and register the public certificate in NetSuite. Run the following commands to generate an EC key pair (ES256): ```bash theme={null} openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem openssl req -new -x509 -key private_key.pem -out certificate.pem -days 730 -subj "/CN=OpalIntegration" ``` Keep `private_key.pem` — you will upload it to Opal. You will upload `certificate.pem` to NetSuite in the next step. ## 2. Enable required NetSuite features 1. In NetSuite, go to **Setup > Company > Enable Features**. 2. Click the **SuiteCloud** tab. 3. Under **SuiteTalk**, check **REST Web Services**. NetSuite SuiteCloud tab with REST Web Services checked under SuiteTalk 4. Under **Manage Authentication**, check **OAuth 2.0**. NetSuite SuiteCloud tab with OAuth 2.0 checked under Manage Authentication 5. Save. ## 3. Create a NetSuite integration record 1. In NetSuite, go to **Setup > Integration > Manage Integrations > New**. 2. Fill in the following settings: | Setting | Value | | --------------------------------------------- | --------- | | Name | `Opal` | | State | Enabled | | Client Credentials (Machine to Machine) Grant | Checked | | Token-Based Authentication | Unchecked | | Authorization Code Grant | Unchecked | | REST Web Services (scope) | Checked | NetSuite integration record showing REST Web Services scope checked under OAuth 2.0 3. Save the record. Copy the **Client ID** — you will need it later. ## 4. Upload the certificate 1. Still in NetSuite, go to **Setup > Integration > OAuth 2.0 Client Credentials (M2M) Setup**. 2. Set **Entity** to the service account user or role that Opal will authenticate as. 3. Upload the `certificate.pem` file you generated in step 1. 4. Save the record. Copy the **Certificate ID** — you will need it later. Every time you regenerate the certificate, NetSuite requires a new mapping row in this setup. Ensure the new row is bound to the correct user and the **Opal Integration** role. ## 5. Create the Opal Integration role Create a dedicated NetSuite role with the minimum permissions required by Opal. 1. Go to **Setup > Users/Roles > Manage Roles > New**. 2. Set the **Name** to `Opal Integration`. 3. Under the **Permissions** tab, add the following permissions and save the role: **Setup** | Permission | Level | | ------------------------------------ | ----- | | Access Token Management | Full | | Bulk Manage Roles | Full | | Log in using Access Tokens | Full | | Log in using OAuth 2.0 Access Tokens | Full | | REST Web Services | Full | | Records Catalog | View | | User Access Tokens | Full | **Lists** | Permission | Level | | --------------- | ----- | | Employee Record | View | | Employees | View | **Reports** | Permission | Level | | ----------------------- | ----- | | SuiteAnalytics Workbook | Edit | ## 6. Assign the role to the integration certificate 1. Go to **Setup > Users/Roles > Manage Roles** and open the **Opal Integration** role. 2. Under the **Users** subtab, add the employee or service account that owns the certificate created in step 4. Alternatively, assign the role directly on the employee record: **Lists > Employees**, open the employee, go to the **Access** subtab, and add the **Opal Integration** role. ## 7. Connect NetSuite in Opal Back in the Opal New App form, fill in the following fields: | Field | Value | | -------------- | ---------------------------------------------------------------------- | | Account ID | Your NetSuite account ID (e.g. `1234567` or `1234567_SB1` for sandbox) | | Client ID | The Client ID from the integration record created in step 3 | | Certificate ID | The Certificate ID from step 4 | | Private Key | Upload the `private_key.pem` file generated in step 1 | Click **Connect**. Opal will validate the credentials and verify the **Opal Integration** role has the required permissions. Any missing permissions appear in the **App Validations** section of the app's **Setup** tab. # Notion Source: https://docs.opal.dev/integrations/notion Learn how to connect your Opal instance with Notion. With Opal's Notion integration, you can: * Create audit tickets for access requests * Propagate access to Opal using Notion ticket ## Configuration requirements To connect Notion with Opal, you must: * Be an Opal Admin ## 1. Retrieve Notion integration token [Create an integration in Notion](https://www.notion.so/my-integrations), then copy the [internal integration token](https://www.notion.com/help/add-and-manage-connections-with-the-api). ## 2. Configure Notion databases For each ticketing project, you should maintain a separate Notion database using the following schema. Connect each database to the connection you used to create the Notion integration token. | Field | Type | | ----------------------------------- | ---------------------------------------- | | Title | Text | | Status | Status (Open, In Progress, Completed) | | Reporter | Person | | Priority | Multi-select (Low, Medium, High, Urgent) | | Created time *(Notion default)* | Created time | | Last edited time *(Notion default)* | Last edited time | For example, to use Notion to track both auditing and propagation tickets, you'd create separate databases for **Opal \<> Propagate Integration** and **Opal \<> Audit Integration**, and connect them to the same integration. ## 3. Create Notion app in Opal In Opal, go to **Configuration** > **Organization Settings** > **Productivity Integrations** and select **Connect** next to **Notion**. Enter the integration token you saved from step 1, then save the integration. ## Create audit tickets To create audit tickets in Notion for every access request, enable **Notion** as a ticketing provider under **Configuration** > **Organization Settings** > **Access Requests**. You'll see the Notion databases you configured show as options after saving the Notion token. ## Propagate access with tickets See the ticket propagation guide to learn how to propagate access with tickets connected to your Notion databases. *** # Okta Source: https://docs.opal.dev/integrations/okta Connect your Okta instance to use Opal to manage and review access. Opal's integration with Okta lets you manage access to your Okta groups, applications, users, and admin roles. Our integration supports the following, and more: * Users can **request time-bounded access** to your Okta groups, apps, and admin roles * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to Okta groups, apps, and admin roles * Admins can **add resources from other Opal integrations** to an Okta group so an Okta group's members can automatically gain birthright access to, for example, a GitHub repo, AWS IAM role, etc. * All access changes are tracked in a **permanent audit log** that can notify a Slack channel or be exported to your favorite tools. ## Supported resources | Resource | Read | Grant and revoke access | Included in [Risk Center](/docs/least-privilege-posture-management) | | ------------------------------ | ---- | ----------------------- | ------------------------------------------------------------------- | | Okta Users and user attributes | ✔️ | ✔️ | ✔️ | | Okta Groups | ✔️ | ✔️ | ✔️ | | Okta Group Rules | ✔️ | ✔️ | ✔️ | | Okta Roles | ✔️ | ✔️ | ✔️ | | Okta Apps | ✔️ | ✔️ | ✔️ | Note that the Okta integration does not support creating Okta users, groups, roles, or apps. It also does not yet support syncing Okta API tokens nor Okta OAuth apps. ## Additional capabilities The Okta integration also supports automatic **real-time sync**, which syncs groups and resources as soon as they're updated in Okta. You can also run automatic [validation checks](/integrations/okta#run-app-validation-checks) to confirm your Okta sync is working properly. The Okta integration also supports user account [deprovisioning](/docs/user-provisioning). ### Real-time sync Opal automatically enables real-time sync for Okta integrations. When access changes occur in Okta, Opal automatically syncs and reflects the change. Real-time sync includes: * New groups * New apps * Users added to groups or apps * New apps added and removed to groups ## Getting Started To get started, go to the **Inventory** page, click **+ App** at the top right. Then, click on the Okta Directory tile. 2312 You will see a form to be completed. Opal requires credentials to connect to your Okta instance. There are two supported authentication methods: * **OAuth App (recommended):** Opal authenticates as a dedicated Okta API Services app using a private key. No shared user account needed. * **API token:** Opal authenticates as a real Okta user account using a static SSWS token. This method is still fully supported but requires a dedicated Okta user and manual token rotation. Opal supports authenticating via an Okta **API Services** app using the OAuth 2.0 Client Credentials flow. This is the recommended method for new integrations. For a full overview of the Okta OAuth for service apps flow, see the [Okta guide](https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/). ### 1. Create an API Services app in Okta In Okta Admin Console, go to **Applications** > **Applications** > **Create App Integration**, and select **API Services**. Give the app a name (e.g. "Opal") and save. In the app, navigate to **General** > **General Settings**. Find *Proof of possession* and make sure *Require Demonstrating Proof of Possession (DPoP) header in token requests* is unchecked. Opal does not currently support DPoP. ### 2. Generate a private key In the app, navigate to **General** > **Client Credentials** and click **Edit**. Switch the Client authentication method to **Public key / Private key** and click **Add key** > **Generate new key**. In the *Private key - Copy this!* dialog, select the **PEM** tab. Save the private key and the Key ID (`kid`) shown alongside the key. This key will only be shown once. ### 3. Grant OAuth scopes On the app's **Okta API Scopes** tab, grant the scopes that match what you want Opal to manage. | Required OAuth scopes | What Opal can manage | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | `okta.users.read`, `okta.users.manage`, `okta.groups.read`, `okta.groups.manage`, `okta.apps.read`, `okta.apps.manage`, `okta.logs.read`, `okta.schemas.read`, `okta.roles.read`, `okta.roles.manage` | Groups, apps, users, system log, custom attributes, and admin roles | | `okta.users.read`, `okta.users.manage`, `okta.groups.read`, `okta.groups.manage`, `okta.apps.read`, `okta.apps.manage`, `okta.logs.read`, `okta.schemas.read` | Groups, apps, and users | | `okta.users.read`, `okta.users.manage`, `okta.groups.read`, `okta.groups.manage` | Groups and users | ### 4. Assign an admin role to the service app OAuth scopes and admin roles are two separate and complementary authorization layers in Okta: **Scopes** define what actions the service app is allowed to request (e.g. read users, manage groups), while **Admin roles** define which resources those actions actually apply to. Both must be configured — granting scopes without an admin role causes Okta to accept the credentials and issue a token, but API calls will return empty results with no error. Assign the minimum role needed for what you want Opal to manage. Following the principle of least privilege, avoid granting Super Admin unless you specifically need Opal to manage admin role assignments. On the service app detail page, go to the **Admin roles** > **Edit assignments** and assign the appropriate role: | What you want Opal to manage | Admin role required on the service app | | ---------------------------------------------------------------------------------- | -------------------------------------- | | Groups, apps, users, system log, custom attributes, and Okta admin role management | **Super Admin** | | Groups, apps, users, system log, and custom attributes | **Org Admin** | | Apps only (view and assign users to apps) | **App Admin** | | Groups (view members and manage group memberships) | **Group Admin** | | Group memberships only | **Group Membership Admin** | Okta also supports [custom admin roles](https://help.okta.com/oie/en-us/content/topics/security/custom-admin-role/about-creating-custom-admin-roles.htm) that let you scope permissions to specific resource sets rather than granting a broad built-in role. A custom role with the "Identity and Access Management" resource set is accepted by Opal as equivalent to Super Admin for admin role management. Opal verifies the admin role assigned to the service app as part of its validation checks. This verification itself requires the service app to have Super Admin or a custom role with IAM permissions. If the service app has neither, Opal will show a warning that it could not verify admin role assignments, but core sync (users, groups, apps) will continue to work with Org Admin. ### 5. Upload credentials in Opal In the Opal **New App** form, select **OAuth App** as the authentication method and fill in: * **Organization hostname:** the hostname of your Okta org (e.g. `mydomain.okta.com`) * **Client ID:** the Client ID shown on the app's General tab * **Private Key PEM:** the PEM key copied in Step 2 * **Key ID:** the `kid` value from Step 2 (optional if you have only one key registered) The API token method is still fully supported. Use it if your Okta configuration does not permit API Services apps, or if you are migrating an existing integration. If you have an existing Opal Okta integration using an API token, you can switch to OAuth App at any time from the **Setup** tab on the app detail page. Your saved API token is preserved as a fallback, and Opal only switches to OAuth once you save the new credentials. To remove the saved token after switching, check **Remove saved API token** when saving. ### 1. Configure an API token for Opal Opal uses an Okta API token to connect to your Okta instance. We suggest that you create a separate Okta user that creates and owns the API token. This lets you customize the permission scopes available to Opal and ensure that these scopes don't change. Additionally, Okta will log any access changes that Opal pushes to Okta under the separate account's name. To create an account, [follow these steps](https://help.okta.com/en-us/Content/Topics/users-groups-profiles/usgp-add-users.htm) for adding a new user on Okta. Next, grant the appropriate permissions to your new account based on what you'd like Opal to manage. To do this, navigate to **Security** -> **Administrators**, and add the desired Okta Admin Roles from the following table: | Required Okta admin roles | What Opal can manage | | -------------------------------------------------- | ----------------------------------------- | | Super Admin | Okta groups, apps, users, and admin roles | | Organization Admin, Application Admin, Group Admin | Okta groups, apps, and users | | Group Membership Admin, Read-Only Admin | Okta groups and users | Finally, generate an API token as the new account: * Log in to the new account on Okta. * Navigate to **Security** -> **API**. * Click on the **Tokens** tab. * Click **Create Token** on the top left. * Record the generated token. ### 2. Upload your Okta credentials Back in the Opal New App form, fill in details about your Okta organization: * **Organization hostname:** enter the URL hostname of your Okta organization (e.g. "mydomain.okta.com") * **API token:** enter the API token you just created If this step is successful, you've completed setting up the Okta App. *** ## Run app validation checks After you save your app, you can view existing sync issues from the **Setup** tab on the app detail page. Missing permissions and sync issues show in the **App Validations** section. Select the refresh icon to rerun validation checks. You can hover over the validation icons to learn why Opal needs a given permission. To correctly sync your app to Opal, ensure you address any sync errors, marked with the red ! icon. Inspect warnings on a case-by-case basis: warnings might impact features you're not using and may be safely ignored, but this depends on your use case. ## Automatic import (Okta groups only) You can configure your Okta groups to be auto-imported into Opal each time the Okta app is synced. Sync happens throughout the day, approximately once an hour. Sync can also be manually triggered by an admin by clicking the Sync button in the top-right of the app. 1. In Okta, create a custom group profile attribute `opal` * Navigate to Directory > Profile Editor, then select the desired group profile and click **Add attribute**. * Fill out the form as follows, and click Save: 2. Set the `opal` attribute to `true` for all Okta groups you want to auto-import. 3. Enable auto-import in Opal * Click into your newly-created Okta app by going to **Apps** on the left sidebar. * Click the Edit button * Next to **Import Settings > Import setting**. Toggle the setting to **Auto-import tagged**. 2312 *** # Import Okta apps Source: https://docs.opal.dev/integrations/okta-apps Learn how to import your Okta apps into Opal. You can import your Okta apps into Opal using group assignment or manually in the Opal dashboard. ## Auto-import via group assignment In most cases, organizations should import applications via Okta groups, since it is the easiest way to import existing group and application relationships. Opal automatically imports Okta Apps that are assigned to Okta Groups. If an Okta Group is imported into Opal and there are Okta Apps assigned to the group, Opal automatically imports these apps. ## Manual import In the Opal dashboard, you can manually select which Okta Apps to import into Opal. To do this, in **Inventory**, find your Okta App and click **...** > **Import items**. 2312 From here, you can filter by type **Okta App**, and select any apps that you want to import into Opal. 2312 ## Use Okta apps in Opal After you import your Okta apps, you can: **1. Set up self-service just-in-time approval workflows** * Using the Opal Web UI or `/opal` Slackbot, employees can browse for Okta Apps and groups to request. Our product lets resource owners easily approve or deny requests one-click from Slack, enabling more decentralized and scalable access management. **2. Grant event-based access to sensitive Okta Apps** * Using Opal, you can grant users event-based access to sensitive Okta Apps by tying access grants to on-call rotations or support tickets. # Okta CIAM Source: https://docs.opal.dev/integrations/okta-ciam Connect your Okta CIAM instance to Opal to manage and review access. You can connect Okta CIAM to Opal to manage and review manage permissions for privileged identities in your Okta tenant. ## Overview Since Okta CIAM allows for a single Okta tenant to contain your internal workforce identities *(i.e. privileged identities)* and external customer identities *(i.e. customer PII)*, this Opal connector allows you to achieve separation between the two. This is done through a profile attribute filter to ensure Opal syncs and manages the appropriate subset of users and groups. ## Supported resources | Resource | Read | Grant and revoke access | | -------------------------------- | ---- | ----------------------- | | Okta Users and User Attributions | ✔️ | ✔️ | | Okta Groups | ✔️ | ✔️ | | Okta Roles | ✔️ | ✔️ | With Opal's Okta CIAM integration: * Users can **request time-bounded access** to your Okta groups and admin roles * Admins can **add resources from other Opal integrations** to an Okta group so members of that Okta group can automatically gain birthright access to resources (e.g. Github repository, AWS IAM role) * All access changes are tracked in a **permanent audit log** that can notify a Slack channel or be exported to your favorite tools. * User account deprovisioning is supported The Okta CIAM integration does not currently support real-time sync. ## Requirements To connect Opal with Okta CIAM, you must first: * Be an [Opal Admin](/docs/roles-in-opal) * [Configure an Okta API token](/integrations/okta#1-configure-an-api-token-for-opal) for Opal * Add a custom profile attribute `opal_okta_ciam_managed` and set it to `True` for all users and groups you want Opal to manage ## 1. Setting up the attribute In your Okta Admin Console, create a custom profile attribute `opal_okta_ciam_managed` boolean. * For [Users](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-add-custom-user-attributes.htm): Add `opal_okta_ciam_managed` boolean to your user profile * For [Groups](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-add-custom-group-attributes.htm): Add `opal_okta_ciam_managed` boolean to your group profile Set `opal_okta_ciam_managed = true` on all internal workforce users and groups you want Opal to manage. Ensure that `opal_okta_ciam_managed = true` is only applied to internal workforce identities and groups you want Opal to manage. Ensure customer accounts and customer-facing groups **do not** have this attribute. Opal will only sync and display users and groups where `opal_okta_ciam_managed` is explicitly set to `true`. ## 2. Create Opal app In Opal, go to the **Inventory** >**+ App**, then select **Okta CIAM**. ## 3. Configure the app Fill in the following fields about your Okta CIAM integration. | Field | Value | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | App name | Identifiable name for the app, e.g., Oracle Fusion Cloud | | App admin | The [Owner](/docs/opal-101#owners) of the app | | Description | A description to provide additional context to requesting users. | | Visibility | **No visibility restrictions** makes the item visible to all users who can view the parent app. **Restrict to groups** restricts the visibility to groups you specify, Opal admins, resource admins, and users granted access. | Using information about your Okta organization, fill in the following fields. | Field | Value | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Organization hostname | The [Okta domain](https://developer.okta.com/docs/guides/find-your-domain/main/) for your Okta organization (e.g. mydomain.okta.com). | | API Token | The Okta API token you configured for Opal | After you save the app, you can [run app validation checks](/integrations/okta#run-app-validation-checks). You may also [configure auto-import Okta groups](/integrations/okta#automatic-import-okta-groups-only). # Okta IDP/HRIS Integration Source: https://docs.opal.dev/integrations/okta-idphris-integration If your organization uses Okta as an Identity Provider, you can additionally designate it as an [IDP/HRIS Integration](/docs/add-your-first-idphr-provider). Doing so allows Opal to sync your Okta identities and their attributes, on top of syncing and managing access to entitlements (e.g. Okta Groups, Okta Apps, etc). ## Getting started Before you set up Okta as your IDP, you must first create an Okta Directory App in Opal. To do this, [follow the Okta integration guide](/integrations/okta). Next, set up Okta as your IDP using the following guides: * [Add Your First IDP/HR Provider](/docs/add-your-first-idphr-provider) * [Add Additional IDP/HR Providers](/docs/add-additional-idphr-providers) Your Okta IDP setup is now complete. ## Import user manager You can configure Opal to automatically import user manager information from Okta and match the imported string to an Opal user. There are two ways to do this: 1. Set the `manager` attribute in an Okta user's profile to an Okta user's email. If there is a matching Opal user associated with this Okta user email, the user's manager in Opal will be updated to this user. 2. Set the `managerId` attribute in an Okta user's profile to an Okta user ID. If there is a matching Opal user associated with this Okta user ID, the user's manager in Opal will be updated to this user. The Okta user ID can be found by navigating to the user from the **People** section in a browser, then observing the Okta user ID at the end of the URL (below, the Okta user ID is `00u1835qib5TKa8Ri5d7`). *** # Okta rate limits Source: https://docs.opal.dev/integrations/okta-rate-limits Learn about rate limits that may affect your Okta integration with Opal. ## Okta per-token rate limits Rate limits hit with Opal's Okta integration are usually Okta's per-token rate limit. This token and rate limit is specific to Opal. If you hit this rate limit, you still have quota remaining for this particular endpoint for other tokens and flows. In Okta, you can adjust the rate limit quota allocated to Opal. The [recommended default](https://developer.okta.com/docs/reference/rate-limits/#api-rate-limits-by-api-token-or-oauth-2-0-app) is 50%. Opal tries to slow down as we approach our allocated rate limit based on the headers Okta returns. However, the information contained in headers can be limited, and it is expected that rate limits will be occasionally violated. Rate limit violations are handled gracefully on Opal's side with retries—no data is lost, and requests are retried as rate limits expire. ## Mitigation steps Okta token rate limit violations are not pressing concerns, but you can increase the rate limit allocated to Opal to reduce the volume of errors and speed up sync. You can also contact Okta about a potential increase to your overall rate limit. If you're concerned about your overall Okta rate limit utilization, you can reduce the rate limit allocated to Opal. This frees up your global rate limit, but it will not reduce the volume of errors and will slow down Opal sync. # OpenAI Platform Source: https://docs.opal.dev/integrations/openai Learn how to connect Opal with OpenAI Platform. Opal's integration with OpenAI Platform allows you to manage access to OpenAI projects and service accounts. ## Supported resources | Resource | Read | Grant and revoke access | | ------------------------------- | ---- | ----------------------- | | OpenAI Projects | ✔️ | ✔️ | | OpenAI Project Service Accounts | ✔️ | | ## Requirements Before you begin, you must: * Be an Opal Admin ## 1. Retrieve an API token in OpenAI To allow Opal to access to your OpenAI projects and accounts, you must first create an [admin API key](https://platform.openai.com/docs/api-reference/admin-api-keys). Use the following permissions scopes for the API token. | Resource | Permissions | | ----------------------- | ----------- | | Fine-tuning Checkpoints | None | | Audit Logs Scope | None | | Management API Scope | Write | | Usage API Scope | None | Save the token, as you won't be able to retrieve it later. ## 2. Create OpenAI app in Opal In Opal, go to **Inventory** > **+App** and select **OpenAI Platform**. Name your OpenAI Platform app, provide a description, and enter the token you generated in step 1. After you save the app, you can import roles from **...** > **Import items**. *** # Opsgenie Source: https://docs.opal.dev/integrations/opsgenie Learn how to connect Opal to Opsgenie. Opsgenie is being [sunset](https://www.atlassian.com/software/opsgenie/migration) and moved over the Jira Service Management. Using Opal's Opsgenie integration, you can link on-call schedules with Opal groups. The user who is currently on-call is added to all permissions associated with that group so they can effectively diagnose and solve live issues. When they're no longer on-call, Opal automatically removes their privileges. Opal always syncs access for whoever is currently on-call, including current overrides. ## 1. Create an Opsgenie token Opal requires a "read-only" Opsgenie API token. Go to your Opsgenie dashboard and create an API token by following [the official Opsgenie documentation](https://support.atlassian.com/opsgenie/docs/api-key-management/). ## 2. Add your token to Opal To enable the Opsgenie integration, in the Opal dashboard, go to **Configuration** > **Settings** > **Productivity Integrations**. Select **Opsgenie** and enter your API token from the previous step. ## 3. Create an Opsgenie-synced group Any group in Opal can be synchronized with an Opsgenie schedule. Edit the group and select schedules to sync: 2262 # Oracle Fusion Cloud Source: https://docs.opal.dev/integrations/oracle-fusion-cloud Learn how to connect your Oracle Fusion Cloud instance to Opal to manage and review access. You can connect Oracle Fusion Cloud to Opal to manage and review access. ## Supported resources | Resource | Read | Grant and revoke access | | ------------------------- | ---- | ----------------------- | | Oracle Fusion Cloud Roles | ✔️ | ✔️ | With Opal's Oracle Fusion Cloud integration: * Users can request time-bounded access to your Oracle Fusion Cloud roles * Admins can sync Oracle Fusion Cloud users and grant or revoke access to [roles](https://docs.oracle.com/en/cloud/saas/risk-management-and-compliance/24d/fasor/roles-overview.html). This includes default roles such as `Accounts Payable Invoice Supervisor` and`Academic Advisor`, and custom roles created in Oracle Fusion Cloud. ## Requirements To connect Opal with Oracle Fusion Cloud, you must first: * Be an [Opal Admin](/docs/roles-in-opal) * [Create](https://docs.oracle.com/en/cloud/get-started/subscriptions-cloud/csgsg/create-users-and-assign-roles.html#GUID-F614798D-15EA-406C-95CF-72050D085C2E) or have a pre-existing user with the [IT Manager](https://docs.oracle.com/en/cloud/saas/applications-common/24c/farca/Use_Cases.html) `ORA_FND_IT_SECURITY_MANAGER_JOB`role in Oracle Fusion Cloud. ## 1. Create Opal app In Opal, go to the **Inventory** >**+ App**, then select **Oracle Fusion Cloud**. ## 2. Configure the app Fill in the following fields. | Field | Value | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | App name | Identifiable name for the app, e.g., Oracle Fusion Cloud | | App admin | The [Owner](/docs/opal-101#owners) of the app | | Description | A description to provide additional context to requesting users. | | Visibility | **No visibility restrictions** makes the item visible to all users who can view the parent app. **Restrict to groups** restricts the visibility to groups you specify, Opal admins, resource admins, and users granted access. | Using the credentials of your IT Manager user, fill in the following fields. | Field | Value | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Oracle Fusion Base URL | The [base URL](https://docs.oracle.com/en/cloud/saas/enterprise-performance-management-common/prest/fdmee_url_structure.html) for your Oracle Fusion Cloud instance. | | Username | The username for the user with the IT Manager role in Oracle Fusion Cloud | | Password | The password for the user with the IT Manager role in Oracle Fusion Cloud | After you save the app, you can import users from **...> Import Items** *** # Integrations Source: https://docs.opal.dev/integrations/overview Integrate your tools with Opal to enable deterministic enforcement Opal connects to your identity providers, infrastructure, and everyday tools so you can grant, request, and review access to everything from one place. Opal integrates with 55+ identity, infrastructure, and productivity tools, including Okta, PagerDuty, and Datadog. ### Identity provider and HR system integrations Connect your Okta instance to manage and review access. Connect Google Groups and Google Workspace to manage and review access. Connect Opal to Azure to manage access. Connect your Active Directory server to manage and review access. Connect your Workday tenant as a source of truth for users and entitlements. ### Cloud infrastructure integrations Connect your AWS infrastructure to manage and review access. Connect your GCP infrastructure to manage and review access. Connect your Snowflake account to manage and review access, including Opal SCIM provisioning to automatically create users. ### App integrations Connect your Opal instance with Anthropic. Connect your Opal instance with Astra. Connect your Opal instance with Confluence. Connect your Opal instance with Jira. Connect Opal to Clickhouse to manage access. Connect Opal with Coupa. Connect Opal with Cursor. Connect your Opal instance to Databricks. Connect your Datadog instance to manage and review access. Connect your Opal instance with Devin AI. Connect Opal with Docusign to manage and review access. Connect your Duo instance to manage and review access. Connect Opal to your GitHub organization to manage and review access. Connect Opal to your GitHub Enterprise account to manage and review access. Connect Opal to your GitLab instance or group to manage and review access. Connect Opal to Grafana to manage access to folders, dashboards, roles, and teams. Connect Opal to Hubspot to manage access. Connect Incident.io to Opal. Connect your LDAP server to manage and review access. Connect your Opal instance with Linear. Connect your NetSuite instance to manage and review access. Connect your Okta CIAM instance to manage and review access. Connect Opal with OpenAI Platform. Connect your Oracle Fusion Cloud instance to manage and review access. Connect your PagerDuty account to manage and review access. Connect Rootly to Opal. Connect your Salesforce account to manage and review access. Connect your Slack workspace to manage and review access to Slack users and groups. Connect your Opal instance with Tableau. Connect Opal to Tailscale to manage SSH access within your tailnet, including Opal just-in-time access requests. Connect Opal to Teleport to manage access to Teleport groups, including just-in-time access to roles. Connect Opal to Twingate to manage access to groups and resources, including just-in-time access requests. Manage access to Workday groups and roles. Connect Opal to Zendesk to manage access to groups, organizations, and custom roles. Connect your Zoom instance to manage and review access. ### Productivity integrations #### Communication integrations Connect Opal to Google Chat. Connect Opal to Lark to send notifications and manage access requests. Connect your Opal instance to Slack to send notifications about access requests. #### Ticketing integrations Connect your Opal instance with Freshservice. Sync access requests with your Jira tickets. Sync access requests with your Linear tickets. Connect your Opal instance with Notion. Connect Opal with Risotto. Connect ServiceNow to Opal. Connect your Opal instance with Shortcut. #### On-call integrations Link on-call schedules with Opal groups. Legacy integration. Import on-call schedules from PagerDuty. Legacy integration. Don't see what you're looking for? [Build your own connector](/docs/how-to-create-your-own-connector) using Opal's custom integration framework. # PagerDuty Source: https://docs.opal.dev/integrations/pagerduty-entitlements Learn how to connect your PagerDuty accounts with Opal to manage and review access. Opal natively supports an integration with PagerDuty. This integration enables organizations to manage access to groups and roles, and sync Pagerduty on-call schedules to any group. You can now sync on-call schedules in Opal's native PagerDuty app as a nested group! There are no breaking changes to our legacy [Pagerduty productivity integration](/integrations/pagerduty-oncall). Use the following steps for managing access to PagerDuty roles. ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ----------------- | ---- | ----------------------- | ------------------------ | | PagerDuty Role | ✔️ | ✔️ | ✔️ | | On call Schedules | ✔️ | ✔️ | ✔️ | You can grant access to PagerDuty roles using the native integration. The PagerDuty integration also supports user account [deprovisioning](/docs/user-provisioning). ## 1. Create app in Opal In Opal, go to the **Inventory**, click on the **+ App** icon, and select the **PagerDuty** app. ## 2. Create a service account for Opal Opal requires an API Access Key to manage your PagerDuty on your behalf. To set up an API Access Key: 1. In PagerDuty, go to **Integrations > API Access Keys > Create New API Key**. 2. Set the description to Opal, leave the read-only checkbox disabled, then select **Create Key Set**. You will receive an API Key with full access. 3. Save the API Key, as it will only be visible once. ## 3. Save your Opal app After you create the service account, go back to Opal and fill in details about your app—the name, description, and visibility. Enter the API Key from the previous step in the **PagerDuty API Key** field, then select **Create**. After you save the app, in the **Details** tab, you can manage default settings, such as: * **Admin**: Who is the owner who can manage configurations * **Description**: What is the human understandable description for the role * **Webhook for access changes**: Opal can push events to a webhook rather than connecting to the end system * **Import setting**: Opal can either import all or manually import resources * **Default import visibility**: Resources can be either global or limited access (based on groups) *** ## Syncing your On-Call Schedules You find and use on-call schedules as a group directly under the **Assets** tab. If you wish to sync it to a group in Opal, you can add the group as a nested group to the on-call schedule group by clicking **+ Groups**. # PagerDuty Source: https://docs.opal.dev/integrations/pagerduty-oncall Learn how to connect PagerDuty to Opal. This is a legacy integration, as we have moved towards streamlining our on-call integrations as of November 2025. There are no breaking changes; You will still be able to import OnCall schedules directly from the PagerDuty Native App using nested groups. Use this guide to connect PagerDuty to Opal as a productivity integration. To connect PagerDuty as an app integration, see the [app integration guide](/integrations/pagerduty-entitlements) instead. With our PagerDuty integration, you can link on-call schedules with Opal groups. We will automatically add whoever is currently on-call to all the resources associated with that group so they can effectively diagnose and solve live issues. Once they're no longer on-call, Opal will automatically remove their privileges. Opal always syncs access for whoever is currently on-call, including current overrides. ## 1. Create a PagerDuty token Opal requires a "read-only" PagerDuty API token. Go to your PagerDuty dashboard and create an API token by following [the official PagerDuty documentation](https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key). ## 2. Add your token to Opal To enable the PagerDuty integration, in the Opal dashboard, go to **Configuration** > **Settings** > **Productivity Integrations**. Select **PagerDuty** and enter your API token from the previous step. ## 3. Create a PagerDuty group Any group in Opal can be synchronized with a PagerDuty schedule. Edit the group and select schedules to sync. # Risotto Source: https://docs.opal.dev/integrations/risotto Learn how to connect Opal in Risotto. ## Overview [Risotto](https://tryrisotto.com) is a conversational access request tool that lets employees request access to applications directly through Slack. When you connect Opal as Risotto's access governance provider, Risotto submits those requests to Opal for Opal to perform the grant while employees continue requesting conversationally in Slack. This is a guide to **configure Opal in your Risotto environment**. The Opal integration is currently in **Beta** in Risotto. Contact your Risotto representative to have it enabled for your organization. ### What this integration enables With Opal connected as Risotto's governance provider, Risotto can: * Submit access requests to Opal on behalf of users, for applications you route to Opal * Let Opal evaluate its own approval policies (reviewers, just-in-time duration, segregation-of-duty rules) * Receive the outcome of a request (approved, denied, fulfilled) and reflect it back in the Slack thread and ticket * Link requesters and reviewers directly to the request in Opal ## Prerequisites Before connecting, make sure you have: 1. Risotto Admin Access 2. Opal admin access -- Required to create the API key and register an Events Streaming destination. 3. Resources already configured in Opal — The applications you want Risotto to route should already exist in Opal with their owners and request policies set up. Risotto talks to Opal Cloud at `https://api.opal.dev/v1` by default. You only need to override this if your Opal instance uses a non-default host. Opal API keys carry the permissions of the user or service account that created them. Create the key from a dedicated service account scoped to the minimum resources Risotto needs, and store it in a secret manager. *** ## 1. Create an Opal API key In your Opal admin console, go to **Settings > API Tokens** and create a new token. Give it a descriptive name (e.g., `Risotto`). Copy the generated token immediately and store it securely — you'll paste it into Risotto in the next step. ## 2. Connect Opal in Risotto In your Risotto dashboard, go to **Settings > Integrations > All** and open the **Opal** panel. Fill in the connection fields: * **API key** *(required)* — The token you copied in Step 1 (`sk-…`). Risotto validates it when you save. * **Base URL** *(optional)* — Defaults to `https://api.opal.dev/v1`. Override this only if your Opal instance uses a non-default host. * **Portal URL** *(optional)* — Defaults to `https://app.opal.dev`. This is the user-facing Opal dashboard URL that Risotto uses for **View in Opal** deep links. Leave blank to use the Opal Cloud default. Toggle **Enabled** to start routing requests to Opal. While off, any Opal-shaped access rules fall back to direct fulfillment — so you can finish configuration before going live. When you **Save**, Risotto immediately validates the API key against Opal. If validation fails, the form surfaces the error (e.g., invalid token or unreachable host) so you can correct it. ## 3. Set up inbound resolution (Events Streaming) Risotto learns of request outcome through Opal's **Events Streaming** (webhooks), and needs to be configured so approvals and grants flow back to Slack in near real time. Back in Risotto's Opal panel, copy the **Webhook URL** that Risotto generated for your organization in the **Events Streaming** section. In Opal, go to **Settings > Events**, add a new Events Streaming destination and paste the Webhook URL as the destination endpoint. It should generated a signing secret. Input this signing secret into the **Webhook signing secret** field in Risotto's Opal panel and **Save**. Risotto uses this secret to verify (via HMAC) that every inbound event genuinely came from Opal. To disable HMAC verification, clear the **Webhook signing secret** field and save. You can only configure a maximum of 3 event streams in Opal. Learn more about [event streaming](/docs/events-streaming.mdx). ## 4. Route applications to Opal Access governance is configured **per application**. For each application you want Opal to fulfill, open the application in Risotto, route its access to **Opal**, and choose the matching Opal resource. When an application is routed to Opal, Risotto keeps a shadow copy of the relevant access configuration in sync so it can present the right options to requesters and dispatch correctly. Applications you don't route to Opal continue to use your existing access rules. **Opal does not emit an event when access expires.** Risotto compensates with an adaptive reconciliation backstop that periodically re-checks the state of outstanding grants in Opal. No extra configuration is required — it runs automatically once Opal is connected. *** ## End-user experience ### What the requester sees in Slack 1. The employee asks Risotto for access to an Opal-managed application in Slack, as usual. 2. Risotto collects any details Opal needs (such as a business justification or the specific access level) before submitting, so the request isn't bounced back for missing information. 3. Risotto confirms it has submitted the request to Opal and posts a **View in Opal** link so the requester can follow the approval there. 4. When Opal approves and grants (or denies) the request, Risotto updates the same Slack thread with the outcome. ### What reviewers see Reviewers are notified and act inside **Opal**, using their existing approval policies — Risotto does not change who approves or how. The request appears in Opal as an on-behalf-of request submitted by Risotto for the named employee, with the justification and access level the user provided in Slack. ### What "View in Opal" links do The **View in Opal** link deep-links to the specific access request in your Opal tenant, using the Portal URL configured in the Opal panel (defaulting to `https://app.opal.dev`). Requesters use it to track status; reviewers and admins use it to approve, deny, or audit the request. Because Opal remains the system of record, the full approval history and any time-bound expiration live in Opal. ## Troubleshooting * Verify the API key is valid (`sk-…`) and hasn't been revoked in Opal. * Leave **Base URL** blank unless Opal directed you to a non-default host; if set, confirm it's reachable. * Make sure the key's account has permission to create access requests on behalf of others. * Confirm the **Enabled** toggle is on — while it's off, Opal-shaped rules fall back to direct fulfillment. * Confirm the application is routed to Opal and mapped to the correct Opal resource. * Verify the Opal Events Streaming destination points at the **Webhook URL** from Risotto's Opal panel. * Confirm the **Webhook signing secret** in Risotto matches the one Opal generated. If unsure, rotate the secret in Opal and re-enter it in Risotto. * Remember that expirations are reconciled on a schedule rather than via webhook, so an expired grant may take a short time to reflect. # Rootly Source: https://docs.opal.dev/integrations/rootly Learn how to connect Rootly to Opal. With our Rootly integration, you can sync on-call schedules in Opal as a nested group. We will automatically add whoever is currently on-call to all the resources associated with that group so they can effectively diagnose and solve live issues. Once they're no longer on-call, Opal will automatically remove their privileges. ## Supported resources | Resource | Read | Grant and revoke access | | ------------------------ | ---- | ----------------------- | | Rootly on-call schedules | ✔️ | | Opal does not provision access in Rootly itself—the integration is read-only. Instead, Opal automatically grants and revokes access to the resources associated with the on-call schedule group for whoever is currently on-call. Opal always syncs access for whoever is currently on-call, including current overrides. ## 1. Create a Rootly API key Opal requires a Rootly API key. In your Rootly dashboard, go to **Organization Settings** > **API Keys** > **Generate New API Key**. See the [Rootly API documentation](https://docs.rootly.com/api-reference/overview) for more details. ## 2. Create Rootly App in Opal In your inventory, click **+ App** and search for **Rootly**. Connect to the **Rootly** native integration and add all necessary information, including the API key you created in step 1. ## 3. Sync your Rootly schedules You can find your on-call schedules as a group under the **Assets** tab. To sync, click **Import assets** to add your schedules into Opal for management. If you're importing schedules after your initial sync, remember to click **Sync Items** in the top right hand corner of the **Import assets** tab. # Salesforce Source: https://docs.opal.dev/integrations/salesforce Learn how to connect your Salesforce accounts with Opal to manage and review access. Opal natively supports an integration with Salesforce. This integration enables organizations to manage access to permission sets, profiles, and roles. ## Supported resources and functionality | Resource | Read | Grant and revoke access | Available in Risk Center | | -------------------- | ---- | ----------------------- | ------------------------ | | SFDC Permission Sets | ✔️ | ✔️ | ✔️ | | SFDC Profiles | ✔️ | ✔️ | ✔️ | | SFDC Roles | ✔️ | ✔️ | ✔️ | The Salesforce integration also supports user account [provisioning and deprovisioning](/docs/user-provisioning). ## 1. Create app in Opal In Opal, go to the **Inventory**, select the **+App** icon, and go to the **Salesforce** app. ## 2. Create a service account for Opal Opal requires a service account to manage your Salesforce on your behalf. Follow these instructions: 1. In Salesforce, open **Setup > Platform Tools > Apps > App Manager > New Connected App** (top right). Use the following settings. NOTE: These apps must be **Connected Apps**, if you do not see the option to create a Connected App, ensure [creation of connected apps is enabled](https://help.salesforce.com/s/articleView?id=xcloud.connected_app_create_basics.htm\&type=5). | Setting | Value | | --------------------- | --------------------------------------------------------------------- | | Name | `Opal` | | API name | `opal` | | Enable OAuth Settings | Enabled | | Callback URL | `https://auth.opal.dev`(for on-prem `https://auth..com`) | | Scopes | Manage User Data via APIs Perform requests at any time | Save the app, then copy the **Consumer Key** and **Consumer Secret**. Click **Manage > Edit Policies**. Under **Oauth Policies > Permitted Users**, select **All users may self-authorize**. Under **IP Relaxation**, select **Relax IP restrictions**. Save these settings. 2. On the left menu, open **Setup > Administration > Users > Profiles**, and create a new profile for Opal. We recommend using the Existing Profile **Minimum Access - Salesforce** and setting the Profile Name to **Opal Integration**. 3. On the following page, select **Edit** and ensure the profile has the following permissions: * API Enabled * Assign Permission Sets * Manage Internal Users * Manage Profiles and Permission Sets * Manage Roles * View all Profiles * View all Users * View Roles and Role Hierarchy * View Setup and Configuration The Opal integration will be prohibited from assigning any profile with the **Modify All Data** permission (e.g. System Administrator) unless it also has that permission, so enable **Modify All Data**. * Modify All Data 4. In **Setup > Administration > Users > Users**, create a new user. Select the **Salesforce** User License and the **Opal Integration** profile you created. You must use a real email address to complete account activation; save the username. Note that the username and email address can differ, but we advise using the same value. Finally, set all other the required fields to any values; e.g., set Last Name to **Opal**. 5. Open the account activation email and set a long, 32+ random character password—think of this as an API key. For the security question, choose a different long 32+ random character random value. Save the password. 6. Log in to the service account and click the user profile avatar in the top right of the page. Copy the Salesforce hostname listed in the dropdown, and save it. ## 3. Add your credentials in Opal After you create the service account, go back to Opal and input the user's credentials and the required fields using the values you saved in the previous steps. *** # ServiceNow Source: https://docs.opal.dev/integrations/servicenow Learn how to connect ServiceNow to Opal. With Opal, you can create audit tickets in ServiceNow for your engineers' access requests, link access requests to existing ServiceNow tickets, and propagate access using ServiceNow tickets. See the guide to [ticket propagation](/docs/ticket-propagation) for more detail, and follow this guide to configure your ServiceNow integration. ## 1. Create a ServiceNow service user To enable the ServiceNow integration, Opal requires the administrator of your ServiceNow instance to create a service user. In ServiceNow, go to the **Users** section by clicking **All**, then select **Users and Groups** > **Users**. Select the **Web service access only** checkbox. The User ID and password will be used as inputs in the next step. At minimum, the service user needs `catalog_admin` and `sn_request_write` roles, available via the [ITSM Roles plugin](https://docs.servicenow.com/en-US/bundle/vancouver-it-service-management/page/product/service-catalog-management/task/request-itsm-roles-rm.html), to create and update entries in the requests (`sc_request`) and items (`sc_req_item`) tables. You'll need to save their **User ID** and **password** to create the ServiceNow integration in Opal. ## 2. Create a catalog item for Opal Create a catalog item under any catalog to bucket Opal requests. A request will be made for this catalog item for every request made in Opal. The state of the request will also reflect the approval state of the Opal request. Optionally, you may add variables associated with this catalog item, and Opal will post the relevant metadata about the access request. Any or all of these variables can be added: | Variable Name | Description | | -------------------- | ---------------------------------------------------------------------------------------- | | `reason` | The reason for which the user requested the item | | `app` | The app for the group or resource requested | | `requested_duration` | The duration of the request in **minutes** | | `item_type` | Either `RESOURCE` or `GROUP` | | `role` | The role for which the item was requested | | `requester` | The **email** of the requester | | `requester_user` | The **Sys ID** of the ServiceNow user who made the request | | `requested_for` | The **email** of the user for whom the request was made | | `requested_for_user` | The **Sys ID** of the ServiceNow user for whom the request was made (empty if not found) | | `item` | The name of the item requested | | `item_id` | The Opal ID of the item requested | Once created, you will need to provide the **Sys ID of the catalog item** to Opal during setup. You can copy the Sys ID from this dropdown: ## 3. Create an OAuth application To allow Opal to authenticate with your ServiceNow instance to perform automated API operations, create an OAuth application by going to **System OAuth > Application Registry**. Click **New** and proceed to **"Create an OAuth API endpoint for external clients".** The fields on this application can be customized to your liking. Just be sure to record down the **Client ID** and **Client Secret** for creating the integration in Opal in the next step. ## 4. Add your ServiceNow credentials to Opal To enable the ServiceNow integration, go to the **Configuration > Settings** panel in Opal. Click **Productivity Integrations**, then select **ServiceNow** and enter the information created from the previous step. You need your ServiceNow hostname, Sys ID from step 2, username and password of the service account from step 1, and Client ID and Client Secret from step 3. See the [guide to ticketing providers](/docs/ticket-propagation) to learn about how to use the integration. You can create audit tickets on every request, reference existing tickets, or propagate access using tickets. ## Request behavior Opal's integration with ServiceNow will create a request (REQ) to mirror each request in Opal. Each request will have a requested item (RITM) for each item in the Opal request. In this example, we requested two items in Opal, which reflects as this request in ServiceNow: Each requested item will be made for the "Opal" catalog item created during setup, and variables for the item will be attached to give more description to the item requested. # Amazon Web Services (AWS) Source: https://docs.opal.dev/integrations/setting-up-your-aws-organization-in-opal Connect your AWS infrastructure to use Opal to manage and review access. Opal's *Amazon Web Services* app lets you manage access to your AWS IAM Roles, EC2 instances, EKS instances, and RDS databases across AWS accounts, as well as your AWS IAM Identity Center groups and permission sets. Our integration supports the following, and more: * Users can **request time-bounded access** to your IAM roles, EC2 instances, EKS instances, RDS databases, and Identity Center permission sets. * Auditors can **initiate access reviews** that assign managers or group admins to periodically review users with long-lived access to AWS resources. * All access changes are tracked in a **permanent audit log** that can notify a Slack channel or be exported to your favorite tools. ## Supported resources | Resource | Read | Grant and revoke access | Connect Opal user sessions to resource | Included in [Risk Center](/docs/least-privilege-posture-management) | | ----------------------------------------- | ---- | ----------------------- | -------------------------------------- | ------------------------------------------------------------------- | | AWS Identity Center Groups | ✔️ | ✔️ | | ✔️ | | AWS Account | ✔️ | | | ✔️ | | AWS Identity Center Role (permission set) | ✔️ | ✔️ | | ✔️ | | AWS IAM Role | ✔️ | ✔️ | ✔️ | ✔️ | | AWS EC2 | ✔️ | ✔️ | ✔️ | ✔️ | | AWS EKS | ✔️ | ✔️ | ✔️ | ✔️ | | AWS RDS MySQL Instance | ✔️ | ✔️ | ✔️ | ✔️ | | AWS RDS Postgres Instance | ✔️ | ✔️ | ✔️ | ✔️ | | AWS RDS MySQL/PSQL Clusters (Aurora) | ✔️ | ✔️ | ✔️ | ✔️ | | AWS Organizational Units | ✔️ | | | ✔️ | Note that the AWS integration does **not** support syncing IAM Groups. ## Authentication model The following shows a high-level summary of how Opal authenticates for two major workflows: 1. Synchronizing your AWS Organizations resources in Opal 2. Granting Opal users sessions to AWS resources ## Requirements To configure your AWS organization in Opal, you must: * Be an Opal Admin * Have an AWS Management or Delegated Administrator account * Set up an OIDC provider in Opal, if you want to use Opal to manage IAM roles, EC2 instances, EKS instances, or RDS databases. Setting up an OpenID Connect (OIDC) provider in Opal is required to use Opal to manage IAM roles, EC2 instances, EKS instances, or RDS databases. Opal uses OIDC to authenticate users when they start an AWS session with these accounts. This adds an extra layer of security by preventing Opal from being able to give access to users that aren't registered with your Identity Provider. To configure an OIDC identity provider: 1. Register Opal with your OIDC provider to receive a **Client ID** and **Client Secret** that will be used to establish a trust relationship between Opal and your OIDC provider. 2. Use the callback URL `https://{{YOUR_OPAL_BASE_URL}/callback/oidc`, substituting in your Opal base URL, which is usually `app.opal.dev` for Opal Cloud organizations. For example, `https://app.opal.dev/callback/oidc`. To learn more about obtaining these credentials, refer to your OIDC documentation -- for example, see [Okta OIDC docs](https://help.okta.com/en-us/Content/Topics/Apps/Apps_App_Integration_Wizard_OIDC.htm) or [Google OIDC docs](https://developers.google.com/identity/openid-connect/openid-connect). If you use Okta, set application type to **Web Application**. In Opal, go to the **Configuration > Settings** tab at the bottom of the left sidebar. 3. Find the **OIDC Provider Settings** setting under **AWS Settings**. Select **Configure**. 4. Enter the Client ID, Client Secret, and Issuer URL from your OIDC provider. 5. Save the Client ID and Issuer URLs, as you'll need them in a subsequent step. OIDC Provider Settings screenshot ## 1. Find your External ID 1. In Opal, go to the **Inventory** and select the **+ App** button in the top right corner to create a new app. Select **Amazon Web Services**. 2. Save the **External ID**, which is unique to your Opal organization and is necessary for the following step. Opal uses this to [safeguard Opal’s third-party access to your data](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html). ## 2. Configure your AWS management account If your Opal instance is self-hosted, follow the [AWS setup for self-hosted Opal guide](/integrations/aws-integration-on-prem-setup) to set up the trust policy for the **OpalIngester** IAM role. Once complete, return to these instructions to add the permissions policy for the role and complete the following steps. In order for Opal to manage access to your AWS infrastructure, you must configure an IAM Role in your AWS management account. In the AWS console, create a new IAM role called **OpalIngester**. Give your role the following trust policy, replacing `${EXTERNAL_ID}` with the External ID from the *Create App* page in Opal: ```json Opal-hosted theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::602387580983:root" }, "Action": [ "sts:AssumeRole" ], "Condition": { "StringEquals": { "sts:ExternalId": "${EXTERNAL_ID}" } } }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::602387580983:root" }, "Action": [ "sts:TagSession" ] } ] } ``` Next, attach the following permissions policy to the role. Note that some permissions are always required, some are required to manage non-Identity Center resources (EC2 instances, IAM roles, etc.), and some are required specifically to manage Identity Center-managed roles. Use the **Explanation** tab to learn how to customize permissions for your use case, and the **Policy** tab for a policy you can copy and paste. ```json Explanation theme={null} // IMPORTANT: This snippet is meant for documenting why Opal requires each of // these permissions, allowing you to customize based on your needs. For a valid // pasteable policy document, open the "Policy" tab above. { "Version": "2012-10-17", "Statement": [ { // This statement is required in all setups. // The Opal AWS integration will not function correctly without it. "Sid": "AlwaysRequired", "Effect": "Allow", "Action": [ // Required to show information about your AWS accounts in Opal. "organizations:DescribeAccount", "organizations:ListAccounts", "organizations:ListTagsForResource", // Required for Opal to validate what access it has "iam:SimulatePrincipalPolicy" // Required to ingest AWS Organizational Units (OUs). "organizations:ListRoots", "organizations:ListOrganizationalUnitsForParent", "organizations:ListAccountsForParent", "organizations:DescribeOrganizationalUnit", // Required to determine which AWS regions are enabled in this account. // Without this permission, Opal falls back to scanning all known AWS regions, // which may include regions not reachable from your network and can cause sync errors. "account:ListRegions" ], "Resource": "_" }, { // Required to import your non-Identity Center resources from this account. // This includes IAM Roles, EC2 instances, EKS clusters, and RDS databases. // This statement can be removed for configurations that opt out of AWS // Organization management in Opal. "Sid": "RequiredToManageNonIdentityCenterResourcesInThisAccount", "Effect": "Allow", "Action": [ // Required to show IAM Roles in Opal. "iam:ListRoleTags", "iam:ListRoles", "iam:GetRolePolicy", "iam:GetPolicy", "iam:GetRole", // Required to show RDS databases in Opal "rds:DescribeDBInstances", "rds:DescribeDBClusters", // Required to show EC2 instances in Opal "ec2:DescribeInstances", // Required to show EKS clusters in Opal "eks:DescribeCluster", "eks:ListClusters", ], "Resource": "_" }, { // Required to manage your Identity Center resources from all accounts. This statement // can be removed for configurations that opt out of Identity Center management in Opal. "Sid": "RequiredToManageIdentityCenter", "Effect": "Allow", "Action": [ // Required to show Permission sets and their assignments in Opal "sso:ListPermissionSets", "sso:ListPermissionSetsProvisionedToAccount", "sso:DescribePermissionSet", "sso:ListAccountAssignments", // Required to populate user access to permission sets "sso:CreateAccountAssignment", "sso:DeleteAccountAssignment", "sso:DescribeAccountAssignmentCreationStatus", "sso:DescribeAccountAssignmentDeletionStatus", // Required to provision Identity Center group memberships "identitystore:ListUsers", "identitystore:ListGroups", "identitystore:DescribeUser", "identitystore:DescribeGroup", "identitystore:GetGroupMembershipId", "identitystore:ListGroupMemberships", "identitystore:CreateGroupMembership", "identitystore:DeleteGroupMembership" ], }, { // Required to provision permission sets. When account assignments are modified, // iam:GetSAMLProvider is called under the hood by AWS. Permission Set provisioning // operations do not function without this permission. This statement can be removed // for configurations that opt out of IAM Identity Center management in Opal. "Sid": "RequiredForIdentityCenterIAMRoleProvisioning", "Effect": "Allow", "Action": "iam:GetSAMLProvider", "Resource": "arn:aws:iam::_:saml-provider/AWSSSO\__\_DO_NOT_DELETE" }, { // Required to provision permission sets in Management account. Replace // ${MANAGEMENT_ACCOUNT_ID} with your AWS management account ID so that this // statement only takes effect for a role in the management account. This // statement can be removed for configurations that do not require provisioning // IAM Identity Center permission sets in their management account with Opal. "Sid": "RequiredForIdentityCenterIAMRoleProvisioningInManagementAccount", "Effect": "Allow", "Action": [ // In certain cases, you may also need to add iam:UpdateSAMLProvider here. // See the AWS documentation for more detail: // https://docs.aws.amazon.com/singlesignon/latest/userguide/iam-auth-access-using-id-policies.html#policyexamplemanageconnecteddirectory "iam:AttachRolePolicy", "iam:PutRolePolicy", "iam:GetRole", "iam:CreateRole", "iam:UpdateRole", "iam:DeleteRole", "iam:ListRoles", "iam:ListRolePolicies", "iam:ListAttachedRolePolicies" ], "Resource": [ "arn:aws:iam::_:role/aws-reserved/sso.amazonaws.com/_" ], "Condition": { "StringEquals": { "aws:PrincipalAccount": "${MANAGEMENT_ACCOUNT_ID}" } } }, { // Required to display usage data in Opal. "Sid": "RequiredToReadUsageData", "Effect": "Allow", "Action": [ "cloudtrail:LookupEvents", // Required for viewing EC2 usage data "ssm:DescribeSessions" ], "Resource": "_" }, ] } ``` ```json Policy theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "AlwaysRequired", "Effect": "Allow", "Action": [ "organizations:DescribeAccount", "organizations:ListAccounts", "organizations:ListTagsForResource", "iam:SimulatePrincipalPolicy", "organizations:ListRoots", "organizations:ListOrganizationalUnitsForParent", "organizations:ListAccountsForParent", "organizations:DescribeOrganizationalUnit", "account:ListRegions" ], "Resource": "*" }, { "Sid": "RequiredToManageNonIdentityCenterResourcesInThisAccount", "Effect": "Allow", "Action": [ "iam:ListRoleTags", "iam:ListRoles", "iam:ListPolicies", "iam:ListAttachedRolePolicies", "iam:GetRolePolicy", "iam:GetPolicy", "iam:GetRole", "rds:DescribeDBInstances", "rds:DescribeDBClusters", "ec2:DescribeInstances", "eks:DescribeCluster", "eks:ListClusters", "ssm:DescribeSessions" ], "Resource": "*" }, { "Sid": "RequiredToManageIdentityCenter", "Effect": "Allow", "Action": [ "sso:ListPermissionSets", "sso:ListPermissionSetsProvisionedToAccount", "sso:DescribePermissionSet", "sso:ListAccountAssignments", "sso:CreateAccountAssignment", "sso:DeleteAccountAssignment", "sso:DescribeAccountAssignmentCreationStatus", "sso:DescribeAccountAssignmentDeletionStatus", "identitystore:ListUsers", "identitystore:ListGroups", "identitystore:DescribeUser", "identitystore:DescribeGroup", "identitystore:GetGroupMembershipId", "identitystore:ListGroupMemberships", "identitystore:CreateGroupMembership", "identitystore:DeleteGroupMembership" ], "Resource": "*" }, { "Sid": "RequiredForIdentityCenterIAMRoleProvisioning", "Effect": "Allow", "Action": "iam:GetSAMLProvider", "Resource": "arn:aws:iam::*:saml-provider/AWSSSO_*_DO_NOT_DELETE" }, { "Sid": "RequiredForIdentityCenterIAMRoleProvisioningInManagementAccount", "Effect": "Allow", "Action": [ "iam:AttachRolePolicy", "iam:PutRolePolicy", "iam:GetRole", "iam:CreateRole", "iam:UpdateRole", "iam:DeleteRole", "iam:ListRoles", "iam:ListRolePolicies", "iam:ListAttachedRolePolicies" ], "Resource": ["arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*"], "Condition": { "StringEquals": { "aws:PrincipalAccount": "${MANAGEMENT_ACCOUNT_ID}" } } }, { "Sid": "RequiredToReadUsageData", "Effect": "Allow", "Action": ["cloudtrail:LookupEvents", "ssm:DescribeSessions"], "Resource": "*" } ] } ``` The `RequiredForIdentityCenterIAMRoleProvisioningInManagementAccount` statement's condition uses the `aws:PrincipalAccount` key, which is documented in AWS's [global condition keys reference](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html). An earlier version of this statement used `aws:PrincipalOrgMasterAccountId` instead, which resolves to the same value (your organization's management account ID) but isn't listed in that reference or recognized by policy linters such as Parliament or IAM Access Analyzer, even though AWS uses it internally in policies like its `AWSSSOServiceRolePolicy` service-linked role. Because it's undocumented, it can trigger warnings in linters and security tooling, so this policy now uses `aws:PrincipalAccount` instead — the effect is the same, and the statement only takes effect for a role in your management account. ## 3. Configure additional AWS accounts to be managed by Opal If you are only setting up **AWS IAM Identity Center** to use Opal to manage AWS IAM Identity Center groups and resources, you can skip to Step 4. [Configure each additional AWS account](/integrations/configure-additional-aws-accounts-to-manage-in-opal)—IAM role, RDS database, etc.—you want Opal to manage, then return to this guide to complete steps 4-6. ## 4. Configure Opal app 1. In Opal, return to the **Create App** form and enter your AWS configuration details. 2. Enter your AWS **Account ID** in the **AWS Management or Delegated Administrator Account ID** field. In the AWS console, this is in the upper right corner. 3. **Optional**. If you're using [real-time sync](/integrations/aws-real-time-sync), enter your **CloudTrail events SQS queue URL**. You can also configure real-time sync after you create the app. 4. Enable or disable the following toggles based on your use cases. You can change these settings later from your app's **Setup** page. * **AWS Organization management**: Enable if you want to manage IAM roles, EC2 instances, EKS instances, or RDS databases in Opal. * **IAM Identity Center management**: Enable if you want to manage AWS IAM Identity Center groups and permission sets in Opal. 5. If you want to manage AWS IAM Identity Center groups and permissions in Opal, enter the following additional settings, which you can find in your AWS console in **IAM Identity Center > Settings**. | Field in Opal | Field in AWS | | -------------------------------- | --------------------- | | AWS IAM Identity Center Region | Region | | AWS Identity Center Instance ARN | Instance ARN | | AWS Identity Store ID | Identity Store ID | | AWS Access Portal URL | AWS access portal URL | ## 5. Run app validation checks After you save your app, you can view existing sync issues from the **Setup** tab on the app detail page. Missing permissions and sync issues show in the **App Validations** section. Select the refresh icon to rerun validation checks. You can hover over the validation icons to learn why Opal needs a given permission. To correctly sync your app to Opal, ensure you address any sync errors, marked with the red **!** icon. Inspect warnings on a case-by-case basis: warnings might impact features you’re not using and may be safely ignored, but this depends on your use case. ## 6. Connect individual AWS accounts After you've configured your AWS app, use the additional guides to configure [IAM roles](/integrations/adding-an-iam-role), [RDS databases](/integrations/adding-an-rds-database), [EC2 instances](/integrations/adding-an-ec2-instance), and [EKS clusters](/integrations/adding-an-eks-cluster). These guides contain instructions on how to let users connect to your AWS accounts. *** # Shortcut Source: https://docs.opal.dev/integrations/shortcut Learn how to connect your Opal instance with Shortcut With Opal's Shortcut integration, you can: * Sync access requests with Shortcut tickets to grant access for the lifetime of the ticket * Create audit tickets for access requests * Propagate access to end systems using Shortcut tickets ## Configuration requirements To connect Shortcut with Opal, you must: * Be an Opal Admin * Have admin access to your Shortcut account to generate an API key * Be on version 1.1.090 or later, if you self-host Opal ## 1. Create a Shortcut API key To enable the Shortcut integration, an administrator of your Shortcut account should [create an API key](https://help.shortcut.com/hc/en-us/articles/205701199-Shortcut-API-Tokens): 1. Log in to your Shortcut account 2. Go to **Settings** in the bottom left corner 3. Navigate to the **API Tokens** section in the left sidebar 4. Copy your existing API key or generate a new one 5. Save the generated key—you'll use it in the next step ## 2. Add your Shortcut details to Opal In Opal, go to **Configuration** > **Organization Settings** > **Productivity Integrations** and select **Connect** next to **Shortcut**. Enter your API key from the previous step and then save the integration. ## Access requests with Shortcut tickets After you integrate Shortcut, when making an access request, you can enable the option to **Expire access when ticket is closed**, which allows Opal to [reference](/docs/ticket-propagation#reference-existing-tickets-on-access-requests) existing Shortcut tickets. When you select **Search for tickets**, a list of Shortcut tickets populates, showing tickets assigned to you in Shortcut based on the Shortcut account that corresponds to your Opal email address. Click on any of these support tickets to attach the ticket to the access request. By default, an expiration time bound is required when making any access request. After the request is approved, access expiration is determined by the combination of this expiration time bound and the closure of the support ticket. Access will expire according to the minimum of these two events: if the ticket is closed before the expiration time bound is reached, access will be revoked when the ticket is closed. Similarly, if the expiration time bound is reached before the closure of the ticket, access is revoked when the expiration time bound is reached. If you want to attach an access request to a support ticket without an expiration time bound, you may select **Indefinite** under the expiration options and bind the request to a support ticket. ## Create audit tickets To create [audit tickets](/docs/ticket-propagation#create-audit-tickets) in Shortcut for every access request, enable **Shortcut** as a ticketing provider under **Configuration** > **Organization Settings** > **Access Requests**. Whenever a user requests access in Opal, Opal creates a ticket in Shortcut, allowing you to audit every access request in your own system. Audit tickets are auto-closed by Opal when access expires. You can manually close audit tickets in Shortcut, but this has no effect in Opal. ## Propagate access with tickets See the [ticket propagation guide](/docs/ticket-propagation#propagate-access-with-tickets) to learn how to propagate access with tickets connected to your Shortcut account. This can be especially useful for systems that still require manual access propagation and require someone to be notified via a ticket. *** # Slack Source: https://docs.opal.dev/integrations/slack Learn how to connect your Opal instance to Slack. You can configure Opal to integrate with your Slack workspace in order to send notifications to requesters and reviewers about permission and group access requests. Opal supports using a single Slack workspace or using multiple workspaces through [Slack Enterprise Grid](https://slack.com/enterprise). See the section below for [installing to an enterprise grid](#setup---enterprise-grid). ## Requirements You must be an Opal administrator and a Slack workspace administrator to set up the Slack integration. ## Get started 1. Log into the Slack workspace you want to integrate with Opal. 2. In the Opal dashboard, go to **Configuration** > **Settings** > **Productivity Integrations**. Installation varies between the Cloud Opal app and self-hosted instances. ## For Opal Cloud instances 1. Click **Connect** on the Slack integration on the **Productivity Integrations** page. 2. Choose to install to a single workspace or multiple workspaces via [Slack Enterprise Grid](https://slack.com/enterprise). Note that you can still install to a single workspace even if that workspace belongs to an Enterprise grid. If you are installing to a single workspace, enter the Slack workspace's domain in the **Workspace domain** field. For example, if your workspace is called `opal-test-workspace`, with the associated URL `opal-test-workspace.slack.com`, enter `opal-test-workspace`. 3. Next, you will be redirected to the Slack authorization page asking you to grant the Opal Slack app permissions to access your workspace or organization. See the later section for [installing to an enterprise grid](#setup-for-enterprise-grid). ## For self-hosted instances For self-hosted Opal instances, you first must create a new Slack app representing the Opal app. 1. Go to the [Slack App Dashboard](https://api.slack.com/apps) and click on the **Create an app** button. 2. Choose to create **From an app manifest**. 3. Select the Slack workspace associated with your self-hosted integration, and click **Next**. 4. Copy/paste the following app manifest JSON file, editing the `` field. ```json json theme={null} { "display_information": { "name": "Opal", "description": "Taking the pain out of permissions", "background_color": "#00020d" }, "features": { "bot_user": { "display_name": "Opal", "always_online": false }, "slash_commands": [ { "command": "/opal", "description": "'/opal' to open request modal • '/opal search [term]' to search", "usage_hint": "search ", "should_escape": false } ] }, "oauth_config": { "redirect_urls": [ "https:///callback/slack" ], "scopes": { "user": [ "channels:read", "groups:read" ], "bot": [ "app_mentions:read", "chat:write", "chat:write.public", "commands", "groups:read", "im:history", "im:write", "users.profile:read", "users:read", "users:read.email", "groups:write", "channels:manage", "channels:read", "channels:history" ] } }, "settings": { "event_subscriptions": { "user_events": [ "channel_deleted", "group_deleted" ], "bot_events": [ "channel_deleted", "group_deleted", "message.channels", "message.im", "team_access_granted", "team_access_revoked" ] }, "interactivity": { "is_enabled": true }, "org_deploy_enabled": true, "socket_mode_enabled": true, "token_rotation_enabled": false } } ``` 5. After you create the app, [download the Opal logo](https://files.readme.io/54e771f-logo-black.png) and add it to the Slackbot **Display Information**. 6. Click on **Basic Information** on the left sidebar, and record the Client ID, Client Secret and Signing Secret fields. You'll use these values in subsequent steps. 7. Generate an app-level token. Click on **Basic Information** on the left sidebar, and go to the **App-Level Tokens** section. Click on the **Generate Tokens and Scopes** button to create the app-level token. Give the token the `connections:write` scope, and make sure to record the token, which you'll use later. This app-level token is needed to use Slack's socket mode, which lets your self-hosted Opal instance avoid requiring an open port on the instance to receive Slack events. 8. Click on **App Home** on the left sidebar. Make sure the **Messages Tab** and **Allow users to send Slack commands and messages from the messages tab** settings are enabled. 9. Click on **Basic Information** on the left sidebar. Click on **Install your app** and **Install to Workspace**. The Slack app you just created will appear in your Slack workspace. 10. In Opal, go back to **Configuration > Settings > Productivity Integrations**. Click **Connect** on the Slack integration. 11. Choose to install to a single workspace or multiple workspaces via [Slack Enterprise Grid](https://slack.com/enterprise). Note that you can still install to a single workspace even if that workspace belongs to an Enterprise grid. 12. For the **Client ID** field, enter the Client ID from step 6. 13. For the **Client secret** field, enter the Client Secret from step 6. 14. For the **Signing secret** field, enter the Signing Secret from step 6. 15. For the **App level token** field, enter the app-level token from step 7. 16. On the last step, you will be redirected to the Slack authorization page asking you to grant the Opal Slack app permissions to access your workspace or organization. See the later section for [installing to an enterprise grid](#setup-for-enterprise-grid). To update your existing Slack integration to use socket mode, follow step 7 to generate the app-level-token and enable socket mode. Then, go to the Opal Configuration page, and disconnect, then reconnect Slack. ## Setup for Enterprise Grid Installing the integration to an Enterprise Grid has the same steps as above. However, the Slack integration authorization page can be confusing when indicating if you're installing to the Enterprise Grid or just one workspace in it. On the authorization step, use the dropdown in the upper-right hand corner to choose an organization under **Your organizations**. In the above example, the Slack organization *Opal Grid 1* has two workspaces: *Opal Grid Test 0* and *Opal Grid Test 2*. There are two options: * Choosing the **organization** (in this example, *Opal Grid 1*) allow you to install Opal to multiple workspaces in the Enterprise Grid * Choosing a **workspace** only installs Opal to that specific workspace (in this example, *Opal Grid Test 0* or *Opal Grid Test 2*) By default, **no workspaces** in the Enterprise Grid have access to Opal after your initial installation. See the following section to add workspaces from the Enterprise Grid. ### Grant access to specific Enterprise Grid workspaces After installing the Slack integration, you can add or remove Opal from workspaces in your Slack enterprise organization using the link on the settings page. Use the **Manage** dropdown to add or remove workspaces. ### Installation status If the installation succeeds, on the Configuration page, you will see a **Disconnect** button in the Slack tile. Opal is ready to send Slack messages to members of your workspace. For self-hosted Opal instances, you'll see an **active** or **inactive** status beside the **Disconnect** button. This corresponds to the state of the web socket connection with Slack. If the connection is inactive, wait a few minutes to see if the connection re-establishes itself, refreshing the page to see if the status updates. If the problem does not resolve itself within 10 minutes, try disconnecting and reconnecting the Slack integration. Finally, you can verify that everything works by trying the `/opal` command in Slack. ## User settings To enable your own Slack notifications, go to Opal dashboard and select your avatar in the bottom right corner. Click **Account Settings** and under **Notification Preferences**, enable the **Slack** toggle. Note that visibility settings apply to user(s) who are attempting to create the linked channel. If the user does not have access to the channel, we will return the error `Error: you do not have access to the selected Slack workspace`. ## Linked reviewer channels Slack channels can be linked to any owner as a reviewer channel. Opal notifies the Slack channel whenever there is an access request to review for the owner. ## Linked audit channels Opal can notify Slack channels whenever there is an event related to the resource or group. To configure this, go to the edit page for the relevant resource or group and select **Linked audit Slack channels**. To make private channels visible here, the Opal app must first be invited to them. # Slack users and groups Source: https://docs.opal.dev/integrations/slack-user-groups Connect your Slack workspace to Opal to manage and review access to Slack users and groups. Opal's Slack integration lets you manage access to your [Slack user groups](https://slack.com/help/articles/212906697-Create-a-user-group) (the `@`-mentionable groups, also called subteams). Once connected, Opal syncs the user groups and members in your Slack workspace and can add or remove members on your behalf. This supports the following, and more: * Users can **request time-bounded access** to your Slack user groups. * Reviewers can **run access reviews** to periodically confirm who has access to each group. * All membership changes are tracked in a **permanent audit log**. This is separate from the [Slack productivity integration](/integrations/slack), which sends Opal notifications to Slack. The two are configured independently, and you can use either or both. ## Requirements * You must be an Opal administrator. * Be on version 1.1170 or later, if you self-host Opal. * You need a Slack app that you can configure and install. You'll supply its OAuth client ID and secret to Opal. * The person who authorizes the connection must be able to install the app and manage user groups in Slack. On Enterprise Grid, you can install the app org-wide to sync every workspace in the grid, or install it in a single workspace. ## 1. Create a Slack app and bot token In the [Slack API dashboard](https://api.slack.com/apps), create a new app (or use an existing one). Navigate to the **OAuth & Permissions** section in the Slack app configuration page, and add a **Redirect URL** that points to Opal, in the format `https:///callback/slack-connection`. Under **OAuth & Permissions**, add the following **Bot Token Scopes**: | Scope | Purpose | | ------------------ | ---------------------------------------- | | `usergroups:read` | Read user group definitions | | `usergroups:write` | Add and remove members from user groups | | `users:read` | List workspace members | | `users:read.email` | Match Slack users to Opal users by email | Then install the app. You can install it to a single workspace, or org-wide on Enterprise Grid. Note the app's **Client ID** and **Client Secret** from the **Basic Information** page — you'll enter these in Opal. ## 2. Create the Slack app in Opal 1. Go to the **Inventory** page and click **+ App**. 2. Select the **Slack** app. 3. Fill out the form: enter an **App name**, select an **App admin** to own the app in Opal. You can also add an optional **Description** (shown to people requesting access) and set the app's **Visibility**. 4. Click to create the app. Then, navigate to the app in the inventory page, and click on the **Setup** tab. Connect the Slack app by entering its **Client ID** and **Client Secret** and completing the one-time Slack OAuth authorization. Opal stores only the resulting access token, not your client secret. Once the app is connected, Opal runs an initial sync to import your Slack users and user groups. You can trigger a sync at any time from the app's detail page in **Inventory** by selecting **Sync item**. On Enterprise Grid, an org-wide install syncs every workspace in the grid. A single-workspace install syncs only that workspace. ## What Opal syncs * **Users** — workspace members that Opal can manage. Opal does not ingest deactivated accounts, bots, app users, the workspace Slackbot, or any user without an email address (Opal needs the email to match Slack users to Opal users). * **User groups** — all user groups in the workspace, along with their members. Each group's `@`-handle is shown in its Opal description so you can tell how it's mentioned in Slack. Opal can add and remove members from a user group, but does not support nested groups or attaching resources directly to a Slack user group. Slack's API does not permit removing the last member of a user group. To achieve the expected result, Opal instead deactivates the user group, which leaves that final member in place. Once a user is added back to the seemingly empty group, Opal reactivates the group and replaces the old member with the new one. During reactivation, Slack briefly re-exposes the group's previous member list before Opal applies the new one, so those previous members may receive a "you were added to @group" notification from Slack. This is a limitation of Slack's API. # Snowflake Source: https://docs.opal.dev/integrations/snowflake Learn how to connect your Snowflake account and use Opal to manage and review access. Opal's **Snowflake** App lets you manage access to your Snowflake Roles and manage a Role's access to Securable Objects such as Databases, Schemas, and Tables. You can: * Import, sync, and manage access to *Snowflake Roles* and *Securable Objects* * View granular privileges granted to each user and role * Perform User Access Reviews (UARs) to assign managers or group admins to periodically review users with access to Snowflake Roles * Audit Event logs for any access changes on managed Snowflake Resources * Allow users to request JIT time-bound access to Snowflake Roles To learn more about the feature set, see the [Opal Snowflake Integration blog](https://www.opal.dev/blog/introducing-opal-snowflake-integration-managing-granular-access-to-critical-data) . There is a limit to 10,000 users, roles, databases, schemas, and tables each. If you would like to increase this, please [reach out](mailto:support@opal.dev). ## Supported functionality | Resource | Read | Grant and revoke access | Available in Risk Center | | ------------------- | ---- | ----------------------- | ------------------------ | | Snowflake Roles | ✔️ | ✔️ | ✔️ | | Snowflake Tables | ✔️ | | ✔️ | | Snowflake Schemas | ✔️ | | ✔️ | | Snowflake Databases | ✔️ | | ✔️ | In Opal, you can grant users access to Snowflake Roles, and add and remove Snowflake Tables, Schemas, and Databases to Snowflake Roles. The integration also supports user account [provisioning and deprovisioning](/docs/user-provisioning). ## Requirements To configure Snowflake, you must be an [Opal Admin](/docs/roles-in-opal) and have appropriate permissions in Snowflake. ## 1. Configure your Snowflake Service Account In order for Opal to manage access to your Snowflake Roles, you must first configure a Snowflake User that uses key pair authentication. In Snowflake, 1. Create a public/private key pair. Follow [Snowflake’s Key Pair Authentication instructions](https://docs.snowflake.com/en/user-guide/key-pair-auth). 2. Create a Snowflake User and Role in Snowflake. You can use the below script as a sample: ```sql sql theme={null} USE ROLE ACCOUNTADMIN; CREATE ROLE OPALSERVICE COMMENT = 'This role is for the Opal Snowflake integration and should not be modified'; -- Allow role to update a user/role's grants GRANT MANAGE GRANTS ON ACCOUNT TO ROLE OPALSERVICE; -- Allow role to view query history for usage insights GRANT DATABASE ROLE SNOWFLAKE.GOVERNANCE_VIEWER TO ROLE OPALSERVICE; -- Add to the Role Hierarchy as recommended by Snowflake GRANT ROLE OPALSERVICE TO ROLE SECURITYADMIN; -- Grant warehouse to role -- should be replaced with your warehouse GRANT USAGE ON WAREHOUSE TO ROLE OPALSERVICE; -- Create the service account CREATE USER OPALADMIN DEFAULT_ROLE = 'OPALSERVICE' DEFAULT_WAREHOUSE = '' -- Replace with the value from the previous step RSA_PUBLIC_KEY = '' COMMENT = 'Service account used by Opal Security to sync user roles'; -- Grant the role to the service account GRANT ROLE OPALSERVICE TO USER OPALADMIN; ``` ## 2. Fill out Opal form to connect to Snowflake In Opal, go to the **Inventory**, select the **+App** button on the top right, then select the Snowflake tile. Fill in the details of your Snowflake configuration. You need the following information: * `Organization` * `Account` * `Locator` * `Account identifier` * `Snowflake URL` * `Public key` * `Private key` * `Private key password` Reference [Snowflake's documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier#finding-the-organization-and-account-name-for-an-account) for help finding the above values. ## 3. Import Snowflake Roles and Securable Objects After the Snowflake App is set up, select **Import items** under the `...` on the top right to see a list of available Snowflake Roles and Securable Objects you can import and manage in Opal. > Admins can choose to import only a select Snowflake Role, or automatically import it along with all the Securable Objects it can access. Importing a Securable Object, such as a Table, automatically creates a resource hierarchy, making it easy to visualize the parent Schema and Database in Opal. ## 4. View and manage Snowflake resources After you import Snowflake Roles and Securable Objects you want to manage in Opal, you can: * Navigate to each role and learn which users and groups have access to it, and which Securable Objects it has access to * Add or remove users to and from the Snowflake Roles * Add or remove Securable Objects to and from Snowflake Roles > The **Customer Support** Snowflake Role has two users with access, no groups with access to it, and it does not have access to any Securable Object resources. Selecting **Remove** in this example revokes Tester's access to the role. *** # Snowflake User Provisioning Source: https://docs.opal.dev/integrations/snowflake-user-provisioning By default, users must exist in Snowflake before they can be added to any roles or resources in Opal, and attempting to grant access to a non-existent user results in a propagation error. Opal supports the provisioning of users into Snowflake automatically via SCIM, which requires additional setup. After following the steps below, users that are not found in Snowflake when granting access will be automatically provisioned to Snowflake beforehand. Note that users are only provisioned into Snowflake, not anywhere else. If your Snowflake instance is set up to require SSO for logins, users must also request access to the SSO app to log in. ## 1. Create a SCIM integration in Snowflake Follow the Snowflake documentation for [creating custom SCIM integrations](https://docs.snowflake.com/en/user-guide/scim-custom). ```sql sql theme={null} USE ROLE ACCOUNTADMIN; CREATE OR REPLACE SECURITY INTEGRATION OPAL_SCIM_PROVISIONING type=scim scim_client='generic' run_as_role='OPALSERVICE'; -- Allow Opal to create users. GRANT CREATE USER ON ACCOUNT TO ROLE OPALSERVICE; -- Allow Opal to create API tokens through the SCIM integration. GRANT OWNERSHIP ON INTEGRATION "OPAL_SCIM_PROVISIONING" TO ROLE "OPALSERVICE" REVOKE CURRENT GRANTS; ``` ## 2. Enable provisioning In Opal Edit the Snowflake app in Opal, and toggle on **Push Snowflake user to connection**. *** # Salesforce Tableau Source: https://docs.opal.dev/integrations/tableau Learn how to connect your Opal instance with Tableau. Opal's integration with Tableau allows you to manage access to Tableau groups. ## Supported resources | Resource | Read | Grant and revoke access | | -------------- | ---- | ----------------------- | | Tableau users | ✔️ | ✔️ | | Tableau groups | ✔️ | ✔️ | ## Requirements Before you begin, you must: * Be an Opal Admin * Be on version `1.1187.0`, if you self-host Opal ## 1. Create a Connected App in Tableau To allow Opal to access to your Tableau users and groups and grant permission to assign and remove users from groups, you must first create a [Connected App](https://help.tableau.com/current/online/en-us/connected_apps_direct.htm). We recommend you create a dedicated Tableau user (e.g. [opal-service@company.com](mailto:opal-service@company.com)) with Site Administrator Creator privileges for this integration. Using a personal admin account risks breaking the integration if that user leaves the organization. In Tableau, go to **Settings > Connected Apps > New Connected App > Direct Trust**. Fill in the following: * Connected app name: Enter a descriptive name (e.g. “Opal”) * Access level: All projects * Domain allowlist: All domains On the app details page, click **Generate New Secret** and save the Secret ID and Secret Value. Click the actions menu next to the app name and select Enable. You will need the following values when configuring the connection in Opal: | Value | Where to find it | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Client ID | Shown on the Connected App details page | | Secret ID | Shown after generating a secret | | Secret Value | Shown once when the secret is generated | | Username | The email of the dedicated Tableau admin user (e.g. [opal-service@company.com](mailto:opal-service@company.com)). This user must have Site Administrator Creator or Site Administrator Explorer privileges. Opal will impersonate this user when making API calls. | ## 2. Create Tableau app in Opal In Opal, go to **Inventory** > **+App** and select **Tableau**. Name your Tableau app, provide a description, and enter the token and token name you generated in step 1. After you save the app, you can import groups from **...** > **Import items**. *** # Tailscale Source: https://docs.opal.dev/integrations/tailscale Learn how to connect Opal to Tailscale to manage SSH access within your tailnet. With the Tailscale integration, you can granularly manage SSH access within your tailnet: * Allow users to request just-in-time access to resources on your tailnet from the web and Slack * Set the right resource owners to delegate approvals to those with the most context * Configure day-one access to Tailscale resources with groups from your identity provider * Automatically escalate and revoke privileged resource access based on on-call schedules, e.g., [PagerDuty](/integrations/pagerduty-oncall) or [Opsgenie](/integrations/opsgenie) ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ------------------------------------------------------------ | ---- | ----------------------- | ------------------------ | | Tailscale Groups | ✔️ | ✔️ | ✔️ | | [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh) | ✔️ | ✔️ | ✔️ | ## Requirements Before you begin this guide, you’ll need a tailnet and an Opal account. To learn how to create a tailnet, see the [Tailscale quickstart](https://tailscale.com/kb/1017/install/). ## Configuration steps To use Opal with Tailscale: 1. Generate an [OAuth client](https://tailscale.com/kb/1215/oauth-clients) from the [OAuth clients page of the admin console](https://login.tailscale.com/admin/settings/oauth) and give the policy file the `write` scope. 1. Using an API key instead of an OAuth client is deprecated. They are still supported but not recommended, as they have a maximum expiry of 90 days and must be refreshed manually. 2. To upgrade a Tailscale app that uses an API key, find the app in **Inventory**, then select **Setup** > **Edit**. Add the OAuth credentials and remove the API key. After you save the settings, check the **App validations** section to confirm your app authenticated correctly. No further action is needed after you add valid credentials. 2. In Opal, go to **Inventory**, click on the **+ App** icon, and select **Tailscale**. Set the following fields. | Field | Value | Example(s) | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | App admin | The team that should manage the Tailscale app in Opal. | *API Owners* | | Description | Let your end users know what they're requesting access to. | *SSH access to the production network* | | Tailnet name | Your tailnet's domain name. Find this by opening the admin console and copying the name next to the Tailscale logo in the upper left. | `example.com`, `[email protected]`, `example.github` | | Tailscale OAuth Client ID | The `Client ID` of the OAuth client you generated in Step 1. | | | Tailscale OAuth Client Secret | The `Client secret` of the OAuth client you generated in Step 1. | | 3. Import Tailscale resources to Opal by selecting **...** > **Import items**. For each ACL tag that is selected, Opal automatically parses the existing access rules and SSH access rules that apply to that tag, and which groups have access to the tagged sources using those rules. Users can now request access or SSH access to a specific tag in Tailscale or to join a specific group. # Teleport Source: https://docs.opal.dev/integrations/teleport Learn how to connect Opal to Teleport to manage access to Teleport groups. Opal natively supports an integration with Teleport. This integration enables organizations to manage access to groups in Teleport. With Opal's Teleport integration, you can: * Give specific roles via birthright access * Grant just-in-time short-lived access to roles * Audit who has access specific roles and why * Review roles in user access reviews ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | -------------- | ---- | ----------------------- | ------------------------ | | Teleport Roles | ✔️ | ✔️ | ✔️ | ## Configuration steps In Teleport, you will need to set up a service account for Opal to perform operations. 1. Create an `opal-service` role. In the Teleport Dashboard, go to **Team** > **Roles**, then **Create New Role**. Use the following YAML: ```yaml yaml theme={null} kind: role metadata: name: opal-service spec: allow: db_labels: "*": "*" app_labels: "*": "*" rules: - resources: - role - user verbs: ["list", "create", "read", "update", "delete"] options: max_session_ttl: 8760h version: v5 ``` The `opal-service` role is used by Opal to make changes to your system. Note that we use a max session TTL of 8760 hours (1 year). The credentials you generate in a subsequent step will be bound by this TTL, and you will need to reissue the credentials at this interval. You may want to set this value higher to avoid needing to reissue the credentials. 2. Next, create an `opal-impersonator` role on the same page. Use the following YAML: ```yaml yaml theme={null} kind: role version: v5 metadata: name: opal-impersonator spec: # SSH options used for user sessions options: # max_session_ttl defines the TTL (time to live) of SSH certificates # issued to the users with this role. max_session_ttl: 10h # The allow section declares a list of resource/verb combinations that are # allowed for the users of this role. By default, nothing is allowed. allow: impersonate: users: ["opal-service"] roles: ["opal-service"] # The deny section uses the identical format as the 'allow' section. # Deny rules always override allow rules. deny: node_labels: "*": "*" ``` This role allows you to sign a credential on behalf of the `opal-service` service user. 3. Attach this role to yourself in the Teleport dashboard by finding your user and going to **Options** > **Edit**, and adding `opal-impersonator` to your **User Roles**. 4. Create the `opal-service` service user. Go to **Team** > **Users** > **Create New User**. Use `opal-service` as the name and add the `opal-service` role. 5. In your terminal, use `tsh login` to log in to your account (not the `opal-service` account). If you were already logged in, you must `tsh logout` and log in again to pick up the new `opal-impersonator` role. ```text text theme={null} tsh login --proxy=myorg.teleport.sh [email protected] ``` 6. Sign a new credential for the `opal-service` service account. ```text text theme={null} tctl auth sign --user=opal-service --out=opal-service.pem --ttl=8760h ``` Note that this step is possible because you attached the `opal-impersonator` role to your Teleport user. The TTL in this step should match the value you configured in step 1. 7. In Opal, go to **Inventory**, click on the **+ App** icon, and select the Teleport App. Fill out the details for the integration, using the contents of `opal-service.pem` generated in the previous step as the **Opal service account credentials file**. ## Link Teleport users to Opal users Teleport does not expose user emails. Therefore, Opal is not able to associate Teleport users with Opal users by default. To use the integration, you need to import the mapping via a custom attribute from your IDP. 1. Make sure that users in your IDP have a field with their Teleport username. 2. In Opal, make sure that you have [connected your IDP](/docs/connect-your-identity-provider-idp). 3. In Opal, go to **Settings** > **IDP & HR Integrations** select the **Importing X custom user attributes** link on your IDP tile. 2372 4. Enter the attribute name from your IDP that has the Teleport username, select **Teleport username** as the **Use as** value from the dropdown and click **Add attribute**. 2312 5. The mappings from Opal user to Teleport username are now imported from your IDP and you can start using the integration. *** # Twingate Source: https://docs.opal.dev/integrations/twingate Learn how to connect Opal to Twingate to manage access to Twingate groups and resources. With the Twingate integration, you can manage zero-trust network access through Opal: * Allow users to request just-in-time access to Twingate groups and resources from the web and Slack * Set the right resource owners to delegate approvals to those with the most context * Configure day-one access to Twingate resources with groups from your identity provider * Automatically escalate and revoke privileged access based on on-call schedules, e.g., [PagerDuty](/integrations/pagerduty-oncall) or [Opsgenie](/integrations/opsgenie) ## Supported resources | Resource | Read | Grant and revoke access | Available in Risk Center | | ------------------ | ---- | ----------------------- | ------------------------ | | Twingate Groups | ✔️ | ✔️ | ✔️ | | Twingate Resources | ✔️ | ✔️ | ✔️ | The integration also supports user account [provisioning and deprovisioning](/docs/user-provisioning). ## Requirements * You must be an Opal admin. * You must be a Twingate admin with the ability to generate API tokens. ## 1. Generate a Twingate API token 1. Log in to the [Twingate Admin Console](https://www.twingate.com/). 2. Go to **Settings** > **API**. 3. Click **Generate Token**. 4. Enter a label (e.g., `Opal`) and set **Permission Level** to **Read & Write**. 5. Copy the token and store it securely. The token is only displayed once. ## 2. Create a Twingate app in Opal 1. In Opal, go to **Inventory**, click **+ App**, and select **Twingate**. 2. Fill in the following fields: | Field | Value | | ------------------ | ------------------------------------------------------------------------------------------------------------------------ | | App admin | The team or user that should manage the Twingate app in Opal. | | Description | Let your end users know what they're requesting access to. | | Twingate network | Your Twingate network name. This is the subdomain of your Twingate URL (e.g., `mycompany` for `mycompany.twingate.com`). | | Twingate API token | The API token you generated in Step 1. | 3. Click **Create**. Opal validates the credentials by connecting to your Twingate network. ## 3. Import Twingate resources After creating the app, import groups and resources from Twingate into Opal: 1. Go to your Twingate app in **Inventory**. 2. Select **...** > **Import items**. 3. Choose the groups and resources you want to manage through Opal. Users can now request access to Twingate groups and resources through Opal. ## User provisioning Opal can automatically provision and deprovision users in Twingate. When provisioning is enabled, users who don't already have a Twingate account are automatically created when they are granted access to a Twingate group or resource. Deprovisioning removes users from Twingate when their access is revoked. To configure user provisioning: 1. Go to your Twingate app in **Inventory**. 2. Select **Setup** > **Edit**. 3. Under **Provision Twingate users**, select **Provision Twingate users** to enable automatic user creation. 4. Under **Deprovision Twingate users**, select **Deprovision Twingate users** to enable automatic user removal. 5. Click **Save**. For more details, see [User provisioning](/docs/user-provisioning). # Overview Source: https://docs.opal.dev/integrations/workday Learn how to connect your Workday tenant to Opal. Opal's integration with Workday lets you [leverage HRIS data](/integrations/workday-idphris-integration) as an additional source of truth for users, employee metadata, and their attributes. The integration also allows you to manage access to entitlements such as [Workday Security Groups and Roles](/integrations/workday-groups-and-roles). ## Supported resources | Resource | Read | Grant and revoke access | Available in [Risk Center](/docs/least-privilege-posture-management) | | ------------------------------------------- | ---- | ----------------------- | -------------------------------------------------------------------- | | Workday Users and user attributes (as HRIS) | ✔️ | ✔️ | ✔️ | | Workday User Security Groups | ✔️ | ✔️ | ✔️ | | Workday Roles | ✔️ | ✔️ | ✔️ | The Workday integration does not currently support syncing Workday service users. ## 1. Create an Opal app To get started, go to the **Inventory** > **Apps** page, select **+App** at the top right, and select **+Connect** under the **Workday** tile. 2312 Because Workday configures permissions on the field level, the set up process involves creating a **Workday Integration System User** and **Workday Integration Security Group** to ensure it has the necessary permissions. ## 2. Create an Integration System User (ISU) In the Workday Search bar, enter **Create Integration System User**, and select the corresponding **Task**. 2312 In the **Create Integration System User** modal, enter the **Account Information**, including User Name, Password. Set the **Session Timeout Minutes** to 0 to prevent session expiry, as it may lead to the integration timing out before completion. 2312 ## 3. Create a Security Group and assign it an ISU In the Workday Search bar, enter **Create Security Group**, and select the corresponding Task. 2312 In the Create Security Group modal, for the Type of Tenanted Security Group, select **Integration System Security Group (Unconstrained)** and enter a name to represent the ISU. 2312 Once created, Edit the Security Group to associate it with the **Integration System User** you created in Step 2. 2312 ## 4. Configure Domain Security Policy Permissions In the Workday Search bar, enter **Maintain Permissions for Security Group**, and select the corresponding Task. 2312 In the task modal, first set the **Operation** to **Maintain** and set the **Source Security Group** to the Security Group you created in Step 3. 2312 Then, edit the **Domain Security Policy Permissions** and add the following GET ONLY operations: | View/Modify Access | Domain Security Policy | Required | Reason | | ------------------ | ----------------------------------------- | -------------------------------------------- | ------------------------------------- | | GET ONLY | Worker Data: Public Worker Reports | Required | Used to import users within resources | | GET ONLY | Worker Data: Workers | Required | Used to import users within resources | | GET ONLY | Security Configuration | Required | Used to check for permissions | | GET ONLY | Worker Data: Current Staffing Information | Required if you use Workday as your IDP/HRIS | Used to retrieve user statuses | | GET ONLY | Integration Build | Required if you use Workday as your IDP/HRIS | Used to retrieve user statuses | | GET ONLY | Worker Data: Employment Data | Required if you use Workday as your IDP/HRIS | Used to import user attributes | | GET ONLY | Worker Data: All Positions | Required if you use Workday as your IDP/HRIS | Used to import user attributes | In Workday, you can add each by clicking on the **+** button on the top left of the table. For example: 2312 ## 5. Activate Security Policy Changes In the Workday Search bar, enter **Activate Pending Security Policy Changes**, and select the corresponding Task. 2312 Review and check the **Confirm** box to activate the Security Policy Changes. 2312 ## 6. Manage Authentication Policies In the Workday Search bar, enter **Manage Authentication Policies**, and select the corresponding Report. 2312 Depending on your policy set up, you can choose to edit an existing policy or create a new one. To create a new one: 1. select **Add Authentication Policy** on the page. 2. Select from the dropdown the corresponding **Environment** you would like the policy to apply to. 3. In the table below, add an **Authentication Ruleset** by selecting the **+** button on the top left. 4. Provide an **Authentication Rule Name** and set the **Security Group** to the one you created in Step 2. For the **Authentication Conditions**, select **Any**. For **Allowed Authentication Types**, select **User Name Password**. 2312 ## 7. Activate All Pending Authentication Policy Changes In the Workday Search bar, enter **Activate All Pending Authentication Policy Changes**, and select the corresponding Task. 2312 Add any comments, review, and check the **Confirm** box to activate the Authentication Policy Changes 2312 ## 8. Obtain the Web Services Endpoint for tenant In the Workday Search bar, enter **Public Web Services**, and select the corresponding Report. 2312 In the table, locate the **Human Resources (Public)** Web Service, hover over it and click on the **...** to the right of the text. Under **Web Service**, select **View WSDL**. This will open another page in the browser. 2312 In the new page containing the document tree, you can use Cmd + F / Ctrl + F to find `/service`, and you should see a URL address that looks like the following: 2312 The corresponding highlighted URL segment up to the `/service` path is your **Workday Web Services Endpoint**. Note that each tenant may have a different endpoint, so a new endpoint would need to be created for each environment you would like to connect. The text directly after `/service` should represent your **Workday Tenant Name**. As an example, if your Workday log in URL is `https://impl.workday.com/HelloWorld`, your Workday Tenant Name would be `HelloWorld`. ## 9. Complete the Opal form to connect Workday In Opal, enter the details based on the Workday items you configured in the previous steps: * Workday Integration System User username (Step 2) * Workday Integration System User password (Step 2) * Workday tenant URL subdomain (Step 8) * Workday tenant name 2312 Once you've completed the form, select **Create**, and your connection should be set up and running. See [Workday Groups and Rules](/integrations/workday-groups-and-roles) to learn how to manage access to Workday entities such as User Security Groups and Organization Roles, and the [Workday IDP/HRIS Integration](/integrations/workday-idphris-integration) guide to learn how to sync Workday entities and attributes. ## Run app validation checks After you save your app, you can view existing sync issues from the **Setup** tab on the app detail page. Missing permissions and sync issues show in the **App Validations** section. Select the refresh icon to rerun validation checks. You can hover over the validation icons to learn why Opal needs a given permission. To correctly sync your app to Opal, ensure you address any sync errors, marked with the red ! icon. Inspect warnings on a case-by-case basis: warnings might impact features you’re not using and may be safely ignored, but this depends on your use case. # Workday groups and roles Source: https://docs.opal.dev/integrations/workday-groups-and-roles Learn how to manage access to Workday groups and roles. Opal’s integration with Workday allows administrators to view and manage user access to Workday entities, such as User Security Groups and Organization Roles, which are typically tied to Domain Security Policies and Role Based Security Groups, respectively. ## 1. Add additional Domain Security Policy Permissions in Workday Go to the Workday Search bar, enter **Maintain Permissions for Security Group**, and select the corresponding Task. 2312 In the task modal, first set the **Operation** to **Maintain** and set the **Source Security Group** to the Security Group you created in Step 2 of *Getting Started*. 2312 Then, edit the **Domain Security Policy Permissions** and add the following operations: | View/Modify Access | Domain Security Policy | | ------------------ | ---------------------------------------- | | GET and PUT | User-Based Security Group Administration | | GET ONLY | Manage: Organization Roles | | GET ONLY | Manage: Organization Integration | ## 2. Edit Business Security Policy in Workday Navigate to the Workday Search bar, enter **Edit Business Security Policy**, and select the corresponding Task. In the task modal, set Business Process Type to **Assign Roles**. Then, add the Security Group you created in Step 2 of *Getting Started* to **Assign Roles (Web Service)**. ## 3. Activate All Pending Authentication Policy Changes in Workday Navigate to the Workday Search bar, enter **Activate All Pending Authentication Policy Changes**, and select the corresponding Task. 2312 Add any comments, review, and check the **Confirm** box to activate the Authentication Policy Changes. ## 4. Import Workday items in Opal In Opal, go to the **Inventory** and select the **Workday** app, then in the upper left, select **...** > **Import items**. Select the User Based Security Groups and Organization Roles that you want to manage. # Workday IDP/HRIS Integration Source: https://docs.opal.dev/integrations/workday-idphris-integration Learn how to connect Workday to Opal as an IDP/HRIS integration. If your organization uses Workday as an HR Provider, you can designate it as an [IDP/HRIS Integration](/docs/add-your-first-idphr-provider). This allows Opal to sync your Workday identities and their attributes, on top of syncing and managing access to entitlements (e.g. Workday Security Groups, Workday Organization Roles, etc). ## Requirements Before you set up Workday as your IDP, you must first create an Workday Directory App in Opal. To do this, [follow the instructions here](/integrations/workday). Next, set up Workday as your IDP using the following instructions: * [Add Your First IDP/HR Provider](/docs/add-your-first-idphr-provider) * [Add Additional IDP/HR Providers](/docs/add-additional-idphr-providers) ## HRIS/User attribute source configuration To configure Workday as an attribute source in Opal, go to **Settings > IDP & HR Integrations** and select your existing Workday connection as an IDP/HRIS provider. ### Default attributes ingested The following attributes are ingested from Workday by default. These attributes can be mapped to Opal's user attributes from the **Settings** > **IDP & HRIS Integrations** page. | Workday Attribute | Default Opal Mapping | Description | | ------------------ | -------------------- | -------------------------------------------------------- | | `email` | Email | The user's email address. | | `remoteID` | - | Workday object WID. | | `employeeID` | - | The user's employee ID. | | `firstName` | - | The user's first name. | | `lastName` | - | The user's last name. | | `managerEmail` | Manager | The email address of the user's manager. | | `position` | - | The user's job position. | | `workLocation` | - | The location where the user works. | | `businessTitle` | Title | The user's business title. | | `employeeType` | - | The type of employment (e.g., full-time, part-time). | | `employeeTimeType` | - | The time type of the employee (e.g., regular, contract). | | `hireDate` | - | The date when the user was hired. | | `managementLevel` | - | The management level of the user. | | `organization` | Team | The organization to which the user belongs. | | `onLeave` | - | Indicates whether the user is currently on leave. | | `active` | - | Indicates whether the user is an active worker. | | `secondaryEmails` | - | Comma-separated list of user's contact emails. | In addition to the attributes listed above, Opal also ingests the worker's provisioning status and secondary email addresses. These attributes are automatically mapped to Opal's system attributes. You can customize these mappings based on your organization's specific requirements by visiting the IDP/HRIS settings page. Ensure that the attributes are correctly mapped to ensure accurate data synchronization between Workday and Opal. ### Ingest custom attributes To ingest custom attributes from Workday into Opal, you need to configure the Workday integration system using the Field Override Service. Follow the steps below to set up and map custom attributes effectively: #### 1. Create a Field Override Service 1. In Workday, go to **Integration Configuration** > Search and select "Create Integration Field Override Service" and start a new Field Override Service setup. 2. Name the Field Override Service, and select the **Worker** business object to associate with the service. 3. Click "Plus" to add fields. Configure each custom field that correspond to your desired attributes in Opal. #### 2. Set Up the Integration System 1. From the Workday homepage, go to **Integration System** > **Create Integration System**. 2. Name your system, then choose **New Using Template** > **Document Transformation** as template. 3. Navigate to **Actions > Integration System > Configure Integration Attachment Service** > **Attachment** > **Create Integration Attachment Service**. Attach an empty text file to attach integration service. 4. In your new Workday Integration System, go to **Custom Integration Services**. Click "Plus" to add and "OK" to confirm the Field Override Service created earlier. 5. Note the System ID for the Integration System as it is needed for retrieving custom attributes. #### 3. Map and Configure Attributes 1. Search **View Integration Systems**, then enter the name of the Integration System you created earlier. 2. Go to **Integration System** > **Configure Integration Field Overrides**, then find the custom or calulcated fields. Map them to the correct values in "Override External Field". 3. Click "OK" to save mappings, and "Done" to finish. #### 4. Link the Integration System to Opal 1. Search for **View Integration System**, enter the name of the Integration System you created. 2. Copy Integration System ID 3. Add it in your Opal Workday Connection 4. To import these custom attributes in Opal, go to **Settings** > **IDP & HR Integrations** in Opal. Your configured field name will be used as the key in the attribute mapping. # Zendesk Source: https://docs.opal.dev/integrations/zendesk Learn how to connect Opal to Zendesk to manage access to Zendesk groups, organizations, and custom roles. With the Zendesk integration, you can manage support platform access through Opal: * Allow users to request just-in-time access to Zendesk groups, organizations, and custom roles from the web and Slack * Set the right resource owners to delegate approvals to those with the most context * Configure day-one access to Zendesk resources with groups from your identity provider ## Supported resources | Resource | Read | Grant and revoke access | Notes | | --------------------- | ---- | ----------------------- | --------------- | | Zendesk Groups | ✔️ | ✔️ | | | Zendesk Organizations | ✔️ | ✔️ | | | Zendesk Custom Roles | ✔️ | ✔️ | Enterprise only | ## Requirements * You must be an Opal admin. * You must have admin access to your Zendesk account to generate an API token. * Be on version 1.1168.0 or greater, if you self-host Opal. ## 1. Create a Zendesk API token In Zendesk, generate an API token by navigating to **Admin Center** > **Apps and integrations** > **APIs** > **Zendesk API** and enabling token access if not already on. Click **Add API token**, give it a description (e.g., `Opal`), and copy the token — it is only shown once. ## 2. Create a Zendesk app in Opal In Opal, go to **Inventory > +App** and select **Zendesk**. Fill in the following fields and hit create. | Field | Value | | ----------- | ---------------------------------------------------------------------------------------- | | App admin | The team or user that should manage the Zendesk app in Opal. | | Description | Let your end users know what they're requesting access to. | | Subdomain | The subdomain of your Zendesk instance (e.g., `mycompany` from `mycompany.zendesk.com`). | | Admin email | The email address of the Zendesk admin/agent who generated the API token. | | API token | The API token you generated in [Step 1](#1-create-a-zendesk-api-token). | ## 3. Import Zendesk resources After creating the app, you can import resources from **... > Import items**. Users will now be able to request access to Zendesk groups, organizations, and custom roles through Opal. ## User provisioning Opal can automatically provision and deprovision users in Zendesk. When provisioning is enabled, users who don't already have a Zendesk account are automatically created as agents when they are granted access to a Zendesk resource. Deprovisioning permanently deletes users from Zendesk when their access is revoked. ## Organizations: multiple organization support By default, Zendesk only allows a user to belong to one organization at a time. If you plan to manage organization membership through Opal, enable **Multiple organizations** in your Zendesk account settings under **Admin Center** > **People** > **Configuration** > **End users**. Without this setting, granting a user access to a second organization will remove them from their current one. ## Custom roles Custom roles are only available on Zendesk **Enterprise** plans. On other plans, the custom roles resource type will not appear when importing items. # Zoom Source: https://docs.opal.dev/integrations/zoom Learn how to connect your Zoom instance to Opal to manage and review access. You can connect Zoom to Opal to manage and review access. ## Supported resources | Resource | Read | Grant and revoke access | | ------------------------------ | ---- | ----------------------- | | Users | ✔️ | | | Roles — *Zoom Role* | ✔️ | ✔️ | | License tiers — *Zoom License* | ✔️ | ✔️ | | Groups — *Zoom Group* | ✔️ | ✔️ | With Opal's Zoom integration: * **Users can** request access to Zoom roles, paid license seats, and groups through Opal. * **Admins can** review who holds each Zoom role, license tier, and group membership, and grant or revoke that access — including running [access reviews](/docs/access-campaigns). * **Admins can** reallocate paid license seats (Licensed / Unassigned). Opal never purchases seats, so a grant with no free seat fails safely instead of billing. ## Requirements To connect with Zoom, you must first: * Be an [Opal Admin](/docs/roles-in-opal). * Have a Zoom plan of **Pro or higher** (groups require Pro; **custom roles require Business**). * Have a Zoom **account Owner or full admin** create a [Server-to-Server OAuth app](https://developers.zoom.us/docs/internal-apps/s2s-oauth/) (covered in [step 1](#1-create-a-server-to-server-oauth-app-in-zoom)). A Zoom Server-to-Server app can hold no more privilege than the admin who created it. Create it under a **durable admin identity** — if that admin is later downgraded, Zoom silently strips the app's scopes and sync/provisioning will start failing. ## 1. Create a Server-to-Server OAuth app in Zoom Signed in as a Zoom account Owner or admin, in [Zoom's Platform Studio](https://marketplace.zoom.us/): 1. Go to **Develop** > **Build App**, select **Server to Server OAuth App**, and select **Create**. Give the app a name, for example `Opal`. Zoom's "What kind of app are you creating" dialog with "Server to Server OAuth App" selected. 2. On the **Scopes** tab, add the scopes Opal needs (see [Scopes](#scopes)): the required read scopes for sync, the optional `user:read:settings:admin` scope for add-on license visibility, and the write scopes for any provisioning you want to enable. The Scopes step of a Zoom Server-to-Server OAuth app, with the "Add Scopes" button highlighted and role scopes already added. 3. Fill in the app's basic information and **Activate** the app. 4. On the **App Credentials** tab, copy the **Account ID**, **Client ID**, and **Client Secret**. The App Credentials step of a Zoom Server-to-Server OAuth app, with a Copy button beside the Account ID, Client ID, and Client Secret fields. ## 2. Create the Opal app In Opal, go to **Inventory** > **+ App**, then select **Connect** on the **Zoom** tile. Opal's Add App picker, with the Connect button on the Zoom tile highlighted. ## 3. Enter your Zoom credentials in Opal Fill in the following fields, then create. Opal validates the credentials and reports which scopes were granted — any missing scope is flagged with the exact capability it disables. | Field | Value | | ------------- | ----------------------------------------------------------------------------- | | App name | An identifiable name for the connection, for example `Zoom`. | | App admin | The [Owner](/docs/opal-101#owners) of this app. | | Description | A short description shown to people requesting access. | | Visibility | Global, or restricted to specific groups. | | Account ID | The Account ID from [step 1](#1-create-a-server-to-server-oauth-app-in-zoom). | | Client ID | The Client ID from step 1. | | Client Secret | The Client Secret from step 1. | Opal's "Configure your Zoom connection" form, with the Account ID, Client ID, and Client Secret fields highlighted. After you save the app, you can import your Zoom roles, licenses, and groups from **...** > **Import items**. ## Additional information ### What Opal syncs * **Users** — across active, inactive, and pending states; identity is matched by email. * **Roles** — **Admin**, **Owner**, and any custom roles (custom roles require Business) as **Zoom Role** resources. Each user holds exactly one role, so at most one Zoom Role maps to a user. The default **Member** role is not modeled as a resource — a user with no elevated role is just an account. * **License tiers** as **Zoom License** resources: **Licensed** (paid seat) and **Unassigned** (No Meetings License), plus add-on licenses **Webinar** and **Large Meeting**. Basic (free) and On-prem tiers are not modeled. * **Groups** as **Zoom Group** groups (a user can belong to multiple). The account **Owner** is excluded from all provisioning. ### Provisioning behavior * **License** — grant sets the tier; revoke downgrades to Basic. Opal never bills: if no free seat is available the grant fails with "no available seats" and nothing changes. * **Role** — each user holds exactly one role, so Zoom Roles are **mutually exclusive**: granting a role moves the user off their previous one, and revoking a role reverts them to **Member**. The base **Member** role isn't a grantable or revocable resource, and the **Owner** role is **read-only** — account ownership is transferred through Zoom's dedicated owner-transfer flow, not Opal, so granting or revoking Owner reports *unsupported*. Applies to active users only. * **Group** — add or remove a single membership, leaving other groups untouched. Adds apply to active users only. Access can only be granted to users who **already exist in Zoom** — Opal never creates Zoom accounts, so a grant targeting someone with no matching Zoom user fails with a clear "could not be matched" message instead of silently doing nothing. All provisioning actions are idempotent — re-granting or re-revoking is a safe no-op. ### Scopes **Required read scopes** (needed for all sync): | Purpose | Scope | | ------------------------------------ | ------------------------------- | | List users (license type + role) | `user:read:list_users:admin` | | Get a user (validation, point reads) | `user:read:user:admin` | | List roles | `role:read:list_roles:admin` | | List role members | `role:read:list_members:admin` | | List groups | `group:read:list_groups:admin` | | List group members | `group:read:list_members:admin` | **Optional read scope** — add-on license visibility: `user:read:settings:admin`. Without it, base tiers, roles, and groups still sync; only the **Webinar** and **Large Meeting** license resources come back empty. **Write scopes** (add only for the provisioning you want): | Capability | Scope(s) | | ------------------------- | ------------------------------------------------------- | | Change license tier | `user:update:user:admin` | | Grant / revoke role | `role:write:member:admin`, `role:delete:member:admin` | | Add / remove group member | `group:write:member:admin`, `group:delete:member:admin` | For least privilege, Opal does **not** request user-creation, user hard-delete, or email/password write scopes — it manages license, role, and group membership for existing users, not account creation or deletion. *** # Authentication Source: https://docs.opal.dev/reference/authentication Learn how to authenticate your requests to the Opal API. # Overview When making requests to the Opal API, you must pass an authentication bearer token in the header to identify yourself as an authorized user. To do this, pass a header with key **Authorization** and value **Bearer** where is the value of the API token you generate from the admin API console. ## Using the API With Opal Service Users Service users have their own identity in Opal and can be assigned Opal roles to scope their permissions. Opal Admins can create a service user on the **Organization Settings -> Service Users** page. Service users can be assigned to Opal roles and scoped roles under the **Resources** tab. Service users can be added to Opal groups under the **Groups** tab. These can be combined to provide granular permissions to Service Users. API keys for service users can be created under the **API Keys** tab and can be set to expire. A maximum of **2 API Keys** can be created for a service user. ## Using the API with Personal Access Tokens Personal access tokens are tied to your identity and have your permissions. By default, only Opal Admins can generate personal access tokens. However, admins can enable all users to create PATs by toggling the **Allow all users to create PATs** setting in **Organization Settings -> Advanced**. Users can generate personal access tokens on the **User -> Settings** page: Personal access tokens can either be **Read-only** or **Full-access**: * **Read-only tokens** can only perform GET requests, limited to resources you have permission to view. * **Full-access tokens** can perform all API operations (GET, POST, PUT, DELETE), but are still scoped to your user permissions - you cannot access or modify resources beyond what your Opal role allows. Once the token is generated, copy it and use it to make authenticated requests to the Opal API. If a token is compromised, you can revoke it from the same page. Any other Opal Admin can revoke your token from the **Organization Settings -> API Tokens** page. # Identifying End System Objects Source: https://docs.opal.dev/reference/end-system-objects Learn how Opal tracks the resources and groups that it syncs with end systems. ## Remote IDs For each resource or group that Opal syncs with an end system app, Opal stores a Remote ID to map it to the corresponding end system object. This means that when you [create a resource](/api-reference/resources/post-resources) or [create a group](/api-reference/groups/post-groups) via our API, you'll need to supply a Remote ID indicating which end system item this created item corresponds to. The table below describes what each Remote ID corresponds to for various end system resource types and group types. For most resource and group types, the remote ID is simply the unique identifier for the item on the remote system. | Resource Type | Remote ID | Example | | ------------------ | --------------------------------------- | ------------------------------------------------------------- | | AWS EKS Cluster | ARN | arn:aws:eks:us-east-2:490306312345:cluster/examplecluster | | AWS IAM Role | ARN | arn:aws:iam::490306312345:role/IamRole | | GCP Bucket | Bucket ID | example-bucket | | GCP Folder | folders/\{Folder ID} | folders/592319123456 | | GCP GKE Cluster | Cluster Name | example-cluster | | GCP Project | Project ID | example-project-312345 | | Okta Standard Role | \{Role\_Type}-\{Opal\_Organization\_ID} | GROUP\_MEMBERSHIP\_ADMIN-c2b572d7-6f36-47bf-81e3-8819bf123456 | | Okta Custom Role | Role ID | br04xma47yJwwLWab12de | ## Metadata Opal also stores metadata with each Opal resource or group to help it identify the corresponding end system object. This will also need to be supplied when [creating a resource](/api-reference/resources/post-resources) or [creating a group](/api-reference/groups/post-groups) via our API. The table below describes the required metadata format for various end system resource types and group types. | Group Type | Metadata JSON Example | | ---------------------- | ---------------------------------------------------------------------------- | | Active Directory Group | `{"ad_group": {"object_guid": "abcdef39-7e75-44e5-bd04-2ed02a123456"}}` | | Google Group | `{"google_groups_group": {"group_id": "abcm2jsg218b123"}}` | | LDAP Group | `{"ldap_group": {"group_uid": "fac0d608-01f1-103b-8def-5fa100c7884c"}}` | | Okta Group | `{"okta_directory_group": {"group_id": "00g1839iewP7YaQs25d7"}}` | | Duo Group | `{"duo_group": {"group_id": "abcdefsfhaxdls123456"}}` | | GitHub Team | `{"git_hub_team": {"org_name": "example-org", "team_slug": "example-team"}}` | | Resource Type | Metadata JSON Example | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AWS EC2 Instance | `{"aws_ec2_instance": {"instance_id":"i-abcdefab163123456", "region":"us-east-2"}}` | | AWS EKS Cluster | `{"aws_eks_cluster": {"cluster_name":"example-name", "cluster_region": "us-east-2", "cluster_arn": "arn:aws:eks:us-east-2:457823123456:cluster/example-name"}}` | | AWS MySQL Instance | `{"aws_rds_instance": {"instance_id": "example-mysql-db", "engine": "mysql", "region": "us-east-2", "resource_id": "db-ABCDEFEWPOB52OBTQWELABCDEF", "database_name": "example_db"}}` | | AWS PostgreSQL Instance | `{"aws_rds_instance": {"instance_id": "example-mysql-db", "engine": "postgres", "region": "us-east-2", "resource_id": "db-ABCDEFEWPOB52OBTQWELABCDEF", "database_name": "example_db"}}` | | AWS IAM Role | `{"aws_role": {"arn": "arn:aws:iam::490306123456:role/RoleName", "name": "RoleName"}}` | | GCP Bucket | `{"gcp_bucket_role":{"bucket_id":"bucket-id"}}` | | GCP Compute Instance | `{"gcp_compute_instance": {"instance_id": "example-instance", "project_id": "example-project", "zone": "us-east1"}}` | | GCP Folder | `{"gcp_folder_role": {"folder_id": "folders/592319123456"}}` | | GCP GKE Cluster | `{"gcp_gke_cluster": {"cluster_name": "clustername"}}` | | GCP Project | `{"gcp_project_role": {"project_id": "proj-id-456123"}}` | | GCP SQL Instance | `{"gcp_sql_instance":{"instance_id":"example-db-instance","project_id":"example-project"}}` | | GitHub Repo | `{"git_hub_repo":{"org_name":"example-name", "repo_name":"another-name"}}` | | Okta App | `{"okta_directory_app": {"app_id": "0hgjthrgkfsTvbabc123", "logo_url": "https://ok12static.oktacdn.com/fs/bcg/4/geflkp5y2qNcK12ba3c"}}` | | Okta Role | `{"okta_directory_role":{"role_id":"GROUP_MEMBERSHIP_ADMIN-abcdefd7-6f36-47bf-81e3-8819bf123456","role_type":"GROUP_MEMBERSHIP_ADMIN"}}` Custom role:`{"okta_directory_role":{"role_id":"cJ04xma47yJwwL123aBC","role_type":"CUSTOM"}}` | | Salesforce Profile | `{"salesforce_profile": {"user_license": "1004Y000001Qb512ABC"}}` | # Opal API Rate Limits Source: https://docs.opal.dev/reference/opal-api-rate-limits # Overview Opal uses rate limits to ensure stability and reliability of our systems. # Rate limits | Route Name | Requests Per Second | | ------------------------------- | ------------------- | | PUT, POST `/v1/resources` | 50 | | PUT, POST `/v1/groups` | 50 | | Default - Applies to all routes | 100 | # Rate Limit Enforcement The above limits are org-wide, and apply to all API Tokens created for your organization. View your API tokens by navigating to settings, and then selecting "API Access Tokens". Requests above the rate limit will receive an `HTTP 429` Error. # Overview Source: https://docs.opal.dev/reference/opal-sdks Supported SDKs: * [Go](https://github.com/opalsecurity/opal-go) * [Python](https://github.com/opalsecurity/opal-python) If you do not see your language here, get in touch and we can generally generate an SDK on demand. Alternatively, you can make your own calls to our REST endpoints by referring to [API Reference](/reference). # Pagination Source: https://docs.opal.dev/reference/pagination Learn how to paginate bulk data requests to the Opal API. # Overview All bulk data lists that you can interact with via the Opal API support pagination via the **next** and **previous** cursors attached to API responses and the **page\_size** query parameter. ## Query Parameters * **cursor**: Denotes the starting position in the data list from where a paginated API endpoint should return bulk data. Get this value from the next or previous property of any previous paginated response. * **page\_size**: Limit on number of objects to return per request. ## Getting the Cursor You can find **next** and **previous** cursors in the response payload of an API request to a paginated endpoint. These cursors can be attached to requests to paginated API endpoints to query the next (or previous) page of results.