Docs
API7 GatewayOperatePlatform OperationsCreate a Custom Role

Create a Custom Role

Combine two permission policies into a custom role, assign it to a user, and validate the resulting access boundary.

Applies to:API7 Gateway 3.10.x

Roles:Platform engineer

Goal

A custom role combining a read-only policy on one gateway group and a full-access policy on another, assigned to a non-root user without disturbing any role the target user already held.

This guide builds one example role; for a role model that spans many teams and environments, see Design a Custom Role System.

Before you begin

  • You have a Dashboard token for a user that can manage roles and permission policies.
  • You have at least two gateway groups, such as production and test. This guide grants read-only access to one and full access to the other.
  • You have a non-root user to assign the resulting role to. See User Management if you need to invite one first.

For the complete policy JSON syntax and the full catalog of actions and resources, see Permission Policies and Boundaries and Permission Policy Actions and Resources.

Set the variables this guide uses:

export API7_ADMIN_URL="https://localhost:7443"
export ORIGINAL_ROLE_IDS_FILE="create-custom-role-original-role-ids.json"
read -rp "Production gateway group ID: " PRODUCTION_GATEWAY_GROUP_ID && export PRODUCTION_GATEWAY_GROUP_ID
read -rp "Test gateway group ID: " TEST_GATEWAY_GROUP_ID && export TEST_GATEWAY_GROUP_ID
read -rsp "Dashboard token: " API7_ADMIN_KEY && export API7_ADMIN_KEY && echo

Then check each prerequisite state:

  • The local backup file used before replacing the target user's roles does not already exist:

    test ! -e "${ORIGINAL_ROLE_IDS_FILE}" || { echo "Stop: ${ORIGINAL_ROLE_IDS_FILE} already exists"; exit 1; }

    Expected: no output. If the file exists, inspect whether it is an unfinished run's role backup before moving it or completing that run's cleanup.

  • The control plane is reachable and your token is valid. Check it with:

    curl -ksS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}\n' "${API7_ADMIN_URL}/api/gateway_groups" -H "X-API-KEY: ${API7_ADMIN_KEY}"

    Expected: 200.

  • Both gateway groups exist. Check each with:

    curl -ksS --connect-timeout 3 --max-time 10 -o /dev/null -w 'production: %{http_code}\n' "${API7_ADMIN_URL}/api/gateway_groups/${PRODUCTION_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
    curl -ksS --connect-timeout 3 --max-time 10 -o /dev/null -w 'test: %{http_code}\n' "${API7_ADMIN_URL}/api/gateway_groups/${TEST_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"

    Expected: production: 200 and test: 200.

  • The target user exists. Look up their ID by username or email:

    read -rp "Username or email to search for: " TARGET_USER_SEARCH
    curl -ksS --connect-timeout 3 --max-time 10 "${API7_ADMIN_URL}/api/users?search=${TARGET_USER_SEARCH}&page=1&page_size=100" \
      -H "X-API-KEY: ${API7_ADMIN_KEY}" | jq -r '.list[] | "\(.id)\t\(.username)\t\(.email)"'
    read -rp "Target user ID from the row above: " TARGET_USER_ID && export TARGET_USER_ID

    Expected: exactly one row for the intended user.

  • No leftover object from an earlier run of this guide exists. Roles and permission policies get a server-generated ID, so this checks by name and label instead of by a fixed ID:

    for policy_name in create-custom-role-production-readonly create-custom-role-test-full-access; do
      curl -ksS --connect-timeout 3 --max-time 10 "${API7_ADMIN_URL}/api/permission_policies?search=${policy_name}&page=1&page_size=100" \
        -H "X-API-KEY: ${API7_ADMIN_KEY}" \
        | jq -r --arg name "${policy_name}" \
          '.list[] | select(.name == $name and .labels.guide == "create-custom-role") | "permission policy\t\(.id)\t\(.name)"'
    done
    curl -ksS --connect-timeout 3 --max-time 10 "${API7_ADMIN_URL}/api/roles?search=create-custom-role-development-team-member&page=1&page_size=100" \
      -H "X-API-KEY: ${API7_ADMIN_KEY}" \
      | jq -r '.list[] | select(.name == "create-custom-role-development-team-member" and .labels.guide == "create-custom-role") | "role\t\(.id)\t\(.name)"'

    Expected: no output. Any row identifies an object left by an earlier run, including the ID needed to inspect or delete it. Clean up that object before continuing; do not delete an object until you have confirmed it belongs to the unfinished run.

Steps

Four steps: two permission policies, the role that combines them, and the target user who receives it.

Step 1: Create the production read-only permission policy

Grant read-only access to the production gateway group and its services.

<.*>Get<.*> is not a safe definition of read-only. It is a regular expression over action names, and gateway:GetAdminKey matches it. That action returns the gateway group's Admin API key, which is a write credential — a holder can configure routes, services and plugins on the data plane directly, bypassing every policy you wrote.

The policy below therefore denies it explicitly. If you adapt this pattern, either enumerate the actions you mean or keep the deny, and audit any other Get-named action that returns a credential rather than a configuration.

  1. In the API7 Dashboard, go to Organization -> Permission Policies.

  2. Click Create Permission Policy.

  3. Enter the name create-custom-role-production-readonly.

  4. In the policy editor, paste the policy below, replacing {production_gateway_group_id} with the ID you set as PRODUCTION_GATEWAY_GROUP_ID:

    {
      "statement": [
        {
          "effect": "allow",
          "resources": ["arn:api7:gateway:gatewaygroup/{production_gateway_group_id}"],
          "actions": ["<.*>Get<.*>"]
        },
        {
          "effect": "allow",
          "resources": ["arn:api7:gateway:gatewaygroup/{production_gateway_group_id}/service/<.*>"],
          "actions": ["<.*>Get<.*>"]
        },
        {
          "effect": "deny",
          "resources": ["arn:api7:gateway:gatewaygroup/{production_gateway_group_id}"],
          "actions": ["gateway:GetAdminKey"]
        }
      ]
    }

    The third statement is the guardrail from the warning above. A deny beats every allow, so it holds even though the second statement's pattern also matches that action.

  5. Save the policy.

What changed: a permission policy exists that allows read (<.*>Get<.*>) actions on the production gateway group and its services, and explicitly denies gateway:GetAdminKey, which that pattern would otherwise have granted.

Step 2: Create the test full-access permission policy

Grant full access to the test gateway group and its services.

  1. In Organization -> Permission Policies, click Create Permission Policy again.

  2. Enter the name create-custom-role-test-full-access.

  3. Paste the policy below, replacing {test_gateway_group_id} with the ID you set as TEST_GATEWAY_GROUP_ID:

    {
      "statement": [
        {
          "effect": "allow",
          "resources": ["arn:api7:gateway:gatewaygroup/{test_gateway_group_id}"],
          "actions": ["<.*>"]
        },
        {
          "effect": "allow",
          "resources": ["arn:api7:gateway:gatewaygroup/{test_gateway_group_id}/service/<.*>"],
          "actions": ["<.*>"]
        }
      ]
    }
  4. Save the policy.

What changed: a second permission policy exists that allows every action on the test gateway group and its services.

Step 3: Create the role and attach both policies

Combine the two policies into one role a team member can be assigned.

  1. Go to Organization -> Roles.
  2. Click Create Role.
  3. Enter the name create-custom-role-development-team-member.
  4. Add the description Read-only access to production and full access to test.
  5. Attach the create-custom-role-production-readonly and create-custom-role-test-full-access policies.
  6. Save the role.

What changed: a custom role exists that combines both policies; it is not yet assigned to anyone.

Step 4: Assign the role to the target user

PUT /api/users/{user_id}/assigned_roles replaces the target user's complete list of assigned roles, so read their current roles first and include them alongside the new one — sending only the new role's ID would unassign every role they already had.

  1. Go to Organization -> Users.
  2. Select the target user found in Before you begin.
  3. Attach the create-custom-role-development-team-member role, leaving any roles the target user already has in place.
  4. Save the change.

What changed: the target user now holds create-custom-role-development-team-member in addition to every role saved in ORIGINAL_ROLE_IDS_FILE. Keep that file until cleanup succeeds.

Validate

Read both objects back independently of the write responses above, and confirm the role holds exactly the two policies and the target user holds the new role.

  1. In Organization -> Roles, open create-custom-role-development-team-member and confirm it contains only create-custom-role-production-readonly and create-custom-role-test-full-access.
  2. In Organization -> Users, open the target user and confirm the new role appears alongside every role that was present before this guide.

This confirms the grant is recorded correctly. To confirm it is enforced, sign in as the assigned user (or use a token that user creates in the Dashboard) and repeat the same read and write against both gateway groups. The production read and both test operations must succeed. The production write must reach the control plane and return an explicit authorization-denial response.

The checked-in Admin API reference does not document an exact status code for a permission-policy denial, so record the status and response body rather than asserting a particular code. A timeout, TLS or DNS failure, invalid token (401), or server error (5xx) is inconclusive and must not be counted as proof that the role blocked the write.

Roll back / Clean up

Restore the target user's original roles first, then delete the role, then delete both policies once nothing references them — deleting an object that is still referenced is rejected.

  1. In Organization -> Users, open the target user and remove only create-custom-role-development-team-member. Confirm every role that predated this guide remains assigned.
  2. In Organization -> Roles, delete create-custom-role-development-team-member.
  3. In Organization -> Permission Policies, confirm neither policy is referenced by another role, then delete create-custom-role-production-readonly and create-custom-role-test-full-access.

Troubleshoot

SymptomCauseFix
After Step 4, the target user's other roles are gone.PUT /api/users/{user_id}/assigned_roles replaces the complete role list; a request that names only the new role unassigns every role that was not included.If ORIGINAL_ROLE_IDS_FILE from Step 4 exists, restore that saved array with the first Roll back / Clean up command. A new GET cannot recover roles after they have been overwritten. If the backup is missing, recover the former assignments from an administrator or audit record before writing another role list.
DELETE on the role or a policy returns 400.The role is still assigned to a user, or the policy is still referenced by a role.Detach or unassign first: restore the target user's roles (Roll back / Clean up's first command) before deleting the role, and confirm GET .../permission_policies/{id}/references reports 0 before deleting a policy.
The Before-you-begin collision check prints one or more rows.An earlier run of this guide created an object with the same name and guide: create-custom-role label that was never cleaned up.Inspect the ID printed in each row and confirm ownership. Reuse the object or clean it up in dependency order; do not create a duplicate.