BeyondTrust/POV

← Back to dashboard

Cloud-DB Password Safe plugin setup + test (Azure Run Command / AWS SSM / GCP Cloud SQL)

Audience: operator · Profile: demo · Read this when: you are standing up database credential rotation and need the one-time manual setup.

Operator runbook for standing up database credential rotation through the custom Password Safe plugins: the three {engine} Azure Run Command Plugins, the three {engine} SSM Custom Plugins and the three GCP Cloud SQL {engine} plugins. Covers what must exist before the settings panel is useful, exactly what to put in each field, and how to prove the plugin ran.

Feature reference: Databases → Layer 2. Panel: Settings → Integrations → Password Safe → Configure → Databases tab → Database Onboarding.

Scope: AWS, Azure and GCP. OCI databases provision and get a tunnel, but have no Password Safe onboarding path at all.

On GCP every channel is now built, and none has been run against a live instance. The Data API channel covers verify, change managed account, change functional account and discovery on PostgreSQL and MySQL — no action returns "not implemented in this build" any more — and Cloud Run covers SQL Server. passwordsafe_gcp_db_registration_method still ships off, because the reason is no longer a missing implementation: it is that nobody has completed a live rotation yet. Run §5 in the order it gives — Verify Functional Account first, then Discovery, then Change Password — and read §6 before you retry a failed change.


0. Decide where the functional account comes from

clouddb_ps_functional_account_mode — Functional account source at the top of the panel — decides this, and it changes what half the panel means. Set it before anything else.

Mode Who owns the functional account What you put in the panel
reference — Reference an existing account You do. Create it in BeyondInsight and name it here. The dashboard resolves it by name, never creates it, and never deletes it on decommission. the account name
create — Create one per database (legacy default) The dashboard. One account per database at provision time, deleted on decommission. the credential material it packs into that account

reference is the same contract the VM onboarding (passwordsafe_vm_functional_account_*) and the k8s token rotation (k8s_ps_functional_account_*) have always used, and it keeps the static cloud credential — the IAM access key, the Azure client secret — out of the dashboard's config store entirely.

Three consequences worth knowing before you pick it:

The account you reference must be <mode>:<dbLogin>, not a plain name

This is the single most expensive mistake in reference mode, because everything about it looks fine until a rotation runs. Every DB plugin parses its functional account positionally, exactly the way it parses the managed-system address. All four AWS SSM actions — Verify FA, Verify MA, Change FA, Change MA — open with:

accountName.Split(':')[1]   -> the DB login the plugin exports as PGUSER / -U / -U
password.Split(':')[0], [1] -> the AWS access key pair (ignored in EC2 mode)
password.Split(':')[2]      -> that DB login's password on the target database

None of those indexes is bounds-checked. So a functional account named for the IAM user alone — psafe-clouddb-ssm, which is the obvious thing to type — onboards green and then fails every credential action with:

Index was outside the bounds of the array.

thrown inside the plugin, on the managed system, hours after the provisioning job that attached it reported success. The platform check cannot catch it: the account is on exactly the right platform. (Live 2026-08-27, AWS postgres.)

Cloud Functional account Username Functional account Password
AWS (IAM-user mode) <iamUserName>:<dbLogin> <accessKeyId>:<secretAccessKey>:<dbLoginPassword>
AWS (EC2-role mode) EC2:<dbLogin> — case-sensitive x:x:<dbLoginPassword>
Azure SP:<dbLogin> / MSI:<dbLogin> <clientId>:<clientSecret>:<dbLoginPassword> / -:-:<dbLoginPassword>
GCP ADC:<dbLogin> / IMP:<dbLogin> / SA:<dbLogin> -:<impersonationTarget|->:<dbLoginPassword|->, or <base64KeyFile>:-:<dbLoginPassword|-> under SA:

Two traps inside the trap:

Onboarding now refuses a referenced account whose name it can prove the plugin cannot parse, and the error names both halves of the contract — but only the name can be checked. Password Safe never returns a functional account's password over the API, so a password with fewer than three :-parts still fails the same way, inside the plugin.

The <dbLogin> must also exist on every managed server, with that password. The dashboard creates only the dedicated managed user (psafe_<id12>); it never creates the functional account's own DB login, and it cannot — it does not know that password. On AWS this is the real cost of reference mode: each provisioned RDS instance gets a random master password, so one shared functional account can only work if you create its login (e.g. psfa, with CREATEROLE) on each server yourself. If that is not something you want to do per database, use create mode on AWS, where the functional account is that database's own master user and the whole question disappears.

What create mode packs into the account it mints

Only relevant if you stay on the legacy default. The dashboard calls create_functional_account_on_platform per database with:

Cloud Functional account username Functional account password
Azure (SP mode) SP:<db-admin-login> <clientId>:<clientSecret>:<adminPassword>
Azure (MSI mode) MSI:<db-admin-login> -:-:<adminPassword>
AWS (IAM mode — both key fields set) IAM:<db-admin-login> <AccessKeyId>:<SecretAccessKey>:<adminPassword>
AWS (EC2-role mode — either key field blank) EC2:<db-admin-login> x:x:<adminPassword>

The AWS password is always three :-parts — the plugin splits it before it looks at the mode, so EC2 mode ships the two x placeholders rather than dropping the parts.

Note the Azure and AWS rows embed the per-database minted admin password. That is why a single pre-created account only works with self-rotation — see the next section.

Self-rotation is the other half of reference mode

The Azure Run Command plugins (v1.0.0.1) expose two change actions, and Password Safe picks between them from Change Password Using Own Credentials on the managed account:

Action What the plugin does What the functional account needs on the DB
Change Managed Account (using self) ALTER USER on itself / ALTER USER CURRENT_USER() / ALTER LOGIN … OLD_PASSWORD nothing — a login that can authenticate, for Verify Functional Account only
Change Managed Account (using Functional Account) ALTER USER/ALTER LOGIN on the target CREATEROLE (PG ≤ 15); CREATEROLE and ADMIN OPTION on the role (PG 16+ — both, not either); CREATE USER (MySQL), ALTER ANY LOGIN (SQL Server)

A dashboard-provisioned server has no such privileged login, so reference mode requires the self-rotate action. Turn on Rotate with the account's own credentials (clouddb_ps_self_rotation) — it emits use_own_credentials = true on the managed account. Leave it off in create mode, where the functional account is the minted DB admin and therefore already privileged.

"A login that can authenticate" is a real requirement on Azure SQL, not a given. Azure SQL Database disables guest in master, so a login with no USER there cannot open a session at all and the rotation fails at CONNECT — before ALTER LOGIN — with Cannot open database "master" requested by the login. Onboarding therefore creates the managed login and a role-less contained user in master (master because that is the database every SQL Server managed system's address names, and the only one Azure SQL permits ALTER LOGIN in). Nothing to do by hand; a SQL Server system onboarded by a build from before 2026-09-03 needs Register in Password Safe re-run on the row. AWS RDS and Cloud SQL keep guest enabled in master, so they get the login only.

Two consequences that are easy to miss:

Tier Used when Where the identity lives
1 FA name is SP:<dbUser> the functional account's own service principal
2 FA name is MSI:… and ControlPlane:ClientId is set on the broker AppSettings:Azure{Postgres,MySql,Mssql}:ControlPlane — ClientSecret, or a PFX via CertificatePath (preferred)
3 FA name is MSI:… and nothing is configured DefaultAzureCredential — an ambient managed identity

So with SP: functional accounts an off-Azure broker needs no extra setup. Tier 2 is the escape hatch for MSI: mode on a broker with no managed identity; it is broker-side config the dashboard cannot set. Prefer the certificate, or set the secret as a machine environment variable (AppSettings__AzureMssql__ControlPlane__ClientSecret) — the shipped appsettings.json lives inside the .psplugin, so a secret in the file is baked into the package.

Half-configured is a silent downgrade: ControlPlane:ClientId with neither a secret nor a certificate logs a warning and falls through to DefaultAzureCredential rather than failing — the same shape as the two-of-three IAM trap in §3.

Self-rotation also needs Password Safe to hold the account's correct current password. It does — the dashboard seeds it at onboarding — but if it ever drifts, the self-login fails and you fix it out of band or temporarily turn this off.

Naming, so you can find things in BeyondInsight after a test run

<id> is the provisioning job's identifier:

Either way, the accounts you create by hand include the underlying cloud identities (an Azure service principal or an AWS IAM user) plus a PRA Configuration-API OAuth client. Those are section 1.


1. Prerequisites, before the panel is worth opening

1.1 Upload the plugins

BeyondInsight → Configuration → Privileged Access Management → Platform Plugins. Upload the .PSPLUGIN for each engine you intend to test, plus the shared PRA Vault plugin:

Plugin Platform name the dashboard expects by default
Azure PostgreSQL Run Command PostgreSQL Azure Run Command Plugin
Azure MySQL Run Command MySQL Azure Run Command Plugin
Azure SQL Server Run Command MSSQL Azure Run Command Plugin
AWS psql SSM psql SSM Custom Plugin
AWS mysql SSM mysql SSM Custom Plugin
AWS mssql SSM mssql SSM Custom Plugin
GCP Cloud SQL PostgreSQL GCP Cloud SQL PostgreSQL
GCP Cloud SQL MySQL GCP Cloud SQL MySQL
GCP Cloud SQL SQL Server GCP Cloud SQL SQL Server
PRA Vault (shared) PRA Vault Username Password

The platform name is looked up by name at registration time. A mismatch fails the lookup with the name quoted in the job error, so it is self-diagnosing — but it fails after the database is already built.

The GCP SQL Server plugin is the one that needs infrastructure: Cloud SQL for SQL Server does not support IAM database authentication for database operations, so it runs on the plugin's cloud-run channel rather than data-api. See §1.7.

1.2 Azure — the RSA key pair

The Azure plugin hands the new password to the jump VM RSA-wrapped. Generate an RSA-4096 pair (scripts/make-plugin-cert.sh in the plugin repo — it is not in this repo), then:

The single worst trap in this feature. If Plugin Private Key (PEM) is blank, the key drop is skipped silently — _azure_jump_prep_commands() simply omits those commands. Jump-VM prep still reports success, the DB still provisions, the managed system still registers. You only find out at the first rotation, when the plugin cannot decrypt, and nothing in the provisioning job hints at the cause.

1.3 Azure — the service principal

In reference mode the SP's client id and secret go into the functional account you create, not into the panel — the SP Client ID / Secret fields are not read at all. Either way the identity needs the same permission.

Grant the SP behind the functional account Virtual Machine Contributor on the jump-VM resource group (or just Microsoft.Compute/virtualMachines/read + .../runCommand/action). Reusing the SP the dashboard already deploys Azure VMs with is supported and is the default — leave SP Client ID / Secret blank to reuse azure_client_id / azure_client_secret.

Also confirm azure_subscription_id and azure_tenant_id are set on the Azure panel: they become address fields 3 and 4, and the plugin parses the address positionally, so a blank one shifts nothing — it just yields an empty field the plugin cannot resolve.

1.4 AWS — the IAM user (IAM-user mode)

Because the plugin runs on a Password Safe node / Resource Broker that is not an EC2 instance, EC2-role mode is not available to you. In reference mode the access key goes into the functional account you create and the panel's key fields are not read; in create mode leaving either key field blank selects EC2-role mode (username EC2:<dbAdmin>, password x:x:<adminPassword>), which parses fine but authenticates with the broker host's own AWS credentials — credentials an off-EC2 broker does not have, so rotation fails at the AWS call. Either way, create an IAM user with:

and an access key. The IAM Username field is informational only — the plugin never sees it; in create mode the mode is selected purely by the key pair's presence.

In reference mode the mode is selected by your account's Username instead, and it must be <iamUserName>:<dbLogin> (or the case-sensitive EC2:<dbLogin>) with the access key pair in the password — see The account you reference must be <mode>:<dbLogin> in §0 before you create it. A name with no : is the one mistake here that survives onboarding.

Keep Assume Role at NoAssumeRole (a full AssumeRole ARN belongs there only in the cross-account EC2-broker mode). The segment must be ≥ 12 characters — the plugin Substring(0,12)s it, so the old local value crashes every action; the dashboard coerces a shorter value to NoAssumeRole on read.

1.5 AWS — the jump-host RSA prep is manual

The dashboard now stages the AWS key material, the same way it does for Azure. Paste private.pem into Plugin Private Key (PEM) and its passphrase into Plugin Key Passphrase in the AWS block, and set Key Directory to wherever the plugin reads them (default /home/ssm-user). The dashboard drops both onto the shared Gateway host over SSM before creating the managed user, dir 700 / files 600.

Use a separate key pair from Azure's. The two private keys land on different hosts — this one on the ECS gateway host, Azure's on clouddb-jumpoint — so one shared pair means a compromise of either host also decrypts the other cloud's payloads. scripts/sandbox/Linux/make-clouddb-plugin-keys.sh generates both pairs and verifies each matches its own certificate.

Leaving the fields blank is still supported and still means you place the files by hand; the difference is that the skip is now logged rather than silent.

What the dashboard does not do is put a DB client on that host's PATH. Its own managed-user creation uses a docker run client image and needs none, and the ECS host is shared with the gateway workload — so a green provisioning job still tells you nothing about whether the plugin's ongoing rotation will work.

Set Public Key Path (on PS node) to where the matching public certificate lives on the PS node/broker; it is the address's certPath segment (field 4 for mssql, field 5 for psql/mysql) and is required — onboarding refuses a blank rather than packing an empty positional field the plugin only trips over at the first rotation.

1.6 All three clouds

1.7 GCP — the rotator service account and the broker identity

There is no key pair and no jump-host prep here — none of §1.2's or §1.5's apparatus has a counterpart on either GCP channel. What you create depends on the engine:

Engine Channel What you have to stand up
PostgreSQL, MySQL data-api A rotator service account. No infrastructure at all
SQL Server cloud-run Nothing by hand — name the broker service accounts and press Deploy (§below)
SQL Server data-api (fallback) No infrastructure, but a regional Secret Manager version holding the functional account's password, in the instance's own region

cloud-run is the recommended path for SQL Server and the default. data-api works too now, but it is a genuine trade rather than a free alternative: with no IAM database authentication the Data API has to be handed the functional account's password out of Secret Manager (address option fasecret=), so Password Safe stops being the sole authority for that credential and nothing re-syncs the copy. Rotating the functional account itself then breaks every rotation it drives until you update the secret by hand. cloud-run mirrors nothing — the credential travels in the request body.

If you take the data-api route: in create mode the dashboard stages the regional secret itself, one per database, and deletes it on decommission. In reference mode it cannot — that account's password is one the dashboard has never seen — so stage it yourself and put the version resource name in clouddb_ps_gcp_fa_secret_version:

projects/<project>/locations/<region>/secrets/<name>/versions/latest

Three rules, all enforced at the click rather than at the first rotation:

It must be regional. The global projects/<project>/secrets/… form is what the plugin article's own example prints and what the dashboard's Secrets page creates, and the Data API rejects it with "does not match the expected format [projects/*/locations/*/secrets/*/versions/*]". The dashboard refuses the global form when you save the address rather than letting a rotation find out. Note that gcloud 578 cannot create a regional secret either (no --location), so use the API directly.

The rotation identity. One service account per project, shared by every database — and a PostgreSQL/MySQL concept. SQL Server has no IAM database authentication on either channel, so it never registers one, on either; its functional account is a real login. Skip this whole subsection if SQL Server is all you are onboarding.

gcloud iam service-accounts create bt-rotator --project=<project> \
    --display-name="BeyondTrust Password Safe Cloud SQL rotator"

Name it short. MySQL truncates an IAM database username at the @ and caps it at 32 characters, so bt-rotator (10) is safe and bt-passwordsafe-cloudsql-rotator-prod (37) is not. Put the full email in clouddb_ps_gcp_rotator_service_account; the dashboard registers it as an IAM database user on each instance it onboards.

PostgreSQL has a second, tighter budget, and it is the one that bites first. A Postgres role name is capped at 63 characters and the whole email has to fit, so the dashboard registers the email with the .gserviceaccount.com suffix dropped — Google's documented form, and the name the database then stores. That leaves 63 − len("@<project>.iam") for the local part: with a 30-character project id, bt-rotator fits with room to spare, but a long local part and a long project id can still overflow. When it does, onboarding fails at 25% with HTTP 400 ... User name "..." to be created is too long (max 63) and nothing is created — shorten the service account, not the project.

For the data-api channel the smallest sufficient predefined role is:

gcloud projects add-iam-policy-binding <project> \
    --member=serviceAccount:bt-rotator@<project>.iam.gserviceaccount.com \
    --role=roles/cloudsql.instanceUser

It carries cloudsql.instances.executesql and cloudsql.instances.login and — the point — not cloudsql.users.update, which among predefined roles lives only in the very broad roles/cloudsql.admin. Scope the binding to specific instances with an IAM condition on resource.name where you can.

The broker identity. The functional-account prefix picks how the Resource Broker authenticates to GCP:

Prefix When What the broker needs
ADC: Broker on a Compute Engine VM The rotator attached to the VM. Nothing stored in Password Safe or the dashboard
ADC: Broker on-premises or in another cloud A key file at GOOGLE_APPLICATION_CREDENTIALS on each broker — the dashboard can generate the key but cannot place it
IMP: Broker already has some GCP identity roles/iam.serviceAccountTokenCreator on the rotator, granted to that identity
SA: Broker with no GCP identity at all — and every Cloud SQL SQL Server onboarding, which cannot use reference mode The base64 of the rotator's JSON key in password segment 1. In create mode the dashboard mints one per database for clouddb_ps_gcp_rotator_service_account and deletes it at teardown — it needs roles/iam.serviceAccountKeyAdmin on that account, and clouddb_ps_gcp_sa_key overrides it where that is out of reach or constraints/iam.disableServiceAccountKeyCreation is set. In reference mode you paste it yourself. The ~3.2 KB composite stores fine, but the plugin refuses a write-back over 1000 characters — so leave password management off for that account

The functional account for Cloud SQL SQL Server. There is nothing to create by hand in create mode: the dashboard mints both halves — a dedicated psafe_<12hex>_fa database login via users.insert, and the Password Safe functional account carrying it. Keep the instance admin credential out of Password Safe; that is the point.

Set clouddb_ps_functional_account_mode_gcp_sqlserver = create rather than the SQL Server engine override. The engine rung governs all three clouds, and reference is the right answer for AWS and Azure (they use operator-created psfa_mssql accounts) — setting it there is what silently sends GCP SQL Server down the manual path. Blank on this key falls through, so it changes nothing until you set it.

In reference mode you owe the database a login per instance, with the password matching the third :-segment of the functional account's password — which Password Safe never hands back. The onboarding job says so and names the key above.

The Cloud Run service (SQL Server). The dashboard deploys this. In Settings → Password Safe → Databases tab → GCP (Cloud SQL plugins) → Cloud Run channel:

  1. Put each Resource Broker's service account in Invoker service accounts. A bare email is fine — the dashboard prefixes it. allUsers and allAuthenticatedUsers are refused by the Terraform module itself, not merely discouraged.
  2. Press Deploy for the region your database is in. One service per region: it holds no per-database state, and Direct VPC egress is region-locked.

The job (clouddb_dbops_deploy) applies twice — once to create the service, once to stamp its own URL in as the audience, which does not exist until the first apply has finished. Between the two the service is deployed and refuses every request.

What the dashboard sets, and why each one is load-bearing:

If your brokers are outside GCP they cannot reach ingress=internal, so the service defaults to public ingress plus IAM: a credential-changing API with a globally resolvable endpoint, protected by IAM rather than network position. Compensate with an org policy (constraints/iam.allowedPolicyMemberDomains) that makes an allUsers binding impossible. Set Ingress → Internal only when every broker runs on Compute Engine. Private Service Connect is the follow-on hardening step; because the dashboard records the audience rather than an operator memorising it, that flip is a redeploy, not a migration.

Prove it before wiring a managed system to it. The service answers an unauthenticated- by-IAM-only health probe that touches no database:

curl -H "Authorization: Bearer $(gcloud auth print-identity-token --audiences=<service-url>)" <service-url>/

It reports the contract path, the versions it can serve, its region, and how many instances are in its allowlist.

The v1 contract is implemented, from the plugin repo's own PLAN-CloudRunSqlServer.md §2 and §4. /v1/credential-op serves verify, change, change-self and list-accounts, answers in the plan's response envelope, and maps each database failure onto its status/code table — so a wrong password comes back as 401 DB_AUTH_FAILED (a legitimate Verify "false") rather than as a generic failure.

Three things to know before the first rotation:

gcloud run services update <service> --region <region> --set-env-vars FN_DBOPS_CAPTURE=1

then read jsonPayload.msg="dbops_contract_capture" in Cloud Logging. Capture defaults off — on, it would write a redacted request body into the log on every rotation forever — and a redeploy from the dashboard resets it.

Deploying it yourself is still supported. Use the plugin repo's ps-dbops-sqlserver Terraform module and put its stable custom audience in clouddb_ps_gcp_dbops_audience — that key is now an override rather than the only source, and a dashboard-deployed service in the database's own region beats it.

The Cloud Run service account is strikingly small — it needs none of roles/cloudsql.client, roles/cloudsql.instanceUser or roles/cloudsql.admin, because it uses neither IAM database authentication nor a Cloud SQL connector.

The database grant. The functional account needs rights over each managed principal. On the GCP data-api channel the dashboard issues these itself as the built-in admin and prints them on the provisioning job only if that fails; everywhere else it can only print them — run them as an admin:

sql ALTER ROLE "<fa>" WITH CREATEROLE; -- once per instance GRANT "<managed>" TO "<fa>" WITH ADMIN OPTION; -- once per managed role

CREATEROLE alone is no longer sufficient — but note this means ADMIN OPTION was added to the requirement, not that it replaced CREATEROLE. PostgreSQL's AlterRole() wants "the CREATEROLE attribute and the ADMIN option on the role". Hold only one and the rotation fails with permission denied to alter role, and because the Data API drops the errdetail that names the missing half, that message is identical whichever half you are missing — it reads like the grant never ran. This is the most likely source of "worked in dev, 403 in production". - MySQL: GRANT CREATE USER ON *.* TO '<fa>'@'%';. Do not grant UPDATE ON mysql.* — Cloud SQL restricts DML on mysql.user. - MySQL, for Account Discovery only: GRANT SELECT ON mysql.user TO '<fa>'@'%';. CREATE USER confers no read of the account catalogue, so a rotator that can change every password on the instance still cannot enumerate the accounts — Discovery comes back as MySQL 1142. The dashboard applies this itself as a second, independent statement and reports it on the job if it cannot; rotation and Verify are unaffected either way, so a refusal here costs you Discovery and nothing else. There is no privilege-free substitute: information_schema.USER_PRIVILEGES is derived from mysql.user and needs the same grant, and performance_schema.accounts sees only accounts that have connected and reports the client host rather than the account's host qualifier, which breaks the user@host round-trip that makes a discovered account rotatable. - SQL Server: ALTER SERVER ROLE CustomerDbRootRole ADD MEMBER [<fa>];, which carries ALTER ANY LOGIN. sysadmin is unavailable on Cloud SQL and ALTER ANY LOGIN is exactly enough. On GCP Cloud SQL in create mode the <fa> here is the dedicated psafe_<12hex>_fa login the dashboard minted, not the instance admin — the job names it and pastes the statement, because cloud-run has no Data API to issue it with.

Or skip the grant entirely on SQL Server by turning on self-rotation: the managed login alters itself with OLD_PASSWORD and the functional account needs no privilege over it at all. That is the recommended setting on cloud-run, and the dashboard emits use_own_credentials only for that channel.

Everything else is automatic on the Data API path: the dashboard enables the Cloud SQL Data API (off by default per instance) and the cloudsql.iam_authentication flag at onboarding, and new instances get the flag from the Terraform module. On cloud-run none of that runs — the service connects with a database login, so there is nothing to patch.


2. Azure panel fill-in

Settings → Integrations → Password Safe → Configure → Databases tab, then expand the Azure (Run Command plugins) subsection.

Field Value Key
Enable Password Safe DB onboarding on — drives both clouds clouddb_ps_onboarding_enabled
Functional account source Reference an existing account (recommended) or Create one per database — see §0 clouddb_ps_functional_account_mode
Rotate with the account's own credentials on with reference mode; off with create — see §0 clouddb_ps_self_rotation
PostgreSQL / MySQL / SQL Server Platform leave blank (falls through to the defaults; advisory in reference mode) clouddb_ps_platform_azure_*
PostgreSQL / MySQL / SQL Server Functional Account reference mode only — the account you created on each Run Command platform clouddb_ps_functional_account_azure_*
Azure onboarding method Run Command plugins (the default) passwordsafe_azure_db_registration_method
Auth Mode create mode only — SP (service principal). Not read in reference mode clouddb_ps_azure_auth_mode
Broker Cert Path where you copied public_cert.cer, e.g. C:\BeyondTrust\certs\public_cert.cer — must match the broker's real path clouddb_ps_azure_cert_path
Require TLS (sslTRUE) on — Azure flexible servers reject plaintext clouddb_ps_azure_ssl
SP Client ID / Secret create mode only — blank to reuse the dashboard's Azure SP. In reference mode leave both blank; the credential lives in your functional account clouddb_ps_azure_sp_client_id / _client_secret
Plugin Private Key (PEM) the full private.pem, BEGIN/END lines included clouddb_ps_azure_plugin_private_key
Plugin Key Passphrase the passphrase for that key clouddb_ps_azure_plugin_passphrase

The client-image fields (postgres:16, mysql:8.4, and blank for SQL Server) are shared with AWS and only used by the dashboard's own managed-user creation. Leave them blank. SQL Server has no image because none exists — mcr.microsoft.com/mssql-tools18 is the apt/dnf package name, not a registry repository — so its managed-user step runs the jump host's native /opt/mssql-tools18/bin/sqlcmd, installed by the jump-host prep on both clouds. Filling the SQL Server field in forces the container path back on and requires an image carrying sqlcmd 18 at that same path.

Address the plugin receives — eight ;-separated fields:

vmName;resourceGroup;subscriptionId;tenantId;dbHost;dbName;certPath;sslTRUE

Leave Azure onboarding method on Run Command plugins. Set to Off it skips Azure entirely and falls back to legacy admin-credential staging while the AWS SSM path stays on — it is the only way to disable one cloud, because the enable checkbox is shared by both. An unset key still reads as enabled at runtime, so an older install that never touched this control behaves the same.


3. AWS panel fill-in

Same panel, the fields above the Azure subsection.

Field Value Key
PostgreSQL / MySQL / SQL Server Platform leave blank (defaults; advisory in reference mode) clouddb_ps_platform_*
PostgreSQL / MySQL / SQL Server Functional Account reference mode only — the account you created on each SSM platform clouddb_ps_functional_account_*
PRA Vault Platform blank → PRA Vault Username Password clouddb_ps_pravault_platform
PRA Vault Functional Account reference mode only — your PRA Config-API account on the PRA Vault platform clouddb_ps_pravault_functional_account
Workgroup blank → reuses passwordsafe_workgroup clouddb_ps_workgroup
IAM Username informational only — the plugin never sees it. Blank is fine in both modes clouddb_ps_ssm_iam_username
IAM Access Key ID create mode — AKIA…. Both key fields set → IAM mode; either blank → EC2-role mode (broken in your topology, §1.4) clouddb_ps_ssm_access_key_id
IAM Secret Access Key create mode — the secret (encrypted at rest) clouddb_ps_ssm_secret_access_key
Assume Role NoAssumeRole (a cross-account AssumeRole ARN only in EC2-broker mode). ≥ 12 chars — shorter crashes the plugin and is coerced to the placeholder clouddb_ps_ssm_account_suffix
Public Key Path (on PS node) RSA public-cert path on the PS node/broker, e.g. C:\Utils\public_ssm.pem. Required clouddb_ps_ssm_public_key_path
Require TLS (MySQL) on — the trailing sslTRUE/sslFALSE segment MySQL alone carries; only the literal sslTRUE enables TLS clouddb_ps_ssm_ssl
Plugin Private Key (PEM) the full private.pem for the AWS pair, BEGIN/END included clouddb_ps_ssm_plugin_private_key
Plugin Key Passphrase its passphrase clouddb_ps_ssm_plugin_passphrase
Key Directory where the SSM plugin reads that key on the jump host clouddb_ps_ssm_key_directory

In create mode the two key fields are load-bearing together: IAM mode needs key id and secret, and one-of-two selects EC2 mode (the plugin still parses, but the broker has no AWS credentials to use — §1.4). The IAM Username field no longer participates. reference mode sidesteps the whole choice — the credential is in the functional account and none of these are read.

Address the plugin receives — ;-packed at fixed per-engine positions (mssql has no database segment; mysql alone ends in an ssl flag). The DB port is the managed system's Port field, never part of the address:

mssql (5):  instanceId;region;dbEndpoint;certPath;assumeRole
psql  (6):  instanceId;region;dbEndpoint;databaseName;certPath;assumeRole
mysql (7):  instanceId;region;dbEndpoint;databaseName;certPath;assumeRole;sslTRUE|sslFALSE

The packed address rides the DNS Name field only. The managed system's IP Address field is the 127.0.0.1 placeholder every other onboarding shape uses: Password Safe refuses a create with no IP at all (The field 'IPAddress' is required.) and equally refuses one that is not a literal IP (Bad IP value: '<address>' in 'IPAddress' field) — both seen live, each after a full RDS apply — so a value that is both a packed address and an IP does not exist. Registration refuses a non-IP ip_address up front rather than spending a database to learn it again.


4. GCP panel fill-in

Settings → Integrations → Password Safe → Configure → Databases tab, then expand the GCP (Cloud SQL plugins) block at the bottom of Database Onboarding.

Field Value
GCP onboarding method Cloud SQL Data API plugins to enable GCP at all; Off disables it while leaving AWS and Azure on
Channel Automatic — Data API for PostgreSQL/MySQL, Cloud Run for SQL Server. Override only to force one channel for every engine
PostgreSQL / MySQL / SQL Server Platform The platform names exactly as uploaded
… Functional Account reference mode only — the operator-created account on each platform
GCP Identity Mode ADC unless the broker needs impersonation; then IMP plus the target below
Impersonation Target IMP mode only — the rotator's full email
Rotator Service Account Data API only. The full email from §1.7, ≤32 characters before the @ for MySQL
Cloud Run Audience Cloud Run only, and required for it. The stable custom audience from §1.7. Blank leaves SQL Server off
Require TLS … to the database Address field 5. On by default

There is no certificate path, no client image and no key material in this block — none of them exist in this design.

reference is the recommended mode here. Unlike Azure, whose functional-account composite embeds a per-database admin password, the GCP composite under IAM authentication is -:-:- — it holds nothing per-database, so one operator-created account per engine covers every Cloud SQL instance on that platform.

clouddb_ps_self_rotation is honoured per channel here. §0 says reference mode requires it, and on cloud-run that holds — it is also the preferred setting, because the managed login alters itself with OLD_PASSWORD and the functional account needs no privilege over it. On data-api the plugin refuses self-rotation at pre-flight, so the dashboard drops the flag for those managed systems and grants the functional account rights over the managed principal instead. Leaving the flag on for AWS/Azure is therefore safe for both GCP channels.

SQL Server is the one GCP engine whose create-mode composite is per-database, since it carries the minted admin password — the same property Azure has, and the same reason to prefer reference mode with self-rotation.


5. Test procedure

  1. Save the panel, then confirm the values came back — reopen it. Config is encrypted with a key derived from JWT_SECRET_KEY, so a mismatched key reads back as blanks rather than as an error.
  2. Provision one small database per engine you want to prove, on the cloud under test. Azure sends Run Commands to the shared clouddb-jumpoint VM (Standard_B2s); the DB clients get baked in on a fresh VM and ensured idempotently on a reused one.
  3. The plugin writes its own log on the Resource Broker — Logs\log-Azure<Engine>RunCommand-<date>.txt beside the plugin assembly, rolling daily, last 3 files. Start there for any plugin-side failure; the dashboard's job log only covers what the dashboard did.
  4. Read the provisioning job. Look for these log lines — each is the checkpoint for one step: managed DB user %r created, onboarded DB managed system … system_id=… account_id=…, and either onboarded PRA Vault managed system or no PRA Vault account … skipping PRA Vault onboarding.
  5. In BeyondInsight, confirm <id>-db exists with the right platform, and that its managed account is psafe_<…>. In reference mode also confirm no <id>-ssm-fa / <id>-azure-fa was created — if one was, the mode did not take effect.
  6. This is the actual plugin test: run Change Password on that managed account, then Verify Functional Account. Everything before this point exercises dashboard code paths; only a credential change exercises your plugin.

On GCP, run the actions in this order instead — it is cheaper and it fails faster:

  1. Verify Functional Account first. One call exercises token minting, the executeSql endpoint path and autoIamAuthn together, and if the endpoint path is wrong it fails with a 404 in about thirty seconds. Do not spend a rotation finding that out.
  2. Discovery. This confirms the GRANT SELECT ON mysql.user from §1.7, and it confirms MySQL accounts come back as user@host rather than as bare names — the plugin depends on that round-trip to make a discovered account rotatable at all.
  3. Change Password, then Verify Functional Account again.

If step 3 reports a failure, go to §6 and send the Error Details before retrying. 6. In reference mode, decommission the database and confirm your functional account still exists. Teardown deletes only accounts the dashboard minted, and this is the one check that proves it. 7. Prove the rotation propagated by opening the PRA tunnel and connecting. If the vaulted credential did not move with the DB credential, you need the Password Safe SmartRule / linked-account configuration — the Terraform provider cannot express it, so it is out-of-band by design, not a bug.


6. Reading a failure

On GCP there is no longer a "successful failure" to look for. The "the 'data-api' channel is not implemented in this build" result — which used to be the pass, because reaching it proved the address parsed, the composite parsed, the platform bound and the pre-flight passed — is gone. Every action now either does the work or reports a real cause.

Two GCP results to read carefully rather than retry.

A failure leaves the database up — but not the job green. If the managed-user creation fails, the run falls back to legacy admin-credential staging and appends a Password Safe managed-user creation failed … line to the job log; the tunnel keeps the admin credential and the job completes, so read the log. If managed-system onboarding fails there is no fallback, and the job fails: the error message quotes the cause and names the remedy. The database row stays available in both cases — fix the cause, then use the row's Register in Password Safe action instead of re-provisioning. (This step used to be best-effort: a rejected managed-system create once shipped a completed job whose only trace was a Password Safe onboarding skipped (non-fatal) log line.)

Symptom Cause
Job green, no onboarded DB managed system line onboarding was gated off — clouddb_ps_onboarding_enabled, pscli not configured, or the provision form's Onboard into Password Safe box was cleared (the job log then says the provision opted out). The row's Register in Password Safe action onboards it without rebuilding the database
Platform name quoted in the error uploaded platform name ≠ the configured name
Rotation fails, decrypt error, provisioning was clean Plugin Private Key was blank → no key material on the jump host. Both clouds log this now; grep the job for NOT staging plugin key material
AWS rotation fails to decrypt but Azure works the two clouds use separate key pairs — check the AWS clouddb_ps_ssm_* fields, not the Azure ones
jump-host plugin prep failed the SSM key drop itself failed — a bad Key Directory, or the Gateway host is not reachable over SSM
(AWS) Index was outside the bounds of the array in the plugin log a packed field has too few segments for the plugin's fixed-position parse: an address with the wrong per-engine count (5 mssql / 6 psql / 7 mysql), a functional-account username without its :, or a password without both :s. Systems onboarded before the per-engine formats carry the old six-field address — use the row's Register in Password Safe action to rebuild them
role "psafe_…" already exists / CREATE USER fails on Register in Password Safe a previous attempt created the managed database user before failing later. Onboarding is create-or-reset on every engine, so this is fixed — a build from before 2026-08-27 needs the user dropped by hand, or the newer image
(Azure, SQL Server) Msg 15025 … The server principal 'psafe_…' already exists at 25% Creating the rotatable managed database user the create-or-reset guard above read sys.server_principals, which Azure SQL Database does not populate with SQL logins — so it matched nothing and the create ran anyway. Guards read sys.sql_logins from 2026-09-03; on an older build, drop the login by hand (DROP LOGIN [psafe_…] in master) or take the newer image. AWS and GCP never hit this
(PostgreSQL 16) pq: permission denied to alter role on Change Managed Account, with the managed system and account onboarded cleanly the functional account holds only one of the two privileges PostgreSQL 16 requires. ALTER ROLE … PASSWORD on another role needs the CREATEROLE attribute and ADMIN OPTION on that role; before 2026-09-10 the dashboard issued only the ADMIN OPTION half, on the reading that PG 16 had replaced CREATEROLE with it rather than added to it. The error text is the same whichever half is missing — the Data API drops the errdetail that names it — so this looks identical to a grant that never ran. Check the provisioning job (not the registration job) for the Applied the Password Safe rotation grant line. On an older build, run ALTER ROLE "<fa>" WITH CREATEROLE; as an admin once per instance
(GCP, PostgreSQL) create user 'bt-rotator@<project>.iam.gserviceaccount.com' … HTTP 400 … User name "…" to be created is too long (max 63) at 25% Creating the rotatable managed database user the rotator was registered under its full email, and a Postgres role name is capped at 63 characters — an ordinary bt-rotator@<project>.iam.gserviceaccount.com is 65, so every PostgreSQL onboarding failed on length alone while MySQL (which truncates at the @ itself) was fine. From 2026-09-08 PostgreSQL is registered under Google's documented form, the email minus .gserviceaccount.com; on an older build there is no workaround short of the newer image. Nothing is created when this fires — re-run Register in Password Safe after rebuilding. A long local part can still overflow the 63 even stripped: shorten the service account (§1.7)
(Azure, SQL Server) rotation or Verify fails with Cannot open database "master" requested by the login the managed login has no USER in master, and Azure SQL disables guest there — see §0. Fixed at onboarding from 2026-09-03; re-run Register in Password Safe on the row to add the user to an existing system
Bad IP value: '<packed address>' in 'IPAddress' field a managed system registered by a build between 2026-08-25 and 2026-08-27, which put the packed address in the IP field. Re-register from the row's Register in Password Safe action
(AWS) Index and length must refer to a location within the string the address's assumeRole segment is under 12 characters — the pre-fix local default; re-register, or fix the address in BeyondInsight (NoAssumeRole or a full role ARN)
(AWS) rotation fails and the functional account's name has no : (a bare EC2, or an IAM username) the account cannot be parsed at all — <mode>:<dbLogin> over a three-part password (§0). In create mode it is a pre-fix account: delete it and re-register. In reference mode it is your account: rename it in BeyondInsight and fix its password, then Register in Password Safe. New onboardings refuse an unparseable name up front, so this only reaches the plugin on systems registered before that guard
(AWS) Caught exception when trying to Send Command the parse succeeded — the failure is AWS-side: credentials (EC2 mode on an off-EC2 broker, §1.4), instance id, region, or SSM permissions
(Azure) Thread was interrupted from a waiting state, ~30s after the attempt started the Azure Run Command plugins read the managed system's Timeout as seconds (like GCP, unlike AWS), and until 2026-09-02 the dashboard registered no Timeout at all — so the field sat at Password Safe's default of 30 and the plugin's watchdog interrupted its own wait 30s in. One Azure Run Command round trip is routinely 20-60s before psql even runs, and the plugin answers a 409 by retrying 5 times at 15s, so the wait has to cover that ladder too. New Azure DB managed systems are registered with Timeout 180; a system onboarded before that carries 30 — re-register it from the row's Register in Password Safe action, or raise Timeout to 180 in BeyondInsight. As on GCP, a diagnostic printing timeout 30000 msec is that 30 after the plugin's seconds-to-milliseconds conversion, not evidence that anything registered 30000
(GCP) A cloud-run rotation times out on the first attempt after an idle period, and may already have applied the change the GCP Cloud SQL plugins read the managed system's Timeout as seconds — the opposite unit to the AWS plugins reading the very same field. Password Safe defaults it to 30, and a Direct-VPC cold start on Cloud Run is documented at "a minute or more". New GCP DB managed systems are registered with Timeout 180 (the plugin's own default); a system onboarded before that carries 30 — re-register it from the row's Register in Password Safe action, or raise Timeout to 180 in BeyondInsight. A plugin diagnostic printing timeout 30000 msec is that default after the plugin's own seconds-to-milliseconds conversion; it is not evidence that anything registered 30000
(AWS) Verify reports success within seconds but nothing changed the plugins read the managed system's Timeout as milliseconds for their one retry of the SSM status poll, so at Password Safe's default of 30 a still-InProgress command falls through as success. New AWS DB managed systems are registered with Timeout 30000; a system onboarded before that carries 30 — re-register it from the row's Register in Password Safe action, or raise Timeout to 30000 on the managed system in BeyondInsight
PSPLUGIN_CHANGE_FAILED: … must have admin option on role / CREATE USER denied / ALTER ANY LOGIN self-rotation is off, so Password Safe called the via-functional-account action against a server with no privileged login — turn on clouddb_ps_self_rotation (§0)
PSPLUGIN_CHANGE_FAILED: … password authentication failed for user psafe_… (self-rotation) Password Safe's stored current password drifted from the server
CredentialUnavailableException / DefaultAzureCredential failed to retrieve a token an MSI: functional account reached tier 3 on a broker with no managed identity — switch the FA to SP:, assign an MI, or configure ControlPlane (§0)
Log warns ControlPlane:ClientId is configured but neither ClientSecret nor CertificatePath is set tier 2 half-configured, silently fell through to tier 3 (§0)
Control-plane certificate not found at '…' / CryptographicException ControlPlane:CertificatePath is wrong on that broker, or the password does not match — it is read on the broker, not the jump VM
Managed-system address rejected or truncated Password Safe's address column is 255 characters; the eight-field Azure address gets close with two GUIDs, a flexible-server FQDN and a long cert path. The dashboard now refuses over-long addresses up front and names the number
Verify Functional Account fails on every new managed system the functional account's DB login does not exist on the provisioned server (§0)
Run Command in progress (409) after retries every database shares clouddb-jumpoint and Azure allows one action-style Run Command per VM; the plugin retries 5 times at 15s (6 attempts) — stagger rotations or add jump VMs
Job fails with Password Safe onboarding failed: … no … functional account is configured reference mode with a blank name — set the clouddb_ps_functional_account_* key the message quotes, then use Register in Password Safe on the row
Job log says functional account … is on platform …, not a … the named account is on the wrong plugin platform; the managed system would inherit it (§0)
functional account 'x' not found in Password Safe typo, or the account exists on a platform the API identity cannot see
create-mode functional accounts still appearing the mode never took effect — reopen the panel and confirm it reads Reference an existing account
Azure Run Command permission denied (AuthorizationFailed on virtualMachines/runCommand/action) the identity the plugin resolved lacks Virtual Machine Contributor on the jump-VM resource group — and that is not always the SP you granted it to. The 403 names the client id and object id it used: check them against the RG's assignments before granting anything (az role assignment list --scope <rg> --include-inherited, filtering on principalId, because a Graph lookup of a sandbox-tenant SP fails for a corp guest identity). A functional-account name without its SP: prefix skips tier 1 entirely, so the id in the error is then the broker's ControlPlane/ambient identity rather than the FA's own SP — granting that one the role papers over an FA whose credentials are never used (§0, §1.3)
Address field 3 or 4 empty azure_subscription_id / azure_tenant_id unset on the Azure panel
4031 / 403 on checkout pscli identity lacks Requestor on the database Smart Rule (§1.6) — also returned when the account is not API-enabled, or when the SystemID does not own the AccountID
Decommission left artifacts managed systems are deregistered before their functional accounts are deleted; a partial failure logs each id it could not remove