Repository navigation
Document Okta SCIM group push for GA (DOCS-270) #4237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ lead: "" | |
| description: "Procedural tutorial on how to set up SCIM provisioning from Okta to the Chainguard platform." | ||
| type: "article" | ||
| date: 2026-08-14T00:00:00+00:00 | ||
| lastmod: 2026-09-28T14:00:04+00:00 | ||
| lastmod: 2026-10-15T00:00:00+00:00 | ||
| draft: false | ||
| tags: ["Procedural"] | ||
| images: [] | ||
|
|
@@ -49,17 +49,19 @@ chainctl iam identity-providers scim enable $IDENTITY_PROVIDER | |
|
|
||
| Generate 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](/platform/administration/custom-idps/scim-provisioning/scim-overview/#prerequisites). | ||
|
|
||
| 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. | ||
|
|
||
| ## Step 2: Create the Okta provisioning app | ||
|
|
||
| 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 **Applications and Resources** > **Applications**, click **Create App Integration**, and choose **SAML 2.0**. Complete the wizard with placeholder values; Chainguard does not consume the SAML assertion, and sign-in stays on your existing OIDC app. On the new app's **General** tab, set **Provisioning** to **SCIM**. | ||
| 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: | ||
|
|
||
| * **SCIM connector base URL**: the endpoint printed in Step 1. | ||
| * **Unique identifier field for users**: `userName`. | ||
| * **Supported provisioning actions**: check **Push New Users** and **Push Profile Updates**. Leave **Push Groups** unchecked; Chainguard's SCIM endpoint accepts user provisioning only. | ||
| * **Supported provisioning actions**: select **Push New Users**, **Push Profile Updates**, and **Push Groups**. You need **Push Groups** only if you plan to grant roles from Okta groups, as described in [Step 5](#step-5-push-groups-to-grant-roles). | ||
| * **Authentication Mode**: select **HTTP Header**, then paste the bare `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. | ||
|
|
@@ -95,6 +97,76 @@ In Okta, assign a user to the provisioning app integration you created in Step 2 | |
|
|
||
| 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. | ||
|
|
||
| ## Step 5: Push groups to grant roles | ||
|
|
||
| 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](/platform/administration/custom-idps/grant-roles-from-groups/) 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. | ||
|
|
||
| ### Map the group to a role | ||
|
|
||
| Store your organization's UIDP in the `ORGANIZATION` variable: | ||
|
|
||
| ```sh | ||
| 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`: | ||
|
|
||
| ```sh | ||
| chainctl iam external-group-role-mappings create \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two additions: the mapping must use the group's name as it was first pushed, and you can only map a role you already hold. |
||
| --external-group-id "<okta_group_name>" \ | ||
| --role <role_name> \ | ||
| --scope $ORGANIZATION \ | ||
| --idp $IDENTITY_PROVIDER | ||
| ``` | ||
|
|
||
| {{< note >}} | ||
| 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the reverse of what happens. After a rename the group keeps the name it had at its first push as the key, so the existing mapping keeps working and a mapping for the new name matches nothing. Suggested: "Okta doesn't send a stable identifier for groups, so Chainguard identifies a group by its name the first time it is pushed, matched exactly including case. If you rename the group in Okta, the mapping keeps working under the original name, so keep using the original name in mappings. You can't reuse the original name for a new group until you unlink the original with Delete the group in the target app." |
||
| {{< /note >}} | ||
|
|
||
| ### Push the group from Okta | ||
|
|
||
| To push the group from Okta, follow these steps: | ||
|
|
||
| 1. If you set up the provisioning app before group push was available, open its **Provisioning** tab, select **Integration**, click **Edit**, and select **Push Groups** under **Supported provisioning actions**. | ||
| 2. On the provisioning app's **Assignments** tab, click **Assign** > **Assign to Groups**, and assign the group. Okta pushes group membership only for users assigned to the app, and assigning the group also provisions its members as Chainguard users. | ||
| 3. On the **Push Groups** tab, click **Push Groups** > **Find groups by name**, and enter the group's name. | ||
| 4. Leave **Push group memberships immediately** selected, and click **Save**. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth adding how to retire a group. Delete the group in the target app removes the group and the roles only it granted. Leave the group in the target app sends nothing, so the group and its roles stay as they were and people removed from it later keep their roles. Deleting a group never deprovisions its members: unassign them from the app to remove them. |
||
|
|
||
| The group's **Push Status** changes to **Active** once Okta has pushed it. | ||
|
|
||
| ### Verify the role bindings | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two additions. A new mapping or a membership change takes up to about five minutes to apply, and a person's first sign-in applies theirs immediately. And |
||
|
|
||
| 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: | ||
|
|
||
| ```sh | ||
| chainctl iam identity-providers scim users list $IDENTITY_PROVIDER | ||
| ``` | ||
|
|
||
| Have a member of the group log in to Chainguard through Okta, then list the role bindings in your organization: | ||
|
|
||
| ```sh | ||
| chainctl iam role-bindings list --parent $ORGANIZATION | ||
| ``` | ||
|
|
||
| The 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: | ||
|
|
||
| ```sh | ||
| 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The workaround is right, but the cause is slightly different. Okta deactivates the user without sending a membership removal, so Chainguard still holds the membership, and after reactivation the role is restored from it, normally within about five minutes. Push now makes Okta send the group's current members, which removes the stale member. Suggested: "...the user can get the group's role back a few minutes after they're reactivated. To prevent this, select Push now before you reactivate the user." |
||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### A user logged in but wasn't linked | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ lead: "" | |
| description: "How SCIM provisioning to the Chainguard platform behaves, and how to manage the provisioning connection over its lifetime." | ||
| type: "article" | ||
| date: 2026-08-14T00:00:00+00:00 | ||
| lastmod: 2026-09-28T14:00:04+00:00 | ||
| lastmod: 2026-10-15T00:00:00+00:00 | ||
| draft: false | ||
| tags: ["Conceptual", "Procedural"] | ||
| images: [] | ||
|
|
@@ -14,7 +14,9 @@ weight: 10 | |
|
|
||
| System for Cross-domain Identity Management (SCIM) is an open standard for automating the exchange of user identity information between systems. Chainguard uses SCIM to create and deactivate user accounts based on your identity provider (IdP). Connect your IdP's SCIM provisioning once; from then on, assigning a user to the application provisions them, and deactivating or unassigning them removes their Chainguard access. Accounts follow your IdP, so you manage access in one place. | ||
|
|
||
| Chainguard's SCIM provisioning manages accounts, not roles. To grant roles from IdP group membership, use [group mappings](/platform/administration/custom-idps/grant-roles-from-groups/); the two are independent, and you can use either or both. Chainguard's SCIM endpoint accepts user provisioning only: group provisioning over SCIM is not yet available, so leave your IdP's SCIM group push turned off. | ||
| Chainguard's SCIM provisioning manages accounts, and with Okta it can also grant roles. Okta can push groups over SCIM, and Chainguard grants the roles you've mapped to each group as role bindings for its members. To set this up, refer to [Push groups to grant roles](/platform/administration/custom-idps/scim-provisioning/okta-scim/#step-5-push-groups-to-grant-roles) in the Okta guide. Group provisioning over SCIM isn't yet available for Microsoft Entra ID, so leave its SCIM group push turned off. | ||
|
|
||
| You can also grant roles from the groups claim in your IdP's login token, using [group mappings](/platform/administration/custom-idps/grant-roles-from-groups/). Group mappings work with or without SCIM. | ||
|
|
||
| This page explains how provisioning behaves and how to manage the connection over its lifetime. To connect a specific identity provider, follow its guide: | ||
|
|
||
|
|
@@ -27,8 +29,8 @@ Provisioning behaves the same way for every provider: | |
|
|
||
| * **Provisioning creates records, logins create accounts.** When your IdP provisions a user, Chainguard stores a provisioning record. Chainguard creates the user's account the first time they log in, exactly as it would without SCIM, and connects it to their provisioning record automatically. Users who have logged in before are connected at their next login, with no re-registration; their existing access is unaffected. | ||
| * **Matching is by `externalId`, never by email.** A provisioned user connects to a login by matching the SCIM `externalId` your IdP sends against the subject of the login token. Each provider guide covers how to make the two align. Chainguard never uses email addresses for matching. | ||
| * **Deactivation takes effect at the user's next login or token refresh.** When your IdP deactivates or unassigns a provisioned user, their current access token runs out on its own schedule (up to an hour), and Chainguard refuses every attempt to log in or refresh after the deactivation. Reactivating the user in your IdP restores their ability to log in; it does not restore any role bindings that were removed while they were deactivated. | ||
| * **Provisioning does not assign roles.** A provisioned user who logs in gets the identity provider's default role, plus any group-mapped roles and role bindings they hold, same as any other user. | ||
| * **Deactivation takes effect at the user's next login or token refresh.** When your IdP deactivates or unassigns a provisioned user, their current access token runs out on its own schedule (up to an hour), and Chainguard refuses every attempt to log in or refresh after the deactivation. Reactivating the user in your IdP restores their ability to log in; it does not restore any role bindings that were removed while they were deactivated. Bindings from Okta group push are the exception: Chainguard re-creates them from the user's group memberships. | ||
| * **Provisioning assigns roles only through pushed groups.** A provisioned user who logs in gets any group-mapped roles and role bindings they hold, same as any other user. With Okta, they also get role bindings from the groups Okta pushes, as described in [Push groups to grant roles](/platform/administration/custom-idps/scim-provisioning/okta-scim/#step-5-push-groups-to-grant-roles). Enabling SCIM clears the identity provider's default role. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Chainguard re-creates them from the user's group memberships" should add "within a few minutes", since a background process restores them. |
||
| * **Enabling SCIM does not restrict other logins.** Users who aren't provisioned log in exactly as before; provisioning adds lifecycle control only for the users your IdP sends. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enabling SCIM needs at least one owner who can sign in and whose owner role is assigned directly (not through SCIM or a group mapping), not two. The same "two owners" wording is at okta-scim line 24 and scim-overview.md lines 40 and 71, outside this diff. Two is a good recommendation, but it isn't enforced.