BeyondTrust/POV

← Back to dashboard

Wiring a POV's VMs into PRA, Password Safe and Entitle

Audience: operator · Profile: pov · Read this when: a POV's VMs are up and you want them reachable, vaulted and time-boxed.

Part of A POV Instance. Jump items, vaulted accounts and just-in-time grants, per POV and per tenant.

Wiring the VMs into PRA, Password Safe and Entitle

The point at which a POV becomes usable by a person. POV page → the Wired column → wire.

Up to three artifacts per VM, and they are independent:

A POV with no Password Safe or Entitle tenant gets the first and skips the rest with a line in the job log. That is not a degraded outcome — a POV wired into PRA is already useful, and failing the jump items over a half-configured tenant elsewhere would be the wrong trade.

Which Gateway they route through

Every jump item routes through this POV's own Gateway — the one installed inside the environment — and never through the tenant's appliance-wide default. That is not a preference. The default one lives on the customer's side of the world and has no route into the POV's private network, so an item pointed at it is created successfully, looks correct in the appliance, and times out at session launch. That is the most expensive failure in this feature to diagnose, because nothing reports it until somebody clicks.

So a POV with no Gateway is refused, up front, before any VM is touched.

The tenant, not the singleton

The jump items are created against the PRA tenant this POV is wired into — resolved with the POV's explicit id, so a POV whose tenant was deleted or disabled is an error rather than a quiet fall back to the default. A partial credential override is refused rather than merged with the configured appliance: authenticating to one customer's host with another's client id is precisely the silent cross-tenant mistake the registry exists to prevent.

What is skipped, and why

The VM What happens
No private address Skipped. The platform reports one once it is running
No OS reported Skipped, not guessed. An SSH jump to a Windows box fails at session launch, in front of whoever clicked it
Already has a jump item Skipped. PRA will happily create a second item with the same name pointing at the same host, and the second one is invisible here

Each VM's outcome is written to its row the moment it exists, so a run that crashes halfway leaves nothing in the appliance this dashboard cannot find again. One VM's failure does not stop the others — a POV where seven of eight VMs are reachable is worth more than one that rolled back to zero because the eighth had no address yet. A run where nothing worked does fail the job, though: a green job with zero artifacts is the one nobody goes back and reads.

Re-running is the remedy for a half-finished run, and it is safe by the "already wired" rule above.

What the managed system is called — and why not the IP

Password Safe names a workgroup-created managed system after its HostName, and the BeyondTrust Terraform provider attaches a managed account to its system by name — there is no system-id argument. So the HostName is not cosmetic: it is the key two different POVs can collide on.

POV environments are cloned from one lab template, so two live POVs very plausibly hold the same 10.x guest addresses. If the private IP were the HostName, both would ask for a system called 10.0.0.5, and the second POV's account would be created against whichever same-named system Password Safe resolved first. That exact failure was measured live on the PRA Vault mirror path and fixed there the same way.

So the managed system is named after the POV and the guest — poc-01-BtPocDC01, the same label the VM's jump item carries — and the guest's private address goes into DnsName and IPAddress, both of them. An operator in BeyondInsight sees the guest's name rather than a number, and the address Password Safe connects on is still there.

This applies to systems created from now on. A guest already onboarded keeps the name it was given: the wire-up never re-onboards a VM that has a managed-system id, and teardown destroys from the stored Terraform state, so an older row still off-boards correctly. To re-shape one, unwire that POV and wire it again.

Password Safe reaches the VM through the Resource Broker — by zone, not by field

Password Safe does reach these guests through the Resource Broker slice 5b installs. What arranges that is the broker's resource zone and the workgroup mapped to it: you create the zone, add the workgroup to it, and install the broker into that zone. All of it is BeyondInsight configuration, and this dashboard performs no part of it.

application_host_id is not that mechanism, though this document used to say it was. It is a managed-system attribute naming another managed system that carries IsApplicationHost. Three things say so:

So PovEnvironment.ps_application_host_id is an optional override with one writer — POST /api/pov/managed/{id}/application-host — and the wire-up sends 0 when it is unset, which is what every other caller in this codebase does. It used to be a hard precondition, which meant the Password Safe half refused on every POV ever created: no code path has ever written that column automatically.

If a rotation later cannot reach a guest, check the broker's zone-to-workgroup mapping first. The wire-up's job log says which of the two it used.

Two things come from the tenant rather than from Settings, because they are names inside that tenant and mean nothing in another one:

Workgroup Where the managed system lands
Functional account (Linux / Windows) Split by guest OS because Password Safe derives the managed system's platform from the functional account. One account cannot serve both

A guest whose OS has no functional account configured is skipped with that reason — a POV of only Linux VMs has no use for a Windows one, and demanding it would block a wire-up that could have completed.

Leave the functional accounts blank and each POV creates its own

The two functional-account fields are names of accounts, not labels: a functional account is the identity Password Safe authenticates to the guest AS in order to rotate its managed account. A POV against a customer's Password Safe tenant usually has both already, because they are the first thing anyone builds during onboarding. A BeyondTrust-owned POV tenant on day one has neither, which is why blank means create one rather than "skip Password Safe".

The field What happens
A name That account is resolved and used as-is. Nothing is created, and teardown never touches it — it is the customer's
Blank This POV mints its own, from the login its guests of that OS already use, and teardown deletes exactly that one

Nothing is stored. The credential is read live from the lab platform per run — the same stored_credentials box the Resource Broker install reads (There is no login field, on purpose) — used for the create call, and never written to this database. A credential that is fetched cannot go stale, and a POV whose template password changed picks up the new one on the next wire-up with nothing here to update.

The account is per-POV, and the guests must agree. One functional account serves every managed system that names it, so before minting one the dashboard reads every guest of that OS and requires them to report the same login. Guests that disagree are a refusal naming which ones, because minting from one of them would rotate that guest and fail the others — days later, on a schedule, which is the same delayed failure the Resource Broker's application host exists to prevent.

The account is created on Password Safe's built-in Linux and Windows platforms. If those platforms were renamed in the tenant, set pov_ps_functional_account_platform_linux / ..._windows to their names there — or just create the account by hand and type its name on the tenant, which skips minting entirely.

The API account on the tenant needs permission to create functional accounts. Without it the mint fails with that reason on the VM's own row, and the rest of the wire-up is unaffected.

When the lab platform has a login for the VM, it is seeded as the managed account's initial credential, so the first rotation replaces a password somebody knows rather than one nobody does.

The Entitle agent

Entitle's SSH connector reaches a private target through an Entitle agent running inside that network — a Kubernetes deployment, named on the integration as its agent token. A POV's VMs are on a private network by construction, so every POV integration is a private one, and for a long time the agent was the one prerequisite this dashboard could not satisfy.

It installs one now. Tenants column → install agent, beside the POV's Entitle tenant. It runs through the POV's control node agent as a Config-Management job, so there is nothing new on the wire and no agent image to rebuild — but the control node's policy.yaml has to carry the grant, so a POV whose control node was installed before this shipped needs Rebuild control node pressed once to rewrite it. The refusal says so.

That grant is a closed list of addresses, not a job type, so pointing the POV at a different host needs Rebuild control node pressed again too. The install checks the host against the policy the last control node install actually wrote and refuses with that remedy — worth knowing because the agent's own refusal, if it gets that far, tells you to add a line to policy.yaml and restart the agent. On a POV do not: that file is written by the dashboard's bootstrap and the next control node rebuild replaces whatever you put there.

What it does, on a Linux guest named on the POV (default entitle):

  1. mints an agent token in this POV's own Entitle tenant, not the instance's;
  2. installs single-node k3s (--disable traefik --disable servicelb — the agent is outbound-only and a POV VM that suddenly answers on :80 is a change nobody asked for);
  3. helm upgrade --install entitle-agent from the BeyondTrust-published chart repo, sized for one small VM;
  4. records the token name on the POV, which is what makes the SSH wire-up stop refusing.

The token is per POV, not per tenant, and that matters. Two POVs for the same customer share one Entitle tenant, and the tenant's agent_token_name can name only one agent. Whichever POV was set up second would create integrations pointing at the first one's network — and nothing would error, because Entitle creates them happily and the connector simply cannot reach the host. So a POV's own agent always wins, and the tenant option stays what it was: the answer for an agent you deployed yourself.

Sizing, and why the chart defaults do not fit

Worth knowing before you pick a host. The chart asks for three replicas at 1 CPU and 1 GiB of requests each, so on any VM you would give a POV the pods sit Pending forever and nothing says why except kubectl describe. The install overrides that to one replica at 250m/512Mi, and it turns off the Datadog log-shipping sidecar the chart runs even with datadog.enabled=false — that one is a native sidecar, which additionally needs Kubernetes 1.29+. Give the host 2 vCPU and 4 GB and it is comfortable.

Two things it needs from the network, both of which fail in a way the job output names: egress to get.k3s.io and get.helm.sh for the install, and to ghcr.io for the agent image. An ImagePullBackOff is the second one.

The guest needs a Python, and the install now puts one there. Ansible runs its modules on the target, so a template with no python3 used to fail at Gathering Facts — before task one — with The module interpreter '/usr/bin/python3' was not found, which reads as a broken playbook rather than as a missing package. The play's first real task is a raw one (the only kind that needs no interpreter): it looks for a python the runner's ansible-core can actually drive, installs one from the guest's own package manager if there is none, and pins the path it found. So there is a third egress requirement — the guest's distro repo — on a template that ships without python.

It checks a version, not a package name, and that is not pedantry: AlmaLinux and RHEL 8 call 3.6 python3 and this ansible-core will not drive it, so dnf install python3 can succeed and leave the run failing exactly as before. The task installs python3.12, python3.11 or python39 there instead. If the guest has no package manager, or its repos are unreachable, the task fails by name and the remedy is to bake python3 into the template.

Not the control node VM. The named host defaults to a VM called entitle and the guessed fallback deliberately excludes the control node: k3s brings its own containerd and its own iptables rules, and the control node is the one guest whose job is to keep alive the channel this install runs over. Nothing stops you naming it on purpose.

The exclusion is by enrolled VM id as well as by name, because the control node no longer has to be named anything in particular — it is auto-detected as the only Linux VM when nothing is typed, so on a template whose Docker host is called BtPocLin01 a name comparison would miss it entirely. It works the other way round too: a guest already named as the Entitle host is excluded from the control node's auto-detection, so the two never quietly select each other.

Teardown destroys the token, and it has to. Entitle refuses to mint a name it already holds and cannot read an existing value back, so a survivor would wedge the next POV that derives the same name. A destroy that fails is reported on the job log with the name to retire by hand rather than blocking the POV's deletion.

The SSH connector also needs a key. Entitle's connector authenticates with an SSH private key, not the password the lab platform holds — so this is the one credential in the whole POV wire-up that cannot be derived from something already there. Store the private half of the key baked into your template's Linux guests with ssh key on the POV row; it is encrypted like every other secret here and cleared at teardown.

Windows guests are skipped: the ephemeral-accounts app mints an account over SSH, which a Windows guest does not answer.

Three things come from the tenant, because they are ids and names inside that Entitle tenant: the owner id, the workflow id and the SSH sudo user. The fourth, the agent token name, comes from the POV's own agent when it has one and falls back to the tenant when it does not.

Teardown

Destroying a POV removes the Entitle integrations first, then off-boards the managed systems, then removes the jump items, then destroys the Entitle agent's token — all before the Gateway, the control node agent and the environment itself.

The agent token goes after the integrations and not with them, because an integration names the agent it routes through: reversed, Entitle would be left holding integrations pointing at a token that no longer exists.

Entitle goes first because an integration is standing access: of the three, it is the artifact whose lingering matters most. None of them stops the others — an integration or managed system left in a tenant is untidy, while stopping the chain leaves something worse behind, and a row whose removal failed keeps its state so a re-run can finish it. They are the only artifacts in a customer's appliance, and every later teardown step removes something they were resolved through — the tenant, the Gateway, the environment.

A row whose destroy failed keeps its terraform state, so a re-run can finish it; clearing it optimistically is how an item becomes unreachable. If the tenant cannot be resolved at all, the job log says how many were left and where.


Failure modes

A jump item was created but the session times out. Almost always the Gateway it routes through: check the item names this POV's own one and not the tenant's appliance-wide default. The dashboard always uses the POV's, so a hand-edited item is the usual cause.

Wiring is refused with "this POV has no Gateway". Working as intended — see which Gateway they route through. Install one first.

Wiring skips the Entitle half with "names no agent token". Working as intended: a POV's VMs are private and Entitle reaches a private target through an agent inside the network, which this dashboard does not install. Deploy it and name its token on the tenant.

Wiring skips the Entitle half with "no Entitle SSH key". Its connector authenticates with a key rather than a password. Use ssh key on the POV row.

Wiring skips the Password Safe half with "no Resource Broker". Working as intended: without one the platform has no route to a private address. Install one from the Resource Broker column, then re-wire.

A VM shows as skipped with "the tenant names no functional account". Password Safe derives the managed system's platform from the functional account, so one is needed per guest OS. Either name one on the Password Safe tenant, or leave the field blank and let the POV create its own — see Leave the functional accounts blank.

A VM shows as skipped with "this POV's guests report N different logins". A functional account is ONE credential used against every managed system that names it, so the dashboard will not mint one from guests that disagree — it would rotate the first guest and fail the rest on a schedule, days later. The message names which guests hold which group. Give them the same login, or create one functional account by hand in Password Safe and name it on the tenant.

A VM shows as skipped with "none of this POV's guests offered a usable stored credential". The lab platform's credential box is empty for those guests, or holds something the parser will not guess at — the same refusal pov_credentials raises for the Resource Broker install, and for the same reason: a wrong username comes back from the wire as an authentication failure, which reads as a bad password.

A VM shows as skipped with "the Password Safe platform 'Linux' could not be resolved". The built-in platform was renamed in that tenant. Set pov_ps_functional_account_platform_linux (or ..._windows) to its name there, or name an existing functional account on the tenant.

A VM shows as skipped with "did not report an OS". The lab platform reported a blank os_family, and guessing would build the wrong kind of jump item. Power it on and refresh the POV so the platform re-reads it.

If it still reports nothing — and it often will, because the OS is inferred from whatever text the platform hands back rather than read from a field — set it yourself in the OS column on the POV's VMs tab. That is stored separately from the platform's own answer, so it survives every later refresh, and it unblocks this VM everywhere at once: the jump items, the Password Safe half, guest steps, functional accounts, and the control node's own auto-detection. Refreshing forever is not the remedy for a platform that will never say.

Every VM's Password Safe half failed with is not support. Use https. Fixed — the dashboard no longer produces it. The message came from the passwordsafe Terraform provider validating its own inputs before it connects. It requires two things of the URL: the scheme must be https, and the path must contain /BeyondTrust/api/public/v. The first refusal prints the scheme it parsed, so a tenant URL with no scheme left a blank where the offending value should be. A hostname is exactly what the tenant form asks for, and the dashboard's REST calls have always normalised one — which is why the workgroup resolved, the functional accounts were created, the PRA jump items were created, and only Terraform failed, once per VM. Terraform is now handed the same normalised base the REST calls use, so a hostname, an origin and a full .../BeyondTrust/api/public/v3 URL all work, on the tenant and in Settings alike. A URL written http:// is refused up front instead, naming itself, rather than being silently promoted to a TLS connection nobody asked for.