Rotating cloud credentials
AWS IAM access keys, RDS IAM tokens, Entra ID service principal secrets, Azure VM local accounts and GCP service-account keys. What each one really does, and which of them has never met a real tenant.
Five engines cover cloud identity: aws_iam_key for an AWS IAM access key, aws_rds_iam for an RDS database authentication token, entra_service_principal for a Microsoft Entra ID service principal secret, azure_vm_local for a local account on an Azure VM, and gcp_service_account_key for a GCP service-account key. They differ from every other engine in this module in an important way — a cloud credential is not a password on a machine, it is a record in a provider's directory, so rotating one means creating a new credential and removing the old one rather than overwriting a value.
Read this article before enabling any of them. Two of the three carry limits you would rather know in advance than discover: one is not a rotation at all, and one has never been run against a real Microsoft tenant.
AWS IAM access keys (`aws_iam_key`)
Prerequisites, on a target whose platform is aws:
- The credential type is
api_token, and the stored secret must be in exactly this shape: the access key id and the secret access key joined by a colon —AKIAEXAMPLE:wJalrXUtn.... Not a JSON object, not two fields, not a profile name. - The account field is the IAM user name, not the key id. This is how Qorionix knows whose key to create.
metadata["region"]— defaults tous-east-1if absent. Set it explicitly.- IAM permissions
iam:CreateAccessKeyandiam:DeleteAccessKeyon the user being rotated. Self-service is fine and is the usual arrangement. - Outbound network access to
https://iam.amazonaws.com/. The engine signs its own requests rather than using the AWS SDK, so there is no shared credential file or instance profile involved — everything comes from the vault entry.
How the rotation runs:
Create a second access key, signed with the current one
AWS returns a new key id and secret. At this instant the IAM user has two working keys.
Delete the old key, signed with the new one
Signing the delete with the new key is the clever part: if the new key were not working, the delete would fail. So a completed rotation is implicit proof that the new key is valid — the credential was used successfully before the old one was destroyed.
Store the new key pair in the vault
In the same colon-joined shape.
That implicit verification is genuinely worth something, and it is not reflected in the interface: the rotation history records this credential as unverified, because verification is tracked by membership in a list of platforms that can be pre-checked by signing in, and AWS is not on that list. So the credential will tell you it could not be confirmed when in practice the successful delete confirmed it. Cosmetic, and named here so you do not chase it.
aws_iam_key: account (IAM user) required
aws_iam_key: credential must be <key_id>:<secret>
create access key: %w
aws_iam_key: could not parse new key from response
aws_iam_key: rotation succeeded but old key cleanup failed: %w
aws http %d: %saws http 403: with a message about the operation usually means the IAM policy lacks iam:CreateAccessKey or iam:DeleteAccessKey. aws http 400: with LimitExceeded is the two-key limit above.
One coverage note in the interest of accuracy: this engine has no automated tests. It is straightforward code against a well-documented API, and the implicit verification through the delete step is a genuine safety property — but the first rotation of any IAM user is worth watching in the AWS console rather than trusting on our word.
As for what breaks: everything that has the old key configured. An access key is used by scripts, CI pipelines, third-party services, developer laptops and infrastructure tooling, none of which Qorionix can see. The failure is immediate rather than delayed — unlike a database pool, an API client re-authenticates on every request — so a rotation of a key that something depends on breaks that thing within seconds. Establish what uses a key before rotating it.
RDS IAM tokens (`aws_rds_iam`) — not a rotation
This one needs its own framing, because calling it rotation is misleading and the code itself says so.
AWS lets you connect to an RDS database using an IAM identity instead of a database password. The way that works is that the client generates a short-lived token, signed with its AWS credentials, and presents that token where the password would go. The token is not a secret AWS stores — it is a signature the client computes locally, and the database validates it.
So this engine does not change anything anywhere. It computes a fresh token and puts it in the vault. There is no network call at all — not to AWS, not to the database. It is a re-issue rather than a rotation, and three consequences follow:
- The token expires in 900 seconds. Fifteen minutes. Against a rotation interval whose minimum sensible unit is a day, this means the value in the vault is essentially always expired.
- Nothing is verified, because nothing was changed. There is no meaningful notion of verification here.
- It is not selectable from the console. No platform in the catalogue derives this engine, so it can only be reached by writing the adapter name directly to the rotation schedules API.
Prerequisites, if you use it anyway: the stored credential must be <key_id>:<secret>, metadata["region"] is required with no default, and metadata["port"] defaults to 5432. The two verbatim errors:
aws_rds_iam: credential must be <key_id>:<secret>
aws_rds_iam: metadata.region requiredOur honest advice is not to schedule this. RDS IAM authentication is a good pattern, and the right place to generate the token is in the client that is about to connect, fifteen minutes before it needs it. A vault entry refreshed once a day contains a token that was valid for the first quarter of an hour of that day. If you want RDS IAM authentication, configure it in your application; if you want a rotating database password, use postgres_role or sqlserver_login.
Entra ID service principals (`entra_service_principal`)
A service principal secret is the password an application uses to sign in to Microsoft Entra ID — the client secret on an app registration. They are typically created once with a long expiry and forgotten, which makes them an excellent rotation candidate in principle.
What that means for you today. If you want Entra service principal rotation, you can have it, on these terms: treat the first rotation as a supervised experiment. Pick an app registration you can afford to break — ideally one you create for the purpose. Rotate it manually with the Rotate now action rather than waiting for a schedule. Then go into the Entra portal and look at the app registration's client secrets with your own eyes: you should see a new secret whose description begins zentryq-pam/, and the previous Qorionix-created one gone. Then confirm the application can still authenticate. Only after that should you point it at anything real, and even then, tell us — a customer running this against a live tenant is the missing evidence, and we would rather hear about it from you early than have you find out alone.
In the meantime, the safe interim position is to store service principal secrets in the vault with rotation off. You still get one encrypted place, access control and an audit trail, and you still get the expiry visible in Entra. Rotate them by hand in the portal on whatever cadence you can sustain.
Prerequisites, if you proceed
metadata["tenant_id"]— required, no default.metadata["object_id"]— required, and this is the field people get wrong. It is the application's object id, not its application (client) id. They are both GUIDs, they sit next to each other on the overview page in the Entra portal, and supplying the wrong one produces a Graph error rather than a clear rejection.- Graph permission —
Application.ReadWrite.OwnedByif the application is rotating its own secret, orApplication.ReadWrite.Allif you are using a separate rotator identity. - Optional
metadata["lifetime_days"]— how long the new secret is valid for. Defaults to 180. - Optional
metadata["resource"]—applicationby default, orservicePrincipal. - Optional
metadata["rotator_client_id"]and["rotator_client_secret"]— a separate identity that performs the rotation, instead of the application rotating itself. Note that the rotator secret is stored in target metadata, which is not encrypted and is returned by the API; prefer self-rotation where the application can holdApplication.ReadWrite.OwnedBy.
The rotation adds a new password to the app registration through Graph, labelled with the description zentryq-pam/ followed by a UTC timestamp, then lists the application's existing secrets and removes every other one carrying that same zentryq-pam/ prefix. Secrets Qorionix did not create are never touched. That prefix rule is the whole safety design of this engine: an app registration frequently has secrets belonging to other tools and other people, and a rotation that tidied up everything it found would be a small disaster.
entra_service_principal: metadata.tenant_id is required
entra_service_principal: metadata.object_id is required (the application's object
id, not its appId)
entra_service_principal: no credential to authenticate with (set
target.Account/Credential for self-rotation, or
metadata.rotator_client_id/rotator_client_secret)
entra_service_principal: Graph addPassword returned no secretText
entra_service_principal: new secret %s created but cleanup incomplete: %s
entra_service_principal token http %d: %s
entra_service_principal: token response carried no access_token
graph http %d: %sSovereign and national clouds are supported by pointing the engine at different authority and Graph endpoints, so a deployment in a non-global Microsoft cloud is a configuration question rather than an impossibility. Ask us if you need it — it is not exposed in the console.
Azure VM local accounts (`azure_vm_local`)
This engine changes a local account's password on an Azure virtual machine through Azure Resource Manager's RunCommand facility rather than dialling the VM directly — which means it works even for a VM with no public IP and no inbound network security group rule open to it. It is delegated rotation only: an app registration holding Microsoft.Compute/virtualMachines/runCommand/action performs the change on the VM's behalf. There is no self-rotation mode, because RunCommand is inherently an external-actor operation.
The engine handles both of RunCommand's reply shapes: a synchronous 200 for a fast VM, and the asynchronous 202-plus-poll shape for a slower one.
GCP service-account keys (`gcp_service_account_key`)
The GCP equivalent of the AWS IAM access-key engine, and it follows the same create-then-delete shape: a new key is created, the old one is deleted, and the vault entry is updated to the new pair.
Where it differs is how it can prove itself. It can self-rotate — no separate delegate identity is needed — by signing a self-issued JWT (an RFC 7523 JWT-bearer grant) with the vaulted key's own RSA private key, provided the service account holds roles/iam.serviceAccountKeyAdmin on itself. A successful sign using the new key is, like the AWS engine's delete step, an implicit proof that the new key works.
Where this leaves you
| Engine | State | What we suggest today |
|---|---|---|
aws_iam_key | Wired and selectable. No automated tests. Verifies implicitly via the delete step, reported as unverified. | Usable. Confirm the IAM user has one key before you start, and act on any cleanup-failed error immediately. |
aws_rds_iam | Functional, but a token re-issue rather than a rotation. Not selectable from the console. Token expires in 15 minutes. | Do not schedule it. Generate RDS IAM tokens in the client that connects. |
entra_service_principal | Wired and selectable. Mock-tested only; no real tenant has ever been rotated. No post-change verification. | Store the secrets in the vault with rotation off, or run one supervised experiment on a disposable app registration and tell us how it went. |
azure_vm_local | Wired and selectable. Delegated rotation only, via Resource Manager RunCommand. | Usable. Grant the delegate app registration the minimum RunCommand role and nothing broader. |
gcp_service_account_key | Wired and selectable. Can self-rotate and verifies via a real signed request with the new key. | Usable. Grant the service account roles/iam.serviceAccountKeyAdmin on itself if you want self-rotation. |
What to do next
- Read the rotation overview — particularly the section on rotations that lose the new secret, which is where two of these three engines appear.
- Rotate a database or a Linux account first so your first experience of rotation is with an engine that proves its own work.
- Look at just-in-time elevation if what you actually want is for cloud privilege to exist only while it is being used, rather than for a long-lived key to be rotated.