Keyless Credentials (S3 AssumeRole & RDS IAM)
Clonit’s default credential models store a static secret: storage profiles hold encrypted S3/R2 access keys, and targets hold a database password (see Database Credentials for the full spectrum of password handling, including the cloud sync secret modes). Two keyless modes remove the stored secret entirely.
Local storage profiles (the ones this machine pushes and pulls with directly) can also go keyless: instead of stored keys, a profile can ride the agent’s own AWS credential chain — see Local storage profiles below.
The single most important thing: the two keyless modes use two different AWS principals. Keyless storage uses clonit-cloud’s identity; keyless database auth uses the agent’s identity. A customer told “one role covers both” will mis-scope their setup. See Two principals below.
Keyless storage (S3 cross-account AssumeRole)
Section titled “Keyless storage (S3 cross-account AssumeRole)”A storage profile gains an auth_mode: static (today — encrypted access/secret
keys) or assume_role (S3 only). With assume_role the cloud stores a role ARN
and a server-minted ExternalId — no keys. At presign time the cloud broker
sts:AssumeRoles into your role and signs the URL with the resulting temporary,
session-bearing credentials.
auth_mode=assume_role → cloud stores: role_arn + (secret) ExternalId, NO keys presign: broker sts:AssumeRole → temp creds → presigned URLOnboarding (one-time)
Section titled “Onboarding (one-time)”-
Create the profile in keyless mode. From the CLI:
Terminal window clonit cloud storage-profiles add \--name team-keyless \--provider s3 \--bucket my-snapshots \--region us-east-1 \--auth-mode assume_role \--role-arn arn:aws:iam::123456789012:role/clonit-accessOr from the web UI (
clonit serve): Storage Profiles → Add Cloud Profile, then set Credentials to Keyless — AssumeRole (S3 only) and fill in the Role ARN. Either way, the cloud server-mints a unique ExternalId, encrypts it at rest, and never returns it in any ordinary API response. -
Verify — run
clonit cloud storage-profiles verify team-keyless. It performs a throwawaysts:AssumeRoleto report whether the grant works, and prints the onboarding artifact: the cloud’s principal ARN, your ExternalId (surfaced only by verify), and a copy-pasteable trust policy and permission policy with both values interpolated. In the web UI the onboarding dialog opens automatically after saving a keyless profile, runs Verify for you, and shows the same artifact with copy buttons. The first verify is expected to fail — your role doesn’t trust the cloud yet. -
Attach the policies to your role, then re-run verify (CLI) or click Re-verify (UI):
// Trust policy (who may assume the role){ "Effect": "Allow", "Principal": { "AWS": "<CLONIT_CLOUD_PRINCIPAL_ARN>" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "<EXTERNAL_ID>" } } }
// Permission policy (what the role may do) — bucket/prefix-scoped// s3:GetObject, s3:PutObject, s3:DeleteObject on arn:aws:s3:::<bucket>/<prefix>/*// (s3:DeleteObject is used by the cloud retention sweep; roles created before// it was part of onboarding degrade gracefully — deletes are audited + skipped// until you add the grant)The CLI and UI sit on top of the same REST surface —
POST /storage-profiles/{id}/verify runs the check and returns the artifact if
you are scripting against the API directly.
R2 has no keyless mode
Section titled “R2 has no keyless mode”Cloudflare R2 has no cross-account role grant. The recommended R2 posture is a
bucket-scoped, revocable R2 API token used AS your static access/secret (not
account Super-Admin keys). There is no assume_role for R2; auth_mode is exactly
{static, assume_role}.
Updating / rotating
Section titled “Updating / rotating”clonit cloud storage-profiles update <name> (or the web
UI’s edit form) changes auth_mode/role_arn and rotates keys or the
ExternalId in place — the profile id stays stable so snapshots that
reference it are never orphaned. Switching to assume_role clears any stored
keys and mints a new ExternalId (re-run verify and re-attach the trust
policy); switching back to static clears the role ARN and ExternalId, and
keys must be re-supplied. The underlying API is PUT /storage-profiles/{id}.
Deploy prerequisite (ops)
Section titled “Deploy prerequisite (ops)”assume_role requires clonit-cloud to run under a stable, non-assumed IAM identity
(set aws.region and, ideally, aws.principal_arn so the published ARN is frozen).
Run the broker directly under that identity — if the broker is itself an
assumed role, STS role-chaining caps the session at 1h, and any presign_expiry
at/above that fails loudly.
The broker requests an assumed session of presign_expiry + 15m (so the signing
credentials outlive every presigned URL and the cached session can refresh before it
ages out). The customer role’s MaxSessionDuration must therefore exceed
presign_expiry — for the default 1h presign_expiry, raise the customer role’s
MaxSessionDuration above 1h (the AWS default is exactly 1h). If the session a role
returns is shorter than presign_expiry, the presign fails loudly rather than
mint a URL that 403s after the session expires — lower presign_expiry or raise the
role’s MaxSessionDuration.
Local storage profiles: three credential sources
Section titled “Local storage profiles: three credential sources”The AssumeRole mode above covers cloud (org-level) storage profiles. Local storage profiles — the ones this machine uses directly for push / pull — have their own credential choice. The storage profile form’s Authentication section offers a Credential Source selector:
| Credential Source | What is stored | Best for |
|---|---|---|
| Access keys (stored encrypted) | Access/secret key, encrypted at rest | Providers that issue their own tokens (R2), or machines with no AWS identity |
| Shared AWS profile (~/.aws) | Only the profile name | Machines with ~/.aws config, including SSO-backed profiles |
| Agent credential chain (keyless) | Nothing | AWS environments — instance/task roles, environment credentials, SSO |
With the agent credential chain, the profile stores no credential at all: at push/pull time the agent resolves whatever AWS identity its environment already provides — the same identity, resolved in the same order, as keyless database auth below. Check Settings → AWS to see which identity that is and whether it authenticates.
Provider caveats:
- MinIO needs keys or a shared profile — the keyless option is not offered (a self-hosted gateway has no ambient AWS identity to resolve against).
- R2 usually needs its own bucket-scoped API tokens as stored keys (see R2 has no keyless mode).
- Azure always uses stored account keys.
- GCS goes keyless its own way: leave the service-account JSON empty and it falls back to Application Default Credentials.
Organizations that never issue static keys at all can pair keyless local profiles (agent side) with the cloud’s AssumeRole mode (broker side): role-based with an ExternalId, no keys stored in either place.
Keyless database (RDS IAM auth)
Section titled “Keyless database (RDS IAM auth)”A target gains a db_auth_mode: password (the default password handling) or
rds_iam (RDS / Aurora — PostgreSQL/MySQL/MariaDB). With rds_iam the target
stores no password. The agent mints a short-lived (~15-min) SigV4 token locally
and uses it as the DB password over verified TLS.
db_auth_mode=rds_iam → stored secret: NONE agent mints rds-db:connect token per connection (fresh each time)How keyless database access works (plain English)
Section titled “How keyless database access works (plain English)”Instead of a stored password, the agent asks AWS to sign a one-time pass: a token valid for about 15 minutes, for one database user on one database server. RDS checks the signature and lets the connection in. The token is minted fresh for every connection and never written anywhere — not to disk, not to the config, not to the cloud.
Three things make it work:
- The grant — the database user is flagged for IAM authentication (one SQL statement, shown below and in the guided setup wizard).
- The permission — the agent’s AWS identity is allowed to request passes
for that user, via a narrowly scoped
rds-db:connectpolicy. - The certificate bundle — the connection is encrypted and verified against the real server, so a pass can’t be captured in transit.
Where does the agent’s AWS identity come from? Wherever the environment already has one, checked in order: environment variables, a shared AWS profile (including SSO), a container/task role, an EC2 instance role. Clonit stores none of these — it borrows whatever identity the environment already has. The Settings → AWS page (its AWS credentials card) and the guided setup wizard both show which identity that currently is.
Why security teams tend to be OK with it:
- There is nothing long-lived to leak or rotate — no password, no stored key.
- Passes expire in minutes, so a captured token has almost no shelf life.
- Permission is scoped to one database user on one server, not an account-wide grant.
- Access is revoked centrally in IAM — detach the policy and no more passes are issued.
- Connections refuse to run over unverified TLS, so a pass is never sent to an impostor server.
Choosing which AWS identity the agent uses
Section titled “Choosing which AWS identity the agent uses”Most setups never pick a profile — the agent just borrows whatever identity
the environment provides. When you do need control (say, a laptop with
several ~/.aws profiles, or one target that must sign with a different
account), the agent resolves the identity for keyless database auth in this
order, first match wins:
- The target’s AWS Profile field — set in the target form (keyless
database mode) or with
--aws-profileontargets add/targets update. Per-target, beats everything else. - The agent config
aws.profile— an agent-wide shared profile (see Configuration). - The environment —
AWS_PROFILEor environment access keys, including values from a gitignored.env.localfile, which the agent loads into its environment at startup. - The shared-config default profile (
~/.aws). - An instance or task role (EC2/ECS).
Keyless storage mirrors the same ladder from the storage profile’s side:
the profile’s stored access keys > the profile’s AWS CLI profile > the agent
config aws.profile > the ambient chain (steps 3–5 above).
Settings → AWS is where you view and set the agent-wide profile: the
input previews what any named ~/.aws profile resolves to, and Save
stores it as the agent’s aws.profile — effective immediately, no restart.
New keyless targets start from the saved value (each target keeps its own
copy you can edit or clear), while the guided setup wizard shows the profile
in effect for the specific target you are setting up.
Security posture at a glance
Section titled “Security posture at a glance”| Concern | Posture |
|---|---|
| No long-lived secrets | No database password is stored — anywhere. Each connection uses a freshly minted, ~15-minute token. |
| No static AWS keys required | In AWS, the agent uses its instance or task role. Elsewhere, SSO or a shared AWS profile works — clonit itself never requires access keys. |
| Key rotation | A non-event: clonit stores nothing, so there is nothing to rotate on the clonit side. |
| Least privilege | The policy the guided setup produces is scoped to a single database user on a single instance. |
| No write access needed to your AWS account | The guided setup only reads (describe calls) to fill in details; every actual change is applied by your own admins. |
| Transport security | TLS is fail-closed with a pinned certificate bundle — no bundle, no connection, never a silent downgrade. |
Configuring an rds_iam target
Section titled “Configuring an rds_iam target”Set the auth mode at create or update time. --db-region is optional — it is
derived from an RDS/Aurora hostname when omitted, and only needed for non-RDS
hostnames or custom DNS:
clonit target add --name prod \ --src-url 'postgres://appuser@db.abc123.us-east-1.rds.amazonaws.com:5432/app' \ --db-auth-mode rds_iam # --db-region us-east-1 (optional)
clonit target update prod --db-auth-mode rds_iam # migrate an existing targetThe src-url/dst-url carry no password for rds_iam (the agent mints the
token as the password at connect time); any password present is ignored.
One-time DB-side setup (customer)
Section titled “One-time DB-side setup (customer)”- PostgreSQL:
GRANT rds_iam TO <dbuser>; - MySQL/MariaDB:
CREATE USER '<dbuser>'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';(for an existing user,ALTER USER '<dbuser>'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';) - Destination users (destination / sanitize destination URLs) also need
create-database rights — clonit drops and recreates those databases on every
load, so they don’t need to exist up front. PostgreSQL:
ALTER ROLE <dbuser> CREATEDB;(orCREATE USER <dbuser> CREATEDB;). MySQL/MariaDB:GRANT ALL ON <db>.* TO '<dbuser>'@'%';works even before the schema exists. - IAM policy on the agent’s role:
rds-db:connecton the DbiResourceId ARN (arn:aws:rds-db:<region>:<acct>:dbuser:<DbiResourceId>/<dbuser>) — thedb-XXXX/cluster resource id, not the human DB name.
The agent needs an AWS credential chain
Section titled “The agent needs an AWS credential chain”rds_iam works in AWS (instance/task role → zero stored secrets) and outside AWS
(env keys / shared profile to sign the token — still no DB password stored). If no
AWS credential chain is found, the agent fails with a distinct, actionable error
(not a confusing blank-password DB failure).
Fail-closed TLS
Section titled “Fail-closed TLS”A bearer token over an unverified TLS session is a credential-capture path, so
rds_iam defaults to certificate-verifying TLS: sslmode=verify-full (PG) /
--ssl-mode=VERIFY_IDENTITY (MySQL) with the RDS CA bundle. The setup
wizard’s CA-bundle step downloads the AWS global bundle to
<config dir>/rds-global-bundle.pem with one click (takes effect immediately,
no restart), or set rds_ca_bundle_path to a bundle you provision yourself.
With no bundle on disk, the connection fails closed rather than downgrade —
clonit will not send a token over unverified TLS.
Caveats and operational notes
Section titled “Caveats and operational notes”Honest fine print:
- Some AWS identity must exist where the agent runs. Keyless doesn’t mean identity-free — in AWS this is the instance/task role; on laptops, prefer an SSO session over long-lived access keys.
- Where logins are audited: IAM database logins appear in the database engine logs, not as CloudTrail API events. (The guided setup’s lookup calls are CloudTrail-visible.)
- Connection-rate cap: AWS limits IAM-auth connections to roughly 200 per second per instance — irrelevant for snapshot workloads, which open a handful of connections per run.
- Static keys and rotation: if the agent runs with static environment keys, rotating them requires an agent restart to pick up the new values; roles and SSO sessions refresh automatically.
Two distinct AWS principals
Section titled “Two distinct AWS principals”| Storage (S3 AssumeRole) | Database (RDS IAM) | |
|---|---|---|
| Whose AWS identity | clonit-cloud’s stable principal | the agent’s identity |
| Where it runs | the cloud broker | the agent (clonit) |
| Customer grants | a role trusting the cloud principal + ExternalId | rds-db:connect on the agent’s role to the DbiResourceId |
| clonit stores | role ARN + (secret) ExternalId; NO keys | NOTHING |
The agent never holds the assumed storage role (storage brokering runs only in the cloud), and the cloud never touches your databases (database auth runs only in the agent). The “run under one IAM role, store no secrets” pitch only applies to a standalone, self-brokering agent (which does its own S3 and its own RDS under one instance role).
See also
Section titled “See also”- Service-User CI (cak_ keys) — the CI credential model.
- Database Credentials — every way Clonit handles a database password, and when to use which.
- cloud storage-profiles — list, add, update, and verify cloud storage profiles from the CLI.
- Configuration — agent
aws.profileandrds_ca_bundle_path;aws.*broker settings live in the cloud deploy config.