Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ Check out [Baton](https://github.com/conductorone/baton) to learn more the proje
|------------|--------|
| Sync | Yes |
| Account Creation (Users, User Accounts) | Yes — one type per connector instance, see `create-account-resource-type` below |
| Account Deletion (Users, User Accounts) | Yes |
| Account Deletion (Users, User Accounts) | Yes — except the user account the connector authenticates with |
| Provisioning (Grant/Revoke) | Yes — Groups (`userAccount` principal only, Group Access accounts only), User Groups (static groups only), Sites (`user` principal only), Managed Devices (`assigned`, `user` principal only, opt-in — see Data Model below), Roles (built-in privilege sets and individual privileges, `userAccount`/`group` principal, see below). |

Account creation/deletion and Grant/Revoke all require `-p`/`$BATON_PROVISIONING` to be set; without it the connector only syncs.

Tested with Jamf Pro 11.32.1. Managed Devices use the Jamf Pro API v4 computers-inventory and v2 mobile-devices endpoints.
Requires Jamf Pro 11.26.0 or later (`GET /api/v1/users` was added in 11.26.0). Managed Devices, which are opt-in, use the Jamf Pro API v4 computers-inventory endpoints, added in 11.30.0, and the v2 mobile-devices endpoints.

## Authentication

Expand All @@ -36,9 +36,9 @@ The Administrator privilege set covers everything. For least privilege, grant:
| Sync users | `Read User` |
| Sync user accounts and groups | `Read Accounts` |
| Sync user groups | `Read Static User Groups`, `Read Smart User Groups` (Smart is only needed when smart groups exist — it's checked per group) |
| Sync sites | `Read Sites`, `Read User`, `Read Static User Groups`, `Read Smart User Groups`, `Read Accounts` (site grants are built by reading users, user groups and accounts) |
| Sync sites | `Read Sites`, `Read Accounts` |
| Sync roles | `Read API Roles`, `Read Accounts` |
| Sync managed devices (opt-in) | `Read Computers`, `Read Mobile Devices`, `Read User` (needed even if `user` isn't synced) |
| Sync managed devices (opt-in) | `Read Computers`, `Read Mobile Devices` |
| Create/delete users | `Create User` and `Read User` (the connector reads the user back after creating); `Delete User` |
| Create/delete user accounts | `Create Accounts` and `Read Accounts`; `Delete Accounts` |
| Group Grant/Revoke (`userAccount` principal) | `Read Accounts`, `Update Accounts` |
Expand All @@ -49,15 +49,12 @@ The Administrator privilege set covers everything. For least privilege, grant:

`Read Accounts`/`Create Accounts`/`Update Accounts`/`Delete Accounts` cover both admin user accounts and admin groups — Jamf has no separate privilege for account groups. Every write path reads the object first, so each `Update` privilege above needs its `Read` counterpart too.

Jamf reports a missing privilege differently depending on which API it uses: the Classic API (`/JSSResource` — Users, Accounts, Static User Groups) answers with HTTP 401, and the Jamf Pro API (`/api/v1`, `/api/v2`, `/api/v4` — the roles catalog, Computers, Mobile Devices) with HTTP 403. The connector reports both as a permission error.
A missing privilege fails the sync or the operation with a permission error.

Group Grant/Revoke adds or removes a `userAccount` from an admin account
group's member list. A group with no members cannot receive its first member
through the connector, and Revoke on such a group returns a retryable error
instead of reporting success — Jamf can transiently return an empty member
list even when the group genuinely has members, and the connector refuses to
write over (or assume someone was removed from) a list it can't trust is
complete. Only Group Access
instead of reporting success. Only Group Access
accounts can usefully be granted membership — a Full or Site Access account's
rights come from its own `privilege_set`, not its groups, so Grant rejects
adding one (membership grants it nothing). Do not use Grant/Revoke on
Expand Down Expand Up @@ -86,20 +83,33 @@ built-in privilege sets (`Administrator`, `Auditor`, `Enrollment Only`) and
every individual privilege are grantable — `Custom` itself is not a role you
grant directly. Granting a set displaces whichever one the principal
previously held. Revoke moves the principal to `Custom` with only the
`Read License Information` privilege — the lowest access Jamf allows, since
`privilege_set` has no neutral "no access" value. Jamf keeps that privilege on
every `Custom` set it creates and never lets a client remove it, so revoking
it specifically is rejected. Individual privileges can
`Read License Information` privilege, which can't be revoked itself.
Individual privileges can
only be granted while the principal's `privilege_set` is already `Custom`:
the flow is Revoke the current set first (which moves it to Custom), then
Grant the individual privileges — granting a built-in set again later leaves
Custom and discards those individual privileges. Group Access accounts are
not supported for Role Grant/Revoke — their rights come from their groups, so
assign the role to the group instead. Changing a Full or Site Access
account's role removes it from any admin group it belonged to (that
membership granted it nothing). A write rejected for the connector's own Jamf
account's role removes it from any admin group it belonged to. A write rejected for the connector's own Jamf
account is reported as a permission error.

## Sync cost

Requests per full sync:

| Resource type | Requests |
|---|---|
| `user` | ⌈users / 1000⌉ |
| `userAccount` | ⌈accounts / 100⌉ |
| `group` | 1, plus one read per group |
| `userGroup` | 1, plus one read per user group |
| `site` | 1, plus per site ⌊users / 1000⌋ + 1, ⌊user groups / 1000⌋ + 1 and ⌈accounts / 100⌉, each only when `user`, `userGroup` or `userAccount` is synced |
| `role` | 1, plus ⌈accounts / 100⌉ + ⌈`Custom` accounts / 10⌉ and one read per `Custom` account, when `userAccount` is synced |
| `managedDevice` | ⌈computers / 100⌉ + ⌈mobile devices / 100⌉ |

Validation adds one request, plus one token request per new process (each Lambda container) and each time the token expires. Most SDK calls make one request; the largest is a `role` grants page of `Custom` accounts, which reads each one's details (up to 11). A group or user group detail comes back whole, so that read grows with the size of the group. For example, 50,000 users, 1,000 accounts (300 of them `Custom`), 50 groups, 200 user groups, 20 sites holding the users and 20,000 devices come to roughly 950 requests; sync time is about that count times the instance's API response time.

## Jamf Pro console admin account creation (`userAccount`)

When `--create-account-resource-type=userAccount`, a created account is
Expand Down
25 changes: 24 additions & 1 deletion baton_capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"displayName": "Group",
"traits": [
"TRAIT_GROUP"
],
"annotations": [
{
"@type": "type.googleapis.com/c1.connector.v2.SkipEntitlements"
}
]
},
"capabilities": [
Expand Down Expand Up @@ -41,6 +46,14 @@
"displayName": "Role",
"traits": [
"TRAIT_ROLE"
],
"annotations": [
{
"@type": "type.googleapis.com/c1.connector.v2.SkipEntitlements"
},
{
"@type": "type.googleapis.com/c1.connector.v2.TypeScopedGrants"
}
]
},
"capabilities": [
Expand All @@ -52,7 +65,12 @@
{
"resourceType": {
"id": "site",
"displayName": "Site"
"displayName": "Site",
"annotations": [
{
"@type": "type.googleapis.com/c1.connector.v2.SkipEntitlements"
}
]
},
"capabilities": [
"CAPABILITY_SYNC",
Expand Down Expand Up @@ -105,6 +123,11 @@
"displayName": "User Group",
"traits": [
"TRAIT_GROUP"
],
"annotations": [
{
"@type": "type.googleapis.com/c1.connector.v2.SkipEntitlements"
}
]
},
"capabilities": [
Expand Down
1 change: 1 addition & 0 deletions cmd/baton-jamf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ func main() {
cfg.Config,
connector.New,
connectorrunner.WithDefaultCapabilitiesConnectorBuilderV2(&connector.Jamf{}),
connectorrunner.WithSessionStoreEnabled(),
)
}
24 changes: 12 additions & 12 deletions docs/connector.mdx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Docs] "ConductorOne" instead of "C1"

The docs prose uses "C1", but this file still says "ConductorOne" in the frontmatter description and og:description (L4-5), the Account Provisioning Target field (L163, "Which Jamf account type ConductorOne should create") and the download link text (L312, "ConductorOne download center"). Not changed by this PR.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: "Set up a Jamf connector"
og:title: "Set up a Jamf connector"
description: "ConductorOne provides identity governance and just-in-time provisioning for Jamf Pro. Integrate your Jamf Pro instance with ConductorOne to run user access reviews (UARs), enable just-in-time access requests, and automatically provision and deprovision Jamf accounts."
og:description: "ConductorOne provides identity governance and just-in-time provisioning for Jamf Pro. Integrate your Jamf Pro instance with ConductorOne to run user access reviews (UARs), enable just-in-time access requests, and automatically provision and deprovision Jamf accounts."
description: "C1 provides identity governance and just-in-time provisioning for Jamf Pro. Integrate your Jamf Pro instance with C1 to run user access reviews (UARs), enable just-in-time access requests, and automatically provision and deprovision Jamf accounts."
og:description: "C1 provides identity governance and just-in-time provisioning for Jamf Pro. Integrate your Jamf Pro instance with C1 to run user access reviews (UARs), enable just-in-time access requests, and automatically provision and deprovision Jamf accounts."
sidebarTitle: "Jamf"
---

Expand All @@ -26,18 +26,18 @@ The Jamf connector syncs the following resources:
{/* AUTO-GENERATED:END - capabilities */}

**Notes:**
- Jamf has two distinct account types: **Users** (directory users) and **User Accounts** (Jamf Pro console admins). The connector can only create **one** of these types per connector instance — set by the **Account Provisioning Target** configuration field. Deletion works for both types regardless of this setting. Created accounts are `Enabled`, `Full Access`, and default to the `Auditor` privilege set; a random password is generated and returned once, as plaintext. See [Jamf account privileges](#jamf-account-privileges) below for the privileges this requires.
- Jamf has two distinct account types: **Users** (directory users) and **User Accounts** (Jamf Pro console admins). The connector can only create **one** of these types per connector instance — set by the **Account Provisioning Target** configuration field. Deletion works for both types regardless of this setting, except for the user account the connector authenticates with, which Delete refuses. Created accounts are `Enabled`, `Full Access`, and default to the `Auditor` privilege set; a random password is generated and returned once, as plaintext. See [Jamf account privileges](#jamf-account-privileges) below for the privileges this requires.
- **User Groups**: Grant/Revoke adds or removes a Jamf user from a **static** User Group only — Smart User Groups are rejected.
- **Sites**: Grant/Revoke is only supported for the `user` principal — a Jamf user's site membership is a true multi-valued list. `userGroup`/`userAccount`/`group` site membership remains sync-only (each of those objects can only belong to one site at a time, so granting a new one would silently displace the old one instead of adding a membership).
- **Sites**: Grant/Revoke is only supported for the `user` principal — a Jamf user's site membership is a true multi-valued list. `userGroup`/`userAccount`/`group` site membership remains sync-only.
- **Managed Devices**: Grant/Revoke sets or clears a device's `assigned` user (computer or mobile device) — single-valued, so granting to a new user displaces whichever user was previously assigned. Devices with no assignee expose no grantable `assigned` entitlement. Computers use the Jamf Pro v4 computers-inventory API: Grant overwrites the assignee's username, name, email, position and phone with the new user's values (Jamf never auto-populates these for computers), and Revoke clears all five. Mobile devices take these fields from the directory user automatically — Grant only sets the username, and Revoke clears it (and everything Jamf derived from it) by clearing the username.
- **Groups** (Jamf Pro admin account groups): Grant/Revoke a `userAccount` with **Group Access** only — a **Full** or **Site Access** account's rights come from its own privilege_set, not its groups, so Grant rejects adding one. A group with no members cannot receive its first member through the connector, and Revoke on such a group returns a retryable error instead of reporting success (an ambiguous empty read is never written over, or assumed to mean someone was removed). Do not use Grant/Revoke on directory (LDAP) groups: their membership is managed by the directory and the connector does not detect them.
- **Roles**: Grant/Revoke sets or clears a `userAccount` or `group`'s role — the three built-in privilege sets (`Administrator`, `Auditor`, `Enrollment Only`) and every individual privilege are grantable; `Custom` itself is not a role you grant directly. Revoke moves the principal to `Custom` with only the `Read License Information` privilege (Jamf's lowest access, and not itself revocable). Individual privileges can only be granted while the principal is already `Custom`: revoke the current set first, then grant the individual privileges. **Group Access** accounts are rejected for Role Grant/Revoke — assign the role to the group instead. Changing a **Full** or **Site Access** account's role removes it from any admin group it belonged to (that membership granted it nothing).
- **Groups** (Jamf Pro admin account groups): Grant/Revoke a `userAccount` with **Group Access** only — a **Full** or **Site Access** account's rights come from its own privilege_set, not its groups, so Grant rejects adding one. A group with no members cannot receive its first member through the connector, and Revoke on such a group returns a retryable error instead of reporting success. Do not use Grant/Revoke on directory (LDAP) groups: their membership is managed by the directory and the connector does not detect them.
- **Roles**: Grant/Revoke sets or clears a `userAccount` or `group`'s role — the three built-in privilege sets (`Administrator`, `Auditor`, `Enrollment Only`) and every individual privilege are grantable; `Custom` itself is not a role you grant directly. Revoke moves the principal to `Custom` with only the `Read License Information` privilege, which can't be revoked itself. Individual privileges can only be granted while the principal is already `Custom`: revoke the current set first, then grant the individual privileges. **Group Access** accounts are rejected for Role Grant/Revoke — assign the role to the group instead. Changing a **Full** or **Site Access** account's role removes it from any admin group it belonged to.

<Note>
**Managed Devices is opt-in.** This resource type is off by default in C1 so existing connectors keep working after upgrading. Enable it by selecting the **Managed Device** resource type in the connector's sync configuration. Self-hosted runs sync it unless `--sync-resource-types`/`$BATON_SYNC_RESOURCE_TYPES` leaves it out. When enabled, the Jamf account used by the connector must additionally have the **Read Computers** and **Read Mobile Devices** privileges, or the sync will fail.
</Note>

Tested with Jamf Pro 11.32.1. Managed Devices use the Jamf Pro API v4 computers-inventory and v2 mobile-devices endpoints.
Requires Jamf Pro 11.26.0 or later (Managed Devices: 11.30.0).

## Gather Jamf credentials

Expand Down Expand Up @@ -91,9 +91,9 @@ The **Administrator** privilege set covers everything. For least privilege, gran
| Sync users | `Read User` |
| Sync user accounts and groups | `Read Accounts` |
| Sync user groups | `Read Static User Groups`, `Read Smart User Groups` (Smart is only needed when smart groups exist — it's checked per group) |
| Sync sites | `Read Sites`, `Read User`, `Read Static User Groups`, `Read Smart User Groups`, `Read Accounts` (site grants are built by reading users, user groups and accounts) |
| Sync sites | `Read Sites`, `Read Accounts` |
| Sync roles | `Read API Roles`, `Read Accounts` |
| Sync managed devices (opt-in) | `Read Computers`, `Read Mobile Devices`, `Read User` (needed even if `user` isn't synced) |
| Sync managed devices (opt-in) | `Read Computers`, `Read Mobile Devices` |
| Create/delete users | `Create User` and `Read User` (the connector reads the user back after creating); `Delete User` |
| Create/delete user accounts | `Create Accounts` and `Read Accounts`; `Delete Accounts` |
Comment thread
luisina-santos marked this conversation as resolved.
Resolved
| Group Grant/Revoke (`userAccount` principal) | `Read Accounts`, `Update Accounts` |
Expand All @@ -104,7 +104,7 @@ The **Administrator** privilege set covers everything. For least privilege, gran

`Read Accounts`/`Create Accounts`/`Update Accounts`/`Delete Accounts` cover both admin user accounts and admin groups — Jamf has no separate privilege for account groups. Every write path reads the object first, so each `Update` privilege above needs its `Read` counterpart too.

Jamf reports a missing privilege differently depending on which API it uses: the Classic API (`/JSSResource` — Users, Accounts, Static User Groups) answers with HTTP 401, and the Jamf Pro API (`/api/v1`, `/api/v2`, `/api/v4` — the roles catalog, Computers, Mobile Devices) with HTTP 403. The connector reports both as a permission error.
A missing privilege fails the sync or the operation with a permission error.

## Configure the Jamf connector

Expand Down Expand Up @@ -160,7 +160,7 @@ Choose an authentication method and enter its fields:
Both methods also take:

- **Instance URL**: The base URL of your Jamf Pro instance, including `https://` (for example `https://yourcompany.jamfcloud.com`).
- **Account Provisioning Target** (optional): Which Jamf account type ConductorOne should create when provisioning accounts — **user** (default) creates directory users, **userAccount** creates Jamf Pro console admin accounts. Only one type can be created at a time per connector instance.
- **Account Provisioning Target** (optional): Which Jamf account type C1 should create when provisioning accounts — **user** (default) creates directory users, **userAccount** creates Jamf Pro console admin accounts. Only one type can be created at a time per connector instance.
</Step>

{/* AUTO-GENERATED:END - config-params */}
Expand Down Expand Up @@ -309,7 +309,7 @@ Check that the connector data uploaded correctly. In C1, click **Apps**. On the
</Tabs>

<Tip>
You can find pre-built binaries for `baton-jamf` on the [ConductorOne download center](https://dist.conductorone.com/ConductorOne/baton-jamf).
You can find pre-built binaries for `baton-jamf` on the [C1 download center](https://dist.conductorone.com/ConductorOne/baton-jamf).
</Tip>


Expand Down
Loading
Loading