> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/harness-platform/use-harness-platform/governance/policy-as-code/policy-as-code-for-users-and-user-groups.md).

# Policy as Code for users and user groups

Use OPA policies to enforce identity governance rules on user invitations, scope assignments, and user group membership changes.

{% hint style="info" %}
This feature is in Beta[^1] and requires enablement. [View all early access features.](/release-notes/features.md)

{% @harness-feature-flag-access/request-feature-flag-access module="platform" pagePath="platform/use-harness-platform/governance/policy-as-code/policy-as-code-for-users-and-user-groups" featureFlags="%5B%7B%22name%22%3A%22OPA%20governance%20for%20users%22%2C%22key%22%3A%22PL\_ENABLE\_OPA\_FOR\_USERS%22%2C%22status%22%3A%22BETA%22%2C%22description%22%3A%22Enables%20OPA%20policy%20evaluation%20on%20user%20creation%20and%20scope-assignment%20events.%22%7D%2C%7B%22name%22%3A%22OPA%20governance%20for%20user%20groups%22%2C%22key%22%3A%22PL\_ENABLE\_OPA\_FOR\_USER\_GROUPS%22%2C%22status%22%3A%22BETA%22%2C%22description%22%3A%22Enables%20OPA%20policy%20evaluation%20on%20user%20group%20create%2C%20update%2C%20and%20membership-change%20events.%22%7D%5D" %}
{% endhint %}

Harness uses [Open Policy Agent (OPA)](https://www.openpolicyagent.org/) as its policy engine. You can write governance rules in [Rego](https://www.openpolicyagent.org/docs/policy-language#learning-rego) that are evaluated whenever a user is invited or assigned to a scope, or whenever a user group is created, updated, or its membership changes.

Use these policies to enforce a wide range of identity governance rules.

{% hint style="warning" %}
**Harness-managed user groups are excluded from policy evaluation**\
\
Groups such as *All Account Users*, *All Org Users*, and *All Project Users* are system-managed: Harness automatically adds users to these groups when they are granted access at the corresponding scope.\
\
These additions bypass OPA evaluation entirely; you cannot use policies to control who enters or leaves a system-managed group.\
\
If your governance requirements cover group membership, use custom user groups instead. Policies apply only to user groups you create.
{% endhint %}

***

## What you will learn in this topic

By the end of this topic, you will be able to:

* Create OPA policies for user invitation and scope-assignment events.
* Create OPA policies for user group create, update, and membership-change events.
* Understand the input payloads available for each event type.
* Describe policies for common identity governance use cases.

***

## Before you begin

Before you configure OPA for user and user groups, ensure you have the following:

* [Harness Policy as Code overview](/harness-platform/use-harness-platform/governance/policy-as-code/harness-governance-overview.md)
* [Policy as Code quickstart](/harness-platform/use-harness-platform/governance/policy-as-code/harness-governance-quickstart.md)
* [OPA's Rego policy language](https://academy.styra.com/courses/opa-rego)

***

## Policy evaluation behavior

When a user is added to multiple groups in a single operation, Harness evaluates the policy for each group independently. If any group's policy denies the operation, the entire operation fails, and no group memberships are updated. If a policy issues a warning, Harness allows the operation to proceed and displays the warning, provided no policy denies the operation.

## Configure a policy and policy set

The steps below describe how to create a policy and a polciy set for users and user groups, along with Rego samples.

### Step 1: Create a policy

1. In Harness, go to **Account Settings** → **Policies** → **New Policy**.
2. Enter a **Name** and click **Apply**.
3. Write your Rego policy. Described below are policies for user and user groups.

* **User policy example**
  * Restrict invitations to allowed email domains:

    ```
    package user_allowed_domains
    allowed_domains := {"yourcompany.com", "partner.com"}

    deny[msg] {
        domain := split(input.user.email, "@")[1]
        not allowed_domains[domain]
        msg := sprintf("User '%s' is not from an allowed domain.", [input.user.email])
    }
    ```
* **User group policy example**
  * Enforce a naming convention on identifiers:

    ```
    package usergroup_naming_policy

    deny[msg] {
        id := input.userGroup.identifier
        not regex.match("^[a-z0-9_]+_teams$", id)
        msg := sprintf("UG_NAMING: identifier '%s' must match the pattern '<name>_teams'", [id])
    }
    ```
  * Require at least one member before a group can be saved:

    ```
    package usergroup_min_members

    deny[msg] {
        count(input.userGroup.users) == 0
        msg := sprintf("UG_MIN: cannot save '%s' with zero members", [input.userGroup.name])
    }
    ```

For more information on the available fields for each entity type, see [Input payloads](#input-payloads).

{% hint style="warning" %}
**Scope-membership data is only available for users already in the account**\
\
Scope-membership data refers to the data that records which scopes (account, org, project) a user already belongs to in Harness. When a user is being added to a group, their membership data will be empty.

Use User **On Save** policies to enforce membership-based restrictions at the point of initial invitation.
{% endhint %}

4. Click **Save**.

### Step 2: Create a policy set

1. Go to **Policy Sets** → **New Policy Set**.
2. Enter a **Name** and optional **Description**.
3. In **Entity Type that this policy set applies to**, select **User** or **User Group**.
4. In **On what event should the policy set be evaluated**, select **On Save**.
5. Click **Continue**.
6. Click **+ Add Policy** and select the policy you created.
7. In **What should happen if a policy fails?**, select:
   * **Warn & continue** - the entity is saved and a warning is surfaced.
   * **Error and exit** - the save is blocked and an error is returned.
8. Click **Finish**.

***

## Input payloads

### User

The following fields are available (inexhaustive) when a User **On Save** policy is evaluated:

| Path                                    | Contents                                                                                                          |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `input.user.email`                      | Email address being added or invited                                                                              |
| `input.user.orgIdentifier`              | Target org scope of the operation                                                                                 |
| `input.user.existingMemberships`        | Existing org/project memberships — populated only for users already in the account; empty for first-time invitees |
| `input.user.roleAssignments`            | Role bindings included in the request                                                                             |
| `input.user.userGroups`                 | User groups included in the request                                                                               |
| `input.metadata.roleAssignmentMetadata` | The invitor's current role assignments — use this for privilege checks                                            |

**Use case:** Policy to prevent non-admins from granting the account admin role:

```rego
package user_privilege_policy

deny[msg] {
    ra := input.user.roleAssignments[_]
    ra.roleIdentifier == "_account_admin"
    not invitor_is_account_admin
    msg := "USER_PRIVILEGE: only account admins can grant the account admin role"
}

invitor_is_account_admin {
    input.metadata.roleAssignmentMetadata[_].roleIdentifier == "_account_admin"
}
```

**Use case:** To block org-scoped operations that would add a user who already belongs to a different org (the `existingMemberships` field is empty for first-time invitees, so this rule applies only to existing account members):

```rego
package user_cross_org_policy

deny[msg] {
    target_org := input.user.orgIdentifier
    target_org != ""
    membership := input.user.existingMemberships[_]
    membership.scopeLevel == "ORGANIZATION"
    membership.orgIdentifier != target_org
    msg := sprintf(
        "USER_CROSS_ORG: already in org %s; cannot add to %s",
        [membership.orgIdentifier, target_org]
    )
}
```

### User group

The following fields are available (inexhaustive) when a User Group **On Save** policy is evaluated:

| Path                              | Contents                                                                                                                                                            |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input.userGroup.identifier`      | The group's unique identifier                                                                                                                                       |
| `input.userGroup.orgIdentifier`   | The org the group belongs to (empty string at account scope)                                                                                                        |
| `input.userGroup.users`           | Array of user IDs in the group                                                                                                                                      |
| `input.userGroup.userMemberships` | Map of `userId` → array of scope objects, each with `scopeLevel` (`"ORGANIZATION"`, `"PROJECT"`, `"ACCOUNT"`) and `scope.orgIdentifier` / `scope.projectIdentifier` |

{% hint style="info" %}
**Role-assignment operations are not yet evaluated by OPA policies**\
\
The user group payload contains scope-membership data (which orgs and projects users belong to) but not role assignments.\
\
Operations such as inheriting role assignments from a parent scope cannot be blocked with a policy.\
\
To write policies based on explicit role assignments at invite time, use the **User** entity type and `input.metadata.roleAssignmentMetadata`.
{% endhint %}

***

## Related articles

* [Harness Governance Overview](/harness-platform/use-harness-platform/governance/policy-as-code/harness-governance-overview.md)
* [Policy Samples](/harness-platform/use-harness-platform/governance/policy-as-code/sample-policy-use-case.md)

{% @harness-feedback/feedback module="harness-ai" pagePath="harness-ai/use-harness-platform/governance/policy-as-code/policy-as-code-for-users-and-user-groups" %}

[^1]: Beta features are feature-complete but still being validated. Available on request.
