This guide covers best practices for Terraform practitioners managing C1 resources. Each section is self-contained, so you can read them in any order.Documentation Index
Fetch the complete documentation index at: https://conductorone-groman-network-requirements-updates.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Before you begin, make sure the C1 Terraform provider is installed and configured. See C1 Terraform provider.
Creating groups in C1
Creating a group in C1 requires three resources working together:- An app resource — the group object itself
- A custom app entitlement — defines the “member” role that users can request
- An entitlement automation — evaluates a CEL expression against user attributes and automatically grants or revokes group membership, with no manual access request required
Example: create a C1 group with dynamic membership
The following example creates a group in the ConductorOne application and automatically adds any user whosedepartment attribute contains “engineering.”
Keep in mind
- Don’t create a new resource type. The resource type for groups already exists in C1. Creating a duplicate will cause a conflict.
- The ConductorOne app ID is stable within your tenant. You can look it up once with a data source, or store it as a local variable if you reference it in multiple places.
- Consider wrapping this pattern in a reusable Terraform module. You’ll repeat these same three resources for every group you create.
Custom app entitlements vs app entitlements
There are two Terraform resources for managing entitlements. Choosing the right one depends on whether the entitlement already exists in C1.- Use
conductorone_custom_app_entitlementto create a new entitlement that does not yet exist in C1 — for virtual entitlements, or when pre-creating an IDP group before a connector sync. - Use
conductorone_app_entitlementto configure or update an entitlement that a connector already manages. This resource is update-only; it cannot create or delete entitlements.
Creating virtual entitlements
Use conductorone_custom_app_entitlement when you need to create a virtual entitlement for a permission not yet discovered by a connector. This resource also supports thematch_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.
Keep in mind
display_nameis required, but if the entitlement is connector-managed, the connector will overwrite it on sync. Addlifecycle { ignore_changes = [display_name] }when usingmatch_baton_idto prevent Terraform from flagging this as drift.slugdescribes the role level within the resource (for example,"member"or"admin"). Use descriptive, lowercase slugs.aliasis user-facing. Keep it lowercase with underscores.
Updating connector-managed entitlements
Use conductorone_app_entitlement to configure or update an entitlement whose lifecycle is managed by a connector. This is useful for bulk-updating policies or provisioning steps across many entitlements at once. This resource is also used to manage the app access entitlement, a static entitlement that exists on every app.Example: configure app access with account provisioning
Use this pattern when you want C1 to provision a new account in the target system when access is granted, rather than assigning an existing account.Example: configure multistep provisioning
Use this pattern when granting access requires more than one provisioning action — for example, delegating to another app’s entitlement before running the connector.Keep in mind
- Terraform import is not required. Reference the entitlement by ID using a data source lookup.
- JSON encoding is required for
multi_stepprovisioning andaccount_provisionconfig blocks. - Entitlement owners can be managed in parallel using conductorone_app_entitlement_owner.
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 seecatalog_id as a field name throughout these resources.
A fully configured access profile requires five related resources:
- conductorone_access_profile — the main profile object
- conductorone_app_entitlement — sets the grant and revoke policies on the access profile
- conductorone_access_profile_requestable_entries or conductorone_access_profile_requestable_entry — adds entitlements to the profile (choose one; see Step 3)
- conductorone_access_profile_visibility_bindings — controls who can see and request the profile
- conductorone_bundle_automation — auto-enrolls users based on entitlement membership
Step 1: Create the access profile
Start withpublished = "false" while you configure the remaining resources. Flip to "true" when the profile is ready.
enrollment_behavior — controls how entitlement policies are handled when a user enrolls:
| Value | Behavior |
|---|---|
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:
| 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. |
unenrollment_entitlement_behavior — controls whether approval policies are enforced when revoking entitlements at unenrollment:
| Value | Behavior |
|---|---|
REQUEST_CATALOG_UNENROLLMENT_ENTITLEMENT_BEHAVIOR_BYPASS | Bypasses approval policies when revoking entitlements. |
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.
Step 2: Set the grant and revoke policies
Set the policy before configuring bundle automation. If the policy is not set first, tasks created during automation may have unexpected policies applied. Use theconductorone_policy data source to look up policies by display name:
Step 3: Add entitlements to the profile
Use one of the following two resources — not both. Option A: Add multiple entitlements as a set (conductorone_access_profile_requestable_entries)
Use this option when all entitlements are in the same Terraform state and you want to manage them as a single unit. All changes to the set are applied together. Requires a toset() wrapper.
conductorone_access_profile_requestable_entry)
Use this option when you need per-entitlement control over create_requests, or when entitlements span multiple Terraform workspaces. Each entitlement gets its own state entry.
Step 4: Configure visibility
Visibility bindings control which users or groups can see and request the profile. Without this, the profile may not be visible even after publishing.Step 5: Add bundle automation
Bundle automation automatically enrolls users who hold a specific entitlement. Usedepends_on to ensure the policy from Step 2 is applied first.
Keep in mind
- Always use
depends_onto 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_requestssettings. - Keep
published = "false"while configuring. This prevents users from requesting an incomplete profile.
Policy best practices
Policies define the approval workflow for access requests — who reviews, in what order, and under what conditions.Always include a baseline rule
Thegrant key is the catch-all baseline rule. It runs for every request that does not match any named rule’s condition. In the examples below, it rejects those unmatched requests by default, so only requests satisfying my_policy_key’s condition are auto-approved.
What a broken policy looks like
This policy will not work because it is missing thegrant baseline step:
What a working policy looks like
Use built-in policies as templates
Terraform does not validate policy structure the same way the C1 UI does. The safest starting point is to generate HCL from an existing, working policy.Edit the generated HCL
Use the generated file as your starting point. Before modifying:
- Remove any auto-generated
idfields from resources you’re creating fresh — Terraform sets these on creation and they shouldn’t be hardcoded. - Watch for read-only attributes. Terraform includes them in generated output, but setting them in config will cause errors.
- Keep the
grantbaseline step intact.
Keep in mind
- Always include a
grantbaseline step — this is required. - Use meaningful policy keys that describe the approval workflow (for example,
manager_approvalrather thanstep_1). - Limit approval chains to three or fewer sequential steps.
- Use group reviewers instead of individual user IDs, so the policy keeps working when people change roles or leave.
- Include clear
accept_messageandreject_messagevalues to guide requestors and approvers. - Use CEL expressions in rule conditions for attribute-based logic. See CEL expressions.