BeyondTrust/POV

← Back to dashboard

Certificates

Audience: operator · Profile: demo · Read this when: you are deciding which certificate backend and which of the two platforms an identity belongs on, and what its address has to say.

Preview. The plugin's shared core is covered by its own 624-assertion suite, but none of its submission paths has been proven against a live authority — the nine backends and the two Entra publishers can only be exercised against a real CA, cloud account or tenant. That is what this feature exists to make cheap. Off by default; enable it under Settings → Preview features → Certificate Lab.

This page is the reference for the Password Safe Certificate custom platform plugin family: what it is, which of the nine backends can do what, where it has to run, and the address grammar that is its entire configuration surface. Nothing here builds anything.

It is the Certificates tab of the Workload Lab page (/workload-lab), alongside SPIRE — both labs govern identities that belong to machines, so they share a page. Each still has its own preview toggle and its own Settings panel.

Three pages, and this is the first:

The companion docs:


What the plugin does, in one paragraph

Password Safe does not become a certificate authority. An external CA signs and remains the authority. The plugin generates the keypair in its own process, sends only a PKCS#10 certificate signing request, and never transmits the private key — so the CA never sees it either. What Password Safe becomes is a registrar and broker: it requests certificates, holds them, governs retrieval, and adds no depth to the certificate chain.

The result is split across two objects, and both halves are needed and both are governed:

Half Where it lives
The PKCS#12 passphrase the managed account's credential
The bundle that passphrase opens a Secrets Safe file secret

Retrieving one without the other yields nothing usable. So the Secrets Safe folder's ACL and the managed account's access policy are both live controls — set them to the same population, or the weaker of the two is your real access boundary.


Two packages, two platforms

The plugin ships as two .psplugin packages over a shared core, with different plugin ids — so both install side by side and neither overwrites the other. They appear in BeyondInsight as two platforms, and that is the part that matters operationally.

Certificate Subordinate CA
Managed credential an end-entity certificate a subordinate certificate authority
Who consumes it the identity itself — a pipeline, an agent, a service another system, which mints its own short-lived certificates beneath it
Backends all nine only the four that can sign a subordinate
isca defaults to false true
bundle defaults to Pkcs12 PemBundle

Which one you want. If the thing that will present the certificate is the identity named in it, the Certificate platform. If the thing that holds it will mint further certificates — PRA Vault issuing session certificates is the case this was built for — the other one.

Why it is a split and not a flag. A subordinate CA is a delegation of issuing authority, not one more credential. Two platforms mean the team that may request a leaf is not automatically the team that may request an issuer, and that separation is expressible in Password Safe's own access model rather than by convention. The dashboard follows the split rather than papering over it, and the refusal you get differs by platform — see Two refusals, and which one you get.

They are the same code. The shared core is the overwhelming majority of it — key generation, CSR construction, bundling, storage, verification, the configuration grammar, every backend — so the two cannot drift apart in behaviour, and everything this page says about configuration applies to both.

One functional account per platform

A functional account is platform-bound, and a managed system inherits its functional account's platform. So an account on Certificate cannot carry a managed system on Subordinate CA: it onboards green and then fails every credential action. A CA serving both therefore holds two functional accounts, one on each platform, carrying the same CA credential.

The dashboard mints them at different times, on purpose:

Teardown deletes both, and only ones the dashboard minted. Missing the second would leave an orphan holding a live enrollment credential after the CA it belonged to is gone.

The nine backends

The Certificate platform's full list. The dashboard provisions the two cloud ones; the rest are certificate authorities a customer already runs, so the dashboard validates their addresses but has no build path for them.

Backend Names accepted Required Can sign a sub-CA
Microsoft ADCS adcs, ad, microsoft-ca ca=, template= refused on policy
EST (RFC 7030) est, rfc7030 url= no — the protocol has no mechanism
EJBCA ejbca, keyfactor-ejbca url=, ca=, and profile= or subcaprofile= yes
HashiCorp Vault PKI vault, vaultpki, hashicorp-vault url=, and role= for a leaf yes
Smallstep step-ca stepca, step, smallstep url=, fingerprint= no
DigiCert ONE digicert, digicert-one url=, profile= no
Sectigo sectigo, sectigo-cm url=, profile= no
AWS Private CA awspca, aws, acm-pca arn= yes
Google Cloud CAS gcpcas, gcp, cas, google-cas project=, location=, pool= yes
Self-signed selfsigned, self none no — a new trust root, not a subordinate

How each of the four signs one, and the grant that follows

Backend How Grant it separately?
awspca the issuance template is switched to SubordinateCACertificate_PathLen{0-3}, chosen from pathlen=. ACM PCA ignores the CSR's basic constraints and builds from the template, so this is the only thing that decides whether a CA comes back yes — restrict acm-pca:IssueCertificate to the subordinate template ARNs
gcpcas the CSR's own extensions are honoured, subject to the pool's issuance policy. The parent needs --max-chain-length=1 or higher no — see below
ejbca a CA-type certificate profile, named with subcaprofile= yes — a CA-type profile is a distinct grant
vaultpki a dedicated endpoint, <mount>/root/sign-intermediate — not a flag on the ordinary signing path, so the two operations cannot be confused yes — update on that path alone

Granting only that is the difference between a compromised functional account minting one bounded authority and minting anything.

GCP is the exception, and it changes where the boundary goes. Issuing a subordinate uses the same privateca.certificates.create as issuing a leaf — what differs is the CSR and the pool's issuance policy, not the permission. So the separation has to be made with a dedicated CA pool whose policy permits CA certificates, and the grant scoped to that pool. There the boundary is the resource rather than the verb.

That is why the path-length choice produces its own CA row rather than being a setting that widens an existing pool: a leaf-only pool and a subordinate-capable one being separate resources, with separate pool-scoped enrollment identities, is the control.

Two refusals, and which one you get

Which refusal an operator sees depends on which platform they are standing on, and the difference is deliberate.

On the Subordinate CA platform, a backend outside the four is declined by name, before any policy is consulted: it says the platform does not support that backend, lists the four it does, and points at the Certificate platform for an end-entity certificate. That names what to use instead, which is what someone who picked the wrong platform needs.

On the Certificate platform, isca= and the constraint options get the backend's own reasoning — because that is the only platform those backends are offered on at all, so "why not ADCS?" is answered there or nowhere:

On a backend that can sign one, isca=true here is legal and does nothing useful, so the answer is the other package — putting an issuer under the access control meant for a leaf is exactly what the split prevents.

Underneath the package's own list sits a second gate: the backend's own capability flag, which fails closed. A backend added later is refused for subordinate issuance until it declares the capability. That direction is deliberate — most certificate authorities cannot sign a subordinate at all, so the unrecognised case is far more likely to be one that should be refused.

selfsignedtest exists in the plugin for its own harness and is refused outright here: it generates and persists its own CA private key unencrypted beside the plugin.

Three of the nine cannot revoke at all — EST has no revocation operation in RFC 7030, step-ca's needs a credential the plugin no longer holds after issuance, and selfsigned publishes nothing to revoke against. On those, Disable Managed Account succeeds and says the certificate stays valid until it expires. That is a success rather than a failure: the backend never could have done it.


Where the plugin runs, and the one constraint that decides ADCS

On Password Safe Cloud a custom platform plugin runs on a Resource Broker. The plugin needs nothing inbound — every call it makes is outbound and broker-initiated — so the question is not inbound versus outbound. It is whether the traffic stays inside the broker's subnet or has to cross the firewall at its edge.

Backend What crosses the subnet firewall Port
gcpcas privateca.googleapis.com, oauth2.googleapis.com (the JWT-bearer exchange that authenticates it) 443
awspca acm-pca.<region>.amazonaws.com 443
est, ejbca, vaultpki, stepca, digicert, sectigo the service's own host, from url= — administrator-supplied, so there is no fixed hostname to pre-approve 443
selfsigned nothing. There is no certificate authority to reach —
Entra publishers login.microsoftonline.com, graph.microsoft.com 443
every backend the BeyondInsight host from biurl=, to write the bundle 443
adcs see below — this is the one that is not 443 135 + 49152–65535

Every backend but ADCS needs nothing beyond outbound 443, so a broker that can already reach BeyondInsight can usually reach AWS, GCP, Entra or an HTTPS CA with no new rule. The Certificate Lab's GCP and AWS paths are both in that group, and the self-signed backends need no egress at all. ADCS is the single exception, and it is the next section.

url= is the service's origin, not an API path — the plugin appends its own, so a path here yields a doubled one and a 404 at the first credential change. The dashboard refuses one, and refuses plain http on everything but Vault (where a development server may legitimately be plain): the request carries the functional account's enrollment credential in a header. insecure=true exists for one real moment — a private CA's own management endpoint presented on a certificate chaining to that very CA, which the broker does not trust until the chain is installed — and it is logged as the footgun it is. Installing the chain is the actual fix.

ADCS is the exception, and it is a subnet question. ICertRequest3 is DCOM: the broker opens TCP 135 to the CA's endpoint mapper, and the CA answers on a dynamic high port in 49152–65535. Between servers on a subnet, 135 is normally already open alongside 139, 445 and 3389, so none of that traffic reaches the edge firewall and enrollment simply works. Across that firewall it is a different request entirely — 135 plus a 16,000-port range through the perimeter, which no network team grants.

So the prerequisite is put the Resource Broker on the CA's subnet, not merely in the same zone. Microsoft's Certificate Enrollment Web Service exists to turn enrollment into an outbound-443 protocol and would remove the constraint; this plugin does not implement it. Where the CA is segmented away from every candidate broker, that gap is the blocker and no configuration works around it.

This is also the easiest constraint here to miss, because a single-subnet lab satisfies it by accident — the problem first appears at a customer whose CA is segmented.


The address is the entire configuration surface

appsettings.json ships inside the .psplugin, so its values are global to every managed system, cannot be changed without repackaging, and on Password Safe Cloud cannot be reached at all. Every value therefore rides the managed system's Network Address, which is not a hostname but the whole certificate profile:

gcpcas?project=bt-se-lab&location=us-central1&pool=demo-pool&lifetime=24h&key=ecdsa-p256&biurl=https://tenant&folder=PKI/Pipelines&owner=1

That is what makes one installed plugin able to serve an ADCS template, a cloud CA pool and a self-signed Entra credential at the same time, each configured by whoever owns that managed system.

The 255-character budget is real

Password Safe's address column is 255 characters, and real profiles get close: the ADCS example in the plugin's test case is 231, and its own earlier draft was 269 — over the limit without looking excessive, before anyone typed a real CA name or a Cloud tenant URL. Dropping secret= (the default title written out longhand) and shortening folder= bought those 38 characters back.

The failure is silent. An address trimmed to fit loses whatever sat at its end, and a truncated &owner=1 reads as an absent owner rather than as damage — so the first symptom is a rotation failing on something with no obvious connection to length.

So the Add identity form shows a live character count, and the dashboard refuses an over-long address naming the overage and what to drop. The plugin checks too and warns at or over the limit, so this is the earlier of two guards rather than the only one — but a warning in an action log arrives after the managed system already exists. Three levers, in the order worth trying:

  1. Drop anything already at its default. secret=cert/{system}/{account} (31 characters), retain=1, subject=CN={AccountName}, warn=25, key=rsa3072, pbe=Aes256, store=SecretsSafe and wait=30 all cost characters to say what the plugin would have done anyway. On AWS, region= is the same thing — it is the ARN's own fourth field.
  2. Move per-identity values onto the managed account name, after a ? — svc-deploy-pipeline?dns=deploy.corp.example.com&lifetime=7d. dns=, ip=, subject=, lifetime=, eku= and key= are all accepted there, where they cost nothing from this budget and stop forcing a managed system per SAN set.
  3. Shorten folder=. Certificates/Pipelines → Certs/Pipelines buys 7 and loses nothing: a folder is addressed by path, not read as prose.

The most expensive single field is an AWS arn= at 101 characters with a real account id and CA id — which is why an AWS profile in particular wants its subject on the managed account. If every option is genuinely doing work, the profile needs splitting across two managed systems; a managed system is already the unit that pins one CA and one template, so a profile too large for one address is usually two profiles.

A mistyped option is refused, not ignored

The plugin logs an unrecognised option as a warning and carries on with a default. That is the failure this dashboard refuses instead: lifetim=30d has no effect at all and leaves a real certificate issued against a validity nobody chose, hours later, on a schedule. The dashboard validates the whole grammar at registration — unknown backends, missing required options, backend-scoped options on the wrong backend, out-of-range values, and a publisher without its target id.


What this feature does not do

Being straight about the boundary is more persuasive than eliding it.


Settings

Settings → Preview features → Certificate Lab → Configure. The row's toggle is cert_lab_enabled. Nothing here is a secret except the two keys marked as one.

Platforms and functional accounts

Key Default What it is
cert_ps_platform Certificate The leaf package's platform, by name or id, resolved live
cert_ps_subca_platform Subordinate CA The subordinate-CA package's platform
cert_ps_workgroup blank: passwordsafe_workgroup Where managed systems are created
cert_ps_functional_account_mode create create mints one functional account per CA from the build's outputs; reference uses an account you maintain (the only option for ADCS and other bring-your-own CAs)
cert_ps_functional_account blank reference mode: the leaf platform's account
cert_ps_subca_functional_account blank: cert_ps_functional_account reference mode: the subordinate platform's account. A functional account is platform-bound, so running both platforms needs both

How a created account reaches BeyondInsight (create mode)

Key Default What it is
cert_ps_bi_auth auto auto, oauth or apikey. auto prefers a dedicated client id, then an API key you set, then the dashboard's own pscli_* pair
cert_ps_bi_client_id / cert_ps_bi_client_secret blank: pscli_client_id A BeyondInsight OAuth registration for the plugin. Both or neither; the secret is a secret
cert_ps_bi_api_key blank The packed fallback, where the console offers no OAuth credential type on a plugin platform. A secret
cert_ps_bi_run_as_user blank: pscli_api_account_name The run-as user for the API key

Where the plugin writes the bundle

Key Default What it is
cert_ps_biurl blank: pscli_api_url's origin The BeyondInsight base URL the plugin calls. On a Cloud tenant this is the only place it can come from
cert_ps_folder Certificates The Secrets Safe folder. It must exist; the plugin creates nothing
cert_ps_owner_group_id blank The OwnerGroupId for created secrets. Secrets Safe requires an owner. Not the same as secrets_bt_owner, which is a user id
cert_ps_secret_template blank: cert/{system}/{account} The secret's path

Certificate profile defaults. Applied to a new managed system unless the form overrides them. A blank one is left out of the address entirely, which matters with 255 characters to spend.

Key Default What it is
cert_default_lifetime 24h 90m, 12h, 30d, 2w, 1y; a bare number means days. ADCS ignores it
cert_default_key ecdsa-p256 rsa2048, rsa3072, rsa4096, ecdsa-p256, ecdsa-p384 or ecdsa-p521
cert_default_eku ClientAuth Named explicitly: under eku=Auto a DNS SAN brings in serverAuth
cert_default_warn blank: the plugin's 25 Renewal threshold, as a percentage of the certificate's own lifetime
cert_default_subject blank: CN={AccountName} The subject
cert_subca_default_lifetime blank: the plugin's default A subordinate CA's lifetime. Rotation does not revoke, so this is the exposure window if its key leaks: issue it just longer than the rotation interval. The plugin cautions above 45 days

GCP CAS lab

Key Default What it is
cert_gcp_cas_location us-central1 CAS is regional: the pool, its CAs and any template share it
cert_gcp_cas_tier DEVOPS DEVOPS (about $20 a month per pool, keeps no certificate records) or ENTERPRISE

Where things live

Page /workload-lab#certificates → web_dashboard/templates/workload_lab/ (index.html + _certificates.html)
API web_dashboard/api/cert_lab.py (/api/cert-lab/*)
CA lifecycle web_dashboard/services/cert_lab_service.py
Address + Password Safe objects web_dashboard/services/cert_ps_service.py
Address grammar + registration web_dashboard/services/ps_resource_service.py (method="certificate")
Terraform terraform/cert_ca/gcp_cas/main.tf, terraform/cert_ca/aws_pca/main.tf
Playbooks examples/playbooks/certificates/
Tests tests/test_ps_certificate.py, tests/test_cert_lab_wiring.py, tests/test_cert_lab_clouds.py, tests/test_cert_lab_functional_account.py