Provision Chainguard users with SCIM
How SCIM provisioning to the Chainguard platform behaves, and how to manage the provisioning connection over its …
For the complete documentation index, see llms.txt.
The Chainguard platform supports SCIM 2.0 provisioning from Okta. With SCIM enabled, Okta creates, updates, and deactivates Chainguard users as you assign and unassign them, and Chainguard links each provisioned user to their single sign-on (SSO) login automatically.
This guide covers the Okta-specific setup. For provisioning behavior, token lifecycle, and limits, refer to Provision Chainguard users with SCIM.
To complete this guide, you need the following:
chainctl.The rest of this guide refers to your identity provider by its UIDP, stored in the IDENTITY_PROVIDER environment variable:
export IDENTITY_PROVIDER=$(chainctl iam identity-providers list -o json | jq -r '.items[0].id')Organizations generally have one identity provider, so the list usually holds a single entry and the .items[0].id filter selects it. If the list holds more than one — because you belong to several organizations, or your organization has several providers — run chainctl iam identity-providers list on its own and set IDENTITY_PROVIDER to the UIDP of the provider you are configuring.
First, generate the bearer token Okta authenticates with:
chainctl iam identity-providers scim token generate $IDENTITY_PROVIDERThe token generate command prints the token to standard output and the SCIM endpoint URL to standard error. Chainguard shows the token exactly once, so keep both values for Step 2. Our guide Provision Chainguard users with SCIM outlines lifetime options and rotation.
Next, enable SCIM for your identity provider:
chainctl iam identity-providers scim enable $IDENTITY_PROVIDERGenerate the token first: scim enable fails with etag: etag is required if the identity provider has no token issued yet. If it instead fails with a message about owner-tier identities, your organization doesn’t yet have two directly assigned owners; refer to the prerequisites for SCIM provisioning.
Enabling SCIM clears the identity provider’s default role. If you set one with --default-role, the DEFAULT ROLE column in chainctl iam identity-providers list is empty afterward.
Okta hosts SCIM provisioning on an app integration, but you cannot add SCIM to the OIDC app integration you use for login. This means you must create a second app integration that exists only to carry provisioning.
In the Okta Admin Console, navigate to the Applications landing page, click Create App Integration, and choose SAML 2.0. Chainguard doesn’t use the SAML assertion, and sign-in stays on your existing OIDC app, so the wizard’s SAML settings need only well-formed URLs. Enter placeholder values like https://example.com/sso for Single sign-on URL and https://example.com/saml for Audience URI (SP Entity ID), and leave the other settings at their defaults. On the new app’s General tab, under App Settings, set Provisioning to SCIM.
Then open the app’s Provisioning tab, select Integration, and click Edit to configure the SCIM connection:
userName.cgscim_... token into the Bearer field. Do not add a Bearer prefix — Okta supplies the scheme itself, and a prefixed value fails authentication.Use Test API Credentials to confirm the URL and token, then save.
Okta’s connection test reads the SCIM endpoint and expects to find a user it recognizes there. A newly enabled Chainguard endpoint has no users yet, so on a first-time setup the test fails with this message. Despite the wording, authentication succeeded; the missing user is what blocks the save.
A workaround is to create the first user directly, then run the test again. Replace <scim_token> and <scim_endpoint_url> with the bearer token and endpoint URL for your identity provider, and <okta_username> and <okta_user_id> with the details of a real user you intend to provision.
curl -X POST \
-H "Authorization: Bearer <scim_token>" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "<okta_username>",
"externalId": "<okta_user_id>",
"active": true
}' \
"<scim_endpoint_url>/Users"Set externalId to the user’s Okta ID — the 00u... value in the page URL when you open that user under Directory > People. Okta’s test rejects an arbitrary value even though the endpoint returns the user, so this is not a field you can fill with a placeholder. externalId is also what Chainguard matches against at login, and a wrong value leaves the user unlinked with no error, so seed a real user you intend to provision.
With the integration saved, select To App in the Provisioning tab, click Edit, and enable Create Users, Update User Attributes, and Deactivate Users. These options appear only after you save the connection, and only for the actions you checked under Supported provisioning actions in Step 2.
In Okta, assign a user to the provisioning app integration you created in Step 2. Okta provisions them immediately. Have that user log in to Chainguard through Okta and confirm they have access. A provisioned user connects to their login by matching the SCIM externalId against the login token’s subject; with Okta, both default to the Okta user ID, so they align without configuration.
To confirm the rest of the lifecycle, deactivate that user in Okta or remove their assignment, and confirm that Chainguard refuses their next login. Then reactivate them and confirm that they can log in again.
Okta can also push groups to Chainguard. Chainguard matches each pushed group against your group-to-role mappings and grants the mapped roles to the group’s members as role bindings. These bindings persist between logins, unlike the session-scoped roles that claim-based group mappings grant, and they appear in chainctl iam role-bindings list.
You manage these bindings from Okta. To change who holds a role, change the group’s membership in Okta; Chainguard rejects manual edits to a binding that SCIM manages.
Store your organization’s UIDP in the ORGANIZATION variable:
export ORGANIZATION=$(chainctl iam organizations list -o json | jq -r '.items[0].id')This command stores the first organization in the list. If you belong to more than one organization, run echo $ORGANIZATION and confirm that it’s the organization you’re configuring.
Then map the Okta group to a Chainguard role. Replace <okta_group_name> with the group’s name exactly as it appears in Okta, and <role_name> with the role to grant, such as viewer or editor:
chainctl iam external-group-role-mappings create \
--external-group-id "<okta_group_name>" \
--role <role_name> \
--scope $ORGANIZATION \
--idp $IDENTITY_PROVIDERNote: Okta doesn’t send a stable identifier for groups, so Chainguard matches a pushed group by its name. If you rename the group in Okta, the mapping stops matching, and Chainguard reports no error. After a rename, create a mapping for the new name.
To push the group from Okta, follow these steps:
The group’s Push Status changes to Active once Okta has pushed it.
Chainguard creates a member’s role binding only after that member has logged in to Chainguard at least once. After that first login, membership changes in Okta take effect without another login.
To confirm that Okta provisioned the group’s members, list the users it has pushed. Members who haven’t logged in yet show no in the LOGGED IN column:
chainctl iam identity-providers scim users list $IDENTITY_PROVIDERHave a member of the group log in to Chainguard through Okta, then list the role bindings in your organization:
chainctl iam role-bindings list --parent $ORGANIZATIONThe output includes a binding with the mapped role and SCIM in the MANAGED BY column. The binding’s NAME / EMAIL column is empty, so identify the member by their identity ID instead. To look up that ID, replace <okta_username> with the member’s Okta username:
chainctl iam identity-providers scim users list $IDENTITY_PROVIDER -o json \
| jq -r '.[] | select(.user_name == "<okta_username>") | .identity_id'The binding’s IDENTITY column matches this value. If the command prints null, the member hasn’t logged in to Chainguard yet.
To confirm that removal works, remove the member from the group in Okta, then list the role bindings again. Chainguard deletes the binding shortly after Okta sends the change.
If you assigned the member to the provisioning app only through the group, removing them from the group also deactivates their Chainguard user. Okta doesn’t remove a deactivated user from the group it pushed, so the user can get the group’s role back if they’re reactivated. If that happens, open the Push Groups tab and select Push now from the group’s Push Status menu. Okta then sends the group’s current membership, and Chainguard removes the role.
A provisioning mismatch does not produce an error; the login proceeds as a normal, unlinked user. Confirm that you assigned the user to the provisioning app and that Okta provisioned them (check the app’s assignment and Okta’s provisioning logs), and that the app sends the default Okta user ID as externalId.
If the message is “No results for users returned,” refer to Error authenticating: No results for users returned in Step 2. Otherwise, confirm the base URL is the endpoint printed by token generate and that the Bearer field holds the correct cgscim_... token. The URL and token must come from the same token generate run.
429
Chainguard rate limits provisioning writes; Okta retries on its own schedule, so bulk assignments complete without intervention. For more information, refer to the Limits section.
Last updated: 2026-10-15 00:00