Stateless reconciler that pushes ACTIVATE users, groups, and SSH public keys into CoreWeave's SCIM endpoint. Once records land in CoreWeave IAM, CoreWeave's AUP (Automated User Provisioning) and SUP (SUNK User Provisioning) propagate identities into CKS and SUNK clusters.
This is the companion to pw-activate-ldap. The two repos are alternatives, not layered:
- pw-activate-ldap: PW operates an LDAP directory; SUNK binds to it. PW-managed staging point.
- pw-activate-scim-sync: CoreWeave IAM is the staging point; PW just pushes records into it. AUP/SUP handle the rest.
Pick whichever side of the line your customer prefers.
+------------------+ +-----------------+ +----------------+
| ACTIVATE control | REST | syncer | HTTPS | CoreWeave |
| plane |<--------| (CronJob) |------->| SCIM endpoint |
+------------------+ +-----------------+ +----------------+
^ |
| pw ssh-public-keys (subprocess) v
| +----------------+
+------------------------------------------------| CoreWeave IAM |
+----------------+
|
AUP + SUP
|
v
+------------------+
| CKS + SUNK login |
+------------------+
The syncer is stateless; each run is a full reconciliation. State lives only in CoreWeave IAM. Records the syncer manages carry an externalId of pw-activate:user:<key> or pw-activate:group:<id>, and the syncer's SCIM list calls filter on that prefix, so anything in CW IAM without the prefix is invisible and untouched.
Notes that shape the design:
- No uid/gid allocator. SUP assigns POSIX numbers downstream; this syncer only pushes the identity records.
- CoreWeave's SCIM endpoint is one-way. CW does not push back; we treat ACTIVATE as authoritative.
- Nested groups are not supported by the CW SCIM endpoint, so groups carry flat user references only.
- Users removed from ACTIVATE are deactivated (
active: false), not hard-deleted. SUP treats deactivation as the deprovisioning signal.
CoreWeave's docs for the downstream half:
pw-activate-scim-sync/
src/pw_scim_sync/
activate/ ACTIVATE REST client + pw ssh-public-keys shim
scim_dest/ SCIM 2.0 client, resource models, mapper
sync/ diff + apply (the reconciler core)
tests/ diff and config unit tests
k8s/ CronJob, ConfigMap, Secret
Dockerfile builds the syncer image
pyproject.toml
env.example local-dev env
# 1. Get the SCIM URL path segment and bearer token from CW Console.
# Console -> Identity -> SCIM. Drop them into secret.example.yaml.
# 2. Edit configmap.example.yaml and secret.example.yaml.
kubectl apply -f k8s/configmap.example.yaml
kubectl apply -f k8s/secret.example.yaml
kubectl apply -f k8s/cronjob.yaml
# 3. Trigger a manual run to validate end-to-end.
kubectl -n pw-scim create job initial-sync \
--from=cronjob/pw-activate-scim-sync
kubectl -n pw-scim logs job/initial-sync -fuv sync
cp env.example .env
# fill in ACTIVATE_API_KEY, ACTIVATE_ORG_*, CW_SCIM_ORG_USERID, CW_SCIM_BEARER_TOKEN
uv run pw-activate-scim-sync --dry-run
uv run pw-activate-scim-sync
uv run pytestThe diff layer (src/pw_scim_sync/sync/diff.py) has no I/O and is the primary unit-test target.
By default the syncer projects every group in the ACTIVATE org. To restrict to a subset, set ACTIVATE_SYNC_GROUPS to a comma-separated list of group names. Users who are not members of any selected group are not synced and any SCIM records the syncer previously created for them will be deactivated on the next run, so think of this as a hard scope, not a hint.
ACTIVATE_SYNC_GROUPS=hpc-users,research-team,gpu-pilotFilter values are matched by ACTIVATE group name (not slug, not ID). Names in the filter that do not exist in ACTIVATE are logged as warnings but do not fail the run.
The syncer is not a full SCIM 2.0 server. It only consumes what AUP needs:
GET /Users?filter=externalId sw "pw-activate:": list managed usersPOST /Users: create userPATCH /Users/<id>: replace SSH keys (urn:coreweave:params:scim:schemas:extension:coreweave:2.0:CoreWeaveUser:sunkSshKeys), toggleactiveGET /Groups?filter=externalId sw "pw-activate:": list managed groupsPOST /Groups: create groupPATCH /Groups/<id>: replacemembers
The User resource carries the CoreWeave extension urn:coreweave:params:scim:schemas:extension:coreweave:2.0:CoreWeaveUser with a sunkSshKeys array; SUP reads that field when projecting POSIX login state.
CW_SCIM_BEARER_TOKEN is issued from CW Console and is the only credential this syncer holds. To rotate, issue a new token in Console, update the pw-activate-scim-sync Secret, then revoke the old token. The next CronJob run picks up the new value; in-flight Jobs continue with the old token until they finish.