Repository navigation
Conversation
A self-hosted installation on a generic OIDC issuer can now take admission and roles from the issuer's group claim instead of manual membership. The policy is opt-in and off unless a group list is configured; with none of the new settings, behaviour and rendered chart output are unchanged. Settings (env, Helm identity.* values): - ARTIFACT_SERVER_OIDC_MEMBER_GROUPS / identity.oidcMemberGroups - ARTIFACT_SERVER_OIDC_ADMINISTRATOR_GROUPS / identity.oidcAdministratorGroups - ARTIFACT_SERVER_OIDC_GROUPS_CLAIM / identity.oidcGroupsClaim (default "groups") Lists are comma-separated, trimmed, and compared exactly. They require the OIDC issuer and client; a claim name without a list, or a list naming no group, is refused. `config check` reports the policy as configuration.oidcGroupRoles. ExternalIdentity gains groups (null when the provider reports none). The browser provider reads the claim from the verified ID token; the MCP verifier reads it from the access token, or from userinfo when it falls back there. A claim that is not a list of strings is a provider failure. WorkOS identities report null. With the policy on, every sign-in and every MCP token is evaluated: an administrator group grants administrator, else a member group grants member, else the person is refused and an active member is deactivated (demoted first, so the issuer can remove the last administrator). Role changes apply at once, a previously deactivated member is restored rather than duplicated, and the bootstrap-email rule does not apply. MCP OAuth no longer short-circuits through the stored subject binding under the policy. IdentityRepository gains setMemberRole, implemented for SQLite, Postgres, and Cloudflare D1.
Login-time group roles only act when someone signs in. This adds a
directory sync so members exist before their first sign-in and leavers
are deactivated without waiting for them to return.
New command: `artifactserver maintenance sync-directory --once`
(compact or external-storage, like cleanup-staging). It writes through
InstallationAccessService.reconcileDirectoryMembers on the identity
repository, never the HTTP admin API or API keys, and prints one JSON
line {groups, directoryUsers, admitted, reactivated, roleChanged,
deactivated, unchanged, skipped}; any failure exits non-zero.
One pass reads every configured group completely (exact-name group
search, then paged briefRepresentation members, 100 per page until a
short page) with a client-credentials token from the issuer's discovered
token endpoint, then reconciles: eligible users (enabled, verified,
email) get administrator if in an administrator group, else member;
missing ones are admitted, deactivated ones restored, roles changed, and
every other active member deactivated (demoted first, as at login).
An unknown group, token/HTTP/page error, or a directory with no eligible
user aborts before any write. Synced members bind to their OIDC subject
at first sign-in through the existing verified-email path.
Settings (valid only with OIDC group roles; issuer must be
<base>/realms/<realm>, admin API <base>/admin/realms/<realm>):
- ARTIFACT_SERVER_OIDC_DIRECTORY_PROVIDER=keycloak
- ARTIFACT_SERVER_OIDC_DIRECTORY_CLIENT_ID
- ARTIFACT_SERVER_OIDC_DIRECTORY_CLIENT_SECRET / _FILE
`config check` reports them as configuration.oidcDirectory.
Helm: identity.oidcDirectoryProvider (default keycloak),
identity.oidcDirectoryClientId, secret.keys.oidcDirectoryClientSecret
(mounted as a file), and a directorySync block (schedule "*/10 * * * *",
activeDeadlineSeconds, backoffLimit). Setting the client ID adds the
<release>-sync-directory CronJob (concurrencyPolicy Forbid, same image,
service account, security, env, and Secret mounts as cleanup-staging)
and counts one more connection in the postgresConnectionBudget check.
Directory values require the OIDC issuer, client, and group lists; the
client ID requires its secret key.
Intended rendering change: every pod spec (Deployment, migration Job,
cleanup CronJob, directory sync CronJob, test pod) now sets
enableServiceLinks: false. With the release named artifact-server,
Kubernetes injected ARTIFACT_SERVER_PORT=tcp://... from the Service,
which the CLI reads as --port, so cleanup-staging failed every run.
Without the new values this is the only difference from v0.1.2 output.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With a generic OIDC issuer, access to a private installation is a hand-maintained member list: an administrator admits each person by email, assigns their role in Artifact Server, and must remember to deactivate leavers. Teams that already manage access with IdP groups (Keycloak, Authentik, Zitadel and others put a
groupsclaim in tokens) cannot reuse those groups. People cannot be seen, shared with, or assigned work until each one has been admitted by hand.Change
Two opt-in features. With none of the new settings set, behaviour and the rendered chart are unchanged, except that every chart pod now sets
enableServiceLinks: false(see below).1. Group roles at sign-in (commit "Admit OIDC members and set their roles from issuer groups")
ARTIFACT_SERVER_OIDC_MEMBER_GROUPS,ARTIFACT_SERVER_OIDC_ADMINISTRATOR_GROUPS(comma lists, exact match) andARTIFACT_SERVER_OIDC_GROUPS_CLAIM(defaultgroups). Chart values:identity.oidcMemberGroups,identity.oidcAdministratorGroups,identity.oidcGroupsClaim.2. Directory sync, so members exist before they first sign in (commit "Sync OIDC members from a Keycloak directory before they sign in")
artifactserver maintenance sync-directory --oncereads the configured groups from the Keycloak admin REST API (<base>/admin/realms/<realm>, derived from the issuer). It authenticates with a client-credentials service account that needs only realm-managementview-users+query-groups.ARTIFACT_SERVER_OIDC_DIRECTORY_PROVIDER=keycloak,ARTIFACT_SERVER_OIDC_DIRECTORY_CLIENT_ID, andARTIFACT_SERVER_OIDC_DIRECTORY_CLIENT_SECRET[_FILE].identity.oidcDirectoryClientIdplussecret.keys.oidcDirectoryClientSecretenable a<release>-sync-directoryCronJob (directorySync.schedule, default every 10 minutes), which is counted inpostgresConnectionBudget.Also in the chart:
enableServiceLinks: falseon every pod. With the release namedartifact-server, Kubernetes service links injectARTIFACT_SERVER_PORT=tcp://<ip>:80, which the CLI reads as its port, so the cleanup CronJob failed on every run. This is the one rendering change with no new values set.Tests
Live verification: we run this branch, as an
orvexfork build, against Keycloak 26.5 with CNPG Postgres. The scheduled sync admitted two not-yet-signed-in people with the correct roles. A probe user was then admitted, reactivated, promoted, demoted and deactivated as its Keycloak group memberships changed.Deployment effects
There are no migrations. The new Postgres access is only
UPDATE installation_members SET role=…, through the newsetMemberRolerepository method, which is also implemented for SQLite and D1.Depends on nothing else. #94 (OAuth bearer on upload
PUT) is independent.