Skip to content
Open
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
88 changes: 80 additions & 8 deletions developer/terraform-best-practices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@

Use [conductorone_custom_app_entitlement](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/resources/custom_app_entitlement) when you need to create a virtual entitlement for a permission not yet discovered by a connector.

This resource also supports the `match_baton_id` field, which links the Terraform resource to an external ID (such as an Okta group ID). When the connector syncs, C1 merges the two rather than creating a duplicate.

Check warning on line 98 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L98

Did you really mean 'Okta'?

```hcl
data "conductorone_app" "okta_app" {
Expand Down Expand Up @@ -184,7 +184,7 @@
}
```

### Example: configure multistep provisioning

Check warning on line 187 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L187

Did you really mean 'multistep'?

Use this pattern when granting access requires more than one provisioning action — for example, delegating to another app's entitlement before running the connector.

Expand Down Expand Up @@ -216,10 +216,86 @@

- **Terraform import is not required.** Reference the entitlement by ID using a data source lookup.
- **JSON encoding is required** for `multi_step` provisioning and `account_provision` config blocks.
- **Entitlement owners** are managed with a separate resource, [conductorone_app_entitlement_owner](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/resources/app_entitlement_owner). Owners are always a list of C1 users — the resource accepts `user_ids` only, and setting the list replaces any existing owners for that entitlement. See the example below.
- **App-level owners** are managed with [conductorone_app_owner](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/resources/app_owner), which takes an `app_id` and a `user_ids` list. Setting `user_ids` replaces any existing owners for the app, and changing either field forces the resource to be replaced.
- **Entitlement and app owners** are managed with separate resources, not fields on `conductorone_app_entitlement`. See [Managing app, entitlement, and connector ownership](#managing-app-entitlement-and-connector-ownership) — use the v2 owner resources there for new configurations.

### Example: manage entitlement owners
<Tip>
`duration_grant` and `duration_unset` are mutually exclusive. Set `duration_grant` to a duration string in seconds (for example, `"3600s"` for one hour) to cap how long a grant lasts. Set `duration_unset = {}` for no maximum duration. Never set both.
</Tip>

---

## Managing app, entitlement, and connector ownership

C1 has two generations of Terraform resources for assigning owners. The **v1** resources (`conductorone_app_owner`, `conductorone_app_entitlement_owner`, `conductorone_app_resource_owner`) each accept a `user_ids` list and only support C1 users as owners. The **v2** resources assign exactly one owner — either a C1 user or another entitlement — per resource instance, and add support for entitlement-type owners that v1 does not have.

<Tip>
**Use the v2 resources for new configurations.** They support entitlement-type owners, which v1 does not, and are the actively developed API. The v1 resources remain supported for existing configurations — see [Legacy: v1 owner resources](#legacy-v1-owner-resources) below — but shouldn't be your starting point for anything new.
</Tip>

### v1 vs v2 at a glance

| Resource | Assigns owner(s) to | Owner type | API version |
|---|---|---|---|
| `conductorone_app_owner` | An app | List of C1 users (`user_ids`) | v1 |
| `conductorone_app_entitlement_owner` | An entitlement | List of C1 users (`user_ids`) | v1 |
| [conductorone_app_resource_owner](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/resources/app_resource_owner) | An app resource | List of C1 users (`user_ids`) | v1 |
| `conductorone_app_owner_user` | An app | A single C1 user (`user_ref_id`) | v2 |
| `conductorone_app_owner_entitlement` | An app | A single entitlement | v2 |
| `conductorone_app_entitlement_owner_user` | An entitlement | A single C1 user (`user_ref_id`) | v2 |
| `conductorone_app_entitlement_owner_entitlement` | An entitlement | A single entitlement | v2 |
| `conductorone_connector_owner_user` | A connector | A single C1 user (`user_ref_id`) | v2 |
| `conductorone_connector_owner_entitlement` | A connector | A single entitlement | v2 |

<Warning>
`conductorone_app_resource_owner` does not yet have a usable v2 equivalent. Continue using the v1 `conductorone_app_resource_owner` resource for app resource ownership.
</Warning>

### Example: assign an entitlement owner (user)

Use `conductorone_app_entitlement_owner_user` to assign a single C1 user as the owner of an entitlement.

```hcl
resource "conductorone_app_entitlement_owner_user" "access_profile_owner" {
app_id = "<APP ID>" # The app that contains the entitlement being owned
entitlement_id = "<ENTITLEMENT ID>" # The entitlement being assigned an owner
role_slug = "primary" # The owner role slug — see "Keep in mind" below
user_ref_id = "<USER ID>" # The C1 user who will own this entitlement
}
```

### Example: assign an entitlement owner (entitlement)

Use `conductorone_app_entitlement_owner_entitlement` when ownership should be held by another entitlement rather than a specific user — for example, so ownership follows whoever holds a role or group membership.

```hcl
resource "conductorone_app_entitlement_owner_entitlement" "access_profile_owner" {
app_id = "<APP ID>" # The app that contains the entitlement being owned
entitlement_id = "<ENTITLEMENT ID>" # The entitlement being assigned an owner
role_slug = "primary" # The owner role slug — see "Keep in mind" below
app_entitlement_ref_app_id = "<OWNING ENTITLEMENT APP ID>" # App ID of the entitlement that will act as owner
app_entitlement_ref_id = "<OWNING ENTITLEMENT ID>" # ID of the entitlement that will act as owner
}
```

The two resources differ only in how the owner is identified:

- **`_user`** — the owner is a single C1 user, identified by `user_ref_id`.
- **`_entitlement`** — the owner is another entitlement, identified by both `app_entitlement_ref_app_id` and `app_entitlement_ref_id`. Both fields are required because an entitlement's ID alone isn't unique across apps.

The app-level (`conductorone_app_owner_user` / `conductorone_app_owner_entitlement`) and connector-level (`conductorone_connector_owner_user` / `conductorone_connector_owner_entitlement`) resources follow the same `app_id`/`role_slug` plus `user_ref_id`-or-`app_entitlement_ref_*` pattern. The connector resources additionally require a `connector_id` identifying the owned connector.

### Keep in mind

- **`role_slug` is required on every v2 owner resource.** Existing configurations set it to `"primary"`. Confirm this is the value your tenant expects — the field itself is defined as a plain required string in the provider schema, with no enforced default or allowed-values list.
- **v2 owner resources have no in-place update.** Every field, including `role_slug`, forces a destroy-and-recreate if changed. Plan for resource replacement, not in-place updates, when rotating an owner.
- **v2 owner resources are singular, not lists.** One resource instance represents exactly one owner. To assign multiple owners of the same type, declare multiple resource instances (for example, with `for_each`) rather than a single resource with a list.
- **Only v2 supports entitlement-type owners.** The v1 resources (`conductorone_app_owner`, `conductorone_app_entitlement_owner`, `conductorone_app_resource_owner`) accept C1 users only.

### Legacy: v1 owner resources

The v1 resources remain supported for existing configurations. Prefer the v2 resources above for anything new — see the recommendation at the top of this section.

### Example: manage entitlement owners (v1)

Use this pattern to set the owners of an entitlement, replacing any owners currently set.

Expand All @@ -234,7 +310,7 @@
}
```

### Example: manage app owners
### Example: manage app owners (v1)

Use the same pattern with `conductorone_app_owner` to set the owners of an app, replacing any owners currently set.

Expand All @@ -251,15 +327,11 @@
When looking up users, the [conductorone_user](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/data-sources/user) data source returns a single user matching your search criteria, while [conductorone_users](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/data-sources/users) returns a paginated `list` of matching users. Use `conductorone_user` when you need one user's ID (as in the examples above) and `conductorone_users` when you need to iterate over multiple users.
</Note>

<Tip>
`duration_grant` and `duration_unset` are mutually exclusive. Set `duration_grant` to a duration string in seconds (for example, `"3600s"` for one hour) to cap how long a grant lasts. Set `duration_unset = {}` for no maximum duration. Never set both.
</Tip>

---

## Creating access profiles

Access profiles group multiple entitlements into a single requestable bundle. The C1 UI calls these "access profiles," but the Terraform provider and API use the term "catalog" — you'll see `catalog_id` as a field name throughout these resources.

Check warning on line 334 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L334

Did you really mean 'requestable'?

A fully configured access profile requires five related resources:

Expand Down Expand Up @@ -295,15 +367,15 @@
| `REQUEST_CATALOG_ENROLLMENT_BEHAVIOR_BYPASS_ENTITLEMENT_REQUEST_POLICY` | Bypasses individual entitlement approval policies. Users get all entitlements in a single request. |
| `REQUEST_CATALOG_ENROLLMENT_BEHAVIOR_ENFORCE_ENTITLEMENT_REQUEST_POLICY` | Enforces each entitlement's approval policy individually during enrollment. |

**`unenrollment_behavior`** — controls what happens to a user's entitlements when they unenroll:

Check warning on line 370 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L370

Did you really mean 'unenroll'?

| Value | Behavior |
|---|---|
| `REQUEST_CATALOG_UNENROLLMENT_BEHAVIOR_REVOKE_ALL` | Revokes all entitlements in the profile. |
| `REQUEST_CATALOG_UNENROLLMENT_BEHAVIOR_REVOKE_UNJUSTIFIED` | Revokes only entitlements the user doesn't hold through another path. |
| `REQUEST_CATALOG_UNENROLLMENT_BEHAVIOR_LEAVE_ACCESS_AS_IS` | Leaves all entitlements in place after unenrollment. |

Check warning on line 376 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L376

Did you really mean 'unenrollment'?

**`unenrollment_entitlement_behavior`** — controls whether approval policies are enforced when revoking entitlements at unenrollment:

Check warning on line 378 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L378

Did you really mean 'unenrollment'?

| Value | Behavior |
|---|---|
Expand All @@ -311,7 +383,7 @@
| `REQUEST_CATALOG_UNENROLLMENT_ENTITLEMENT_BEHAVIOR_ENFORCE` | Enforces each entitlement's revoke policy individually. |

- **`request_bundle`**: Set to `"true"` to allow users to request the entire profile as a bundle rather than individual entitlements.
- **`create_requests`**: Set to `"true"` to create provisioning tasks when users enroll or unenroll.

Check warning on line 386 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L386

Did you really mean 'unenroll'?

### Step 2: Set the grant and revoke policies

Expand Down Expand Up @@ -432,7 +504,7 @@
### Keep in mind

- Always use `depends_on` to ensure the policy (Step 2) is applied before bundle automation (Step 5).
- Use Option A for single-workspace configs. Use Option B when entitlements span multiple workspaces, or when you need per-entitlement `create_requests` settings.

Check warning on line 507 in developer/terraform-best-practices.mdx

View check run for this annotation

Mintlify / Mintlify Validation (conductorone) - vale-spellcheck

developer/terraform-best-practices.mdx#L507

Did you really mean 'configs'?
- Keep `published = "false"` while configuring. This prevents users from requesting an incomplete profile.

---
Expand Down