Broker a short-lived cloud role
Hold a lease on a role, assume it through the plane for a short credential, use it and release the lease.
iam.assumecoordination.lease
Hold a lease on a role, assume it through the plane for a short credential, use it and release the lease.
iam.assumecoordination.leasePut admission in front of your MCP servers, repositories, SaaS tools and cloud accounts. Every agent gets a lease or a reason – and you keep the lever to stop any of them.
Agents should hold no standing cloud keys. The broker checks the role against protected classes, requires a live lease on the exact role ARN held by the agent, then asks STS for a session. Issuance is ledgered with the actor, the role and the expiry, never the keys.
The answer carries the secret access key and the session token once, and the ledger never holds them. Releasing the lease does not revoke a credential already issued: it lives until expirationMs. STS refuses sessions under 900 seconds.
arn:aws:iam::The code reads the key from INTRAMEDIAL_KEY. Keep it in your shell or your secret store, never in a file you commit. Calls that need a signed-in person cannot use a key: the console makes them.
Take a lease on the exact role ARN, as the agent that will use it. The credential's lifetime is capped by the lease's remaining time.
assumed is true; the lease and expiry follow.
{
"assumed": true,
"roleArn": "arn:aws:iam::123456789012:role/deploy",
"sessionName": "release-agent",
"leaseId": "lease_4b7e",
"leaseScoped": true,
"expirationMs": ,
: ,
: ,
:
}expirationMs.iam.assumeiam-assumecoordination.leasecoordination-lease
curl -sS -X POST https://api.intramedial.com/api/v1/acp/coordination/lease \
-H "Authorization: Bearer $INTRAMEDIAL_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "arn:aws:iam::123456789012:role/deploy",
"actorDid": "did:web:acme.example:agent:release"
}'const res = await fetch("https://api.intramedial.com/api/v1/acp/coordination/lease", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INTRAMEDIAL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
resource: "arn:aws:iam::123456789012:role/deploy",
actorDid: "did:web:acme.example:agent:release",
}),
});
console.log(res.status, await res.json());import os, requests
res = requests.post(
"https://api.intramedial.com/api/v1/acp/coordination/lease",
headers={"Authorization": "Bearer " + os.environ["INTRAMEDIAL_KEY"]},
json={
"resource": "arn:aws:iam::123456789012:role/deploy",
"actorDid": "did:web:acme.example:agent:release",
},
timeout=30,
)
print(res.status_code, res.json())Send the role, the agent, a session name and the web identity token. The answer carries the secret once, so save it where logs cannot see it.
curl -sS -X POST https://api.intramedial.com/api/v1/acp/iam/assume \
-H "Authorization: Bearer $INTRAMEDIAL_KEY" \
-H "Content-Type: application/json" \
-d '{
"roleArn": "arn:aws:iam::123456789012:role/deploy",
"actorDid": "did:web:acme.example:agent:release",
"sessionName": "release-agent",
"webIdentityToken": "eyJ…"
}'
# Save the answer to assume.json: it holds the secret once, so keep it out of logs.const res = await fetch("https://api.intramedial.com/api/v1/acp/iam/assume", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INTRAMEDIAL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
roleArn: "arn:aws:iam::123456789012:role/deploy",
actorDid: "did:web:acme.example:agent:release",
sessionName: "release-agent",
webIdentityToken: "eyJ…",
}),
});
console.log(res.status, await res.json());import os, requests
res = requests.post(
"https://api.intramedial.com/api/v1/acp/iam/assume",
headers={"Authorization": "Bearer " + os.environ["INTRAMEDIAL_KEY"]},
json={
"roleArn": "arn:aws:iam::123456789012:role/deploy",
"actorDid": "did:web:acme.example:agent:release",
"sessionName": "release-agent",
"webIdentityToken": "eyJ…",
},
timeout=30,
)
print(res.status_code, res.json())Export the keys for the one job that needs them. They live until expirationMs, the expiry STS reports for the session.
export AWS_ACCESS_KEY_ID=$(jq -r .accessKeyId assume.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r .secretAccessKey assume.json)
export AWS_SESSION_TOKEN=$(jq -r .sessionToken assume.json)
# jq -r .expirationMs assume.json when STS says the credential endsRelease the lease when the job ends. That stops the next credential from being issued. It does not recall the ones already issued, which live until expirationMs. To watch for bursts of requests, watchdog.check reads the iam.assume events.
curl -sS -X POST https://api.intramedial.com/api/v1/acp/coordination/release \
-H "Authorization: Bearer $INTRAMEDIAL_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "arn:aws:iam::123456789012:role/deploy",
"actorDid": "did:web:acme.example:agent:release"
}'const res = await fetch("https://api.intramedial.com/api/v1/acp/coordination/release", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INTRAMEDIAL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
resource: "arn:aws:iam::123456789012:role/deploy",
actorDid: "did:web:acme.example:agent:release",
}),
});
console.log(res.status, await res.json());import os, requests
res = requests.post(
"https://api.intramedial.com/api/v1/acp/coordination/release",
headers={"Authorization": "Bearer " + os.environ["INTRAMEDIAL_KEY"]},
json={
"resource": "arn:aws:iam::123456789012:role/deploy",
"actorDid": "did:web:acme.example:agent:release",
},
timeout=30,
)
print(res.status_code, res.json())Copied from: /docs/iam-assume
expirationMs.The ledger is append-only and sealed under Merkle roots. The time on an entry is Intramedial's own clock, and a signed bundle can be checked offline with the published key.
Take an exclusive lease on a resource with coordination.lease, see who holds it, and let it go when you are done.
coordination.leaseRead the ledger for bursts, spikes and silence in lease and credential events, and run the check from your own scheduler.
watchdog.checkAsk the plane whether an agent may act on a connected SaaS source, and read the verdict before anything is sent.
saas.admitOne short email when we add recipes to the cookbook. No other mail, and you can unsubscribe from any message.
No key or account: Try it on the coordination.admit module page runs the real kernel in your browser. Use a workspace key for your own workspace.