BeyondTrust/POV

← Back to dashboard

Phase 2 — Entitle virtual application provisioning

Audience: contributor · Profile: both · Read this when: you are provisioning the Entitle virtual applications that back those groups.

Legacy mechanism. This runbook covers Entitle granting dashboard permissions through Entra groups. The current mechanism is a REST integration that works for any user and takes effect immediately — see ../integrations/beyondtrust/entitle-dashboard-permissions.md.

Operator walk-through for the terraform/entitle_user_jit/ module + web_dashboard/scripts/bootstrap_entitle_app.py wrapper. Validates Phase 2 of the Entitle user-JIT design.

After Phase 2 deploys, this confirms:

  1. terraform plan against a clean Entitle tenant prints the expected shape: 3 workflows, N resources (one per dashboard-* Entra group Phase 1 created), 3 policies, 1 bundle.
  2. terraform apply provisions them; a second terraform apply is a no-op.
  3. In the Entitle UI, the VM Dashboard virtual application appears with every dashboard-* resource listed and routed through its tier-appropriate workflow.
  4. The Phase 1 Entra group ids in oauth_group_mappings flow cleanly into each entitle_resource.directory_group_id.

Takes ~30 minutes including the bootstrap script + terraform apply. Run against a real Entitle tenant. Phase 3's E2E loop builds on top.

Prerequisites

Step 1 — Generate the groups tfvars file

docker compose exec app python -m web_dashboard.scripts.bootstrap_entitle_app `
  --output-tfvars /app/terraform/entitle_user_jit/groups.auto.tfvars.json

Expected: - Wrote N groups -> /app/terraform/entitle_user_jit/groups.auto.tfvars.json where N is the dashboard-* row count from the Phase 1 prereq query. - The file contains one entry per group with display_name, directory_group_id, description, and tier (one of auto_approve / single_approver / two_approver). - Re-running overwrites the file deterministically.

If N is 0, stop — Phase 1 hasn't been run, or the groups table is empty. Re-run Phase 1's bootstrap before continuing.

Step 2 — Inspect the tier assignments

cat terraform/entitle_user_jit/groups.auto.tfvars.json | jq '.groups | to_entries | map({key, tier:.value.tier}) | group_by(.tier) | map({tier:.[0].tier, count:length})'

Expected (default 102 prod-seeded groups: 1 admin + 1 baseline + 98 scope/levels + 2 workstreams): - auto_approve = 32 (baseline + 31 *-read) - single_approver = 46 (29 *-write + 15 *-use + 2 workgroup-*) - two_approver = 24 (admin + 23 *-delete)

Exact counts vary with PERMISSION_SCOPE_LEVELS and the number of workstreams — not every scope offers all four levels, so the totals are the sum of its value lengths rather than a multiple of the scope count. If a row landed in the wrong tier, edit _tier_for_group() in bootstrap_entitle_app.py and re-run Step 1.

*-use routes to single_approver. It was missing from the suffix lists until recently, so dashboard-secrets-use fell through to the "tier inference fell through" warning and took the same tier by default — a log line worth grepping for if you are on an older build.

Step 3 — terraform init

cd terraform/entitle_user_jit
$env:TF_VAR_entitle_api_key = '<your-entitle-api-key>'
terraform init

Expected: Terraform downloads the entitleio/entitle provider (3.2.2 or a later 3.x) and initialises the working directory.

Step 4 — terraform plan (dry run)

terraform plan `
  -var "entitle_integration_id=<entra-integration-id>" `
  -var "single_approver_group=<entitle-directory-group-name>" `
  -var "two_approver_group=<entitle-directory-group-name>"

The approver values are the names of Entitle directory groups, exactly as Entitle shows them. The plan stops unless exactly one group has each name.

Expected: plan shows: - 3 to add for entitle_workflow (auto_approve / single_approver / two_approver). - N to add for entitle_resource_synced.dashboard_group["…"], where N matches Step 1's count. These are not new resources: each one adopts the resource Entitle's Entra sync already made for that group, and sets its workflow. - 0 to change, 0 to destroy.

A group the sync has not picked up yet fails the plan with the provider's "resource not found". Wait for the next sync, or trigger one from the integration in the Entitle UI.

CI runs terraform validate on this module against the real provider schema (the terraform job), so a schema error here means the provider moved past 3.x.

Step 5 — terraform apply

terraform apply `
  -var "entitle_integration_id=<entra-integration-id>" `
  -var "single_approver_group=<approver-identifier>" `
  -var "two_approver_group=<two-approver-identifier>"

(or via the wrapper: docker compose exec app python -m web_dashboard.scripts.bootstrap_entitle_app --apply --entitle-integration-id … --single-approver-group … --two-approver-group … — the wrapper threads vars through TF_VAR_* env so the API key never lands in shell history.)

Expected: apply completes cleanly. Outputs include: - workflow_ids — three-entry map. - resource_ids — N-entry map (dashboard-* → entitle resource id). Paste it into the dashboard's Settings → Integrations → Entitle panel (resource ID map) for Phase 4's "Request access" deep links. - resource_count — N.

Step 6 — Spot-check in the Entitle UI

Sign into your Entitle tenant's web console. Confirm:

  1. The N dashboard-* group resources carry the tag VM Dashboard (from application_name) and a tier:<tier> tag, so filtering the catalog by VM Dashboard shows exactly them.
  2. Each one's description is the one the bootstrap script wrote.
  3. Workflows page lists the three new workflows with the names vm-dashboard-auto-approve, vm-dashboard-single-approver, vm-dashboard-two-approver.
  4. Opening one of the *-write resources confirms its Workflow field points at vm-dashboard-single-approver; a *-read resource points at vm-dashboard-auto-approve; an *-delete or admin resource points at vm-dashboard-two-approver.

Step 7 — Idempotency

terraform apply -var "entitle_integration_id=<…>" -var "single_approver_group=<…>" -var "two_approver_group=<…>"

Expected: No changes. Your infrastructure matches the configuration.

This is the load-bearing property — operators can re-run the bootstrap script + apply as part of every deployment without risk. Tier reassignment is a single edit to _tier_for_group() followed by a re-run; Terraform diffs each resource's workflow and only updates the affected entries.

Step 8 — Add a new dashboard-* group end-to-end

To prove the Phase 1 → Phase 2 chain works:

  1. Add a new permission scope to web_dashboard/api/auth.py:PERMISSION_SCOPE_LEVELS, declaring only the levels something actually enforces.
  2. Re-run Phase 1: python -m web_dashboard.scripts.bootstrap_entitle_groups --scope=permissions --yes.
  3. Re-run Phase 2 Step 1 + Step 5 (apply).

Expected: Step 5 reports + N to add, where N is the number of levels the new scope declared — not always three. The new resources now carry the VM Dashboard tag and their tier's workflow.

Before you add one, read this. has_permission treats every permission map as a strict per-scope allowlist. So adding a scope and gating a route on it revokes that route from every user who has an explicit permission map — with no error, no log line, and no row the administrator who set those permissions ever saw. A new scope needs a backfill in web_dashboard/database.py in the same change, granting exactly what a non-admin with an explicit map could already reach: the full level set for a previously ungated route, and nothing for one that required the Admin flag. See Permissions → Adding a scope.

Step 9 — Where this fits

Phase 2 ships the server-side configuration in Entitle. Phase 3 is the end-to-end request flow — operator A opens Entitle, picks a dashboard-aws-read resource, gets auto-approved (because that resource's workflow is vm-dashboard-auto-approve), and the next dashboard login sees the new permission. The Phase 3 runbook captures that loop end-to-end against the Entitle tenant configured here.

Phase 4 (UI affordances) consumes the resource IDs that the resource_ids output surfaces — the dashboard's 403 page deep-links to the matching resource so users can request access in one click.

Rollback

  1. terraform destroy from the terraform/entitle_user_jit directory deletes the three workflows and releases the group resources from Terraform state. The resources themselves stay in Entitle (the Entra sync owns them), still pointing at the deleted workflows, so set their workflow in the UI, or re-apply, before anyone requests them. The Entra groups and Phase 1 are untouched.
  2. For partial rollback (e.g. one bad tier assignment), edit _tier_for_group() and re-run the bootstrap script + apply. The provider's diffing handles the workflow swap.
  3. If terraform state gets out of sync with the live Entitle tenant (operator deleted a resource via the UI), terraform refresh followed by another apply reconciles. The wrapper's tfvars file stays canonical for the desired-state inputs.