- Security & Policies
- Limit an agent key to specific functions
Limit an agent key to specific functions
An agent key often needs to call two or three functions, not all of them. Layer 1 RBAC grants functions:invoke for every function. You narrow it with a deny rule in your policy set. This guide shows the full flow.
Step 1. Create the key
Section titled “Step 1. Create the key”Find the ID of the role you want the key to hold:
ironflow role listThen create the key with that role ID:
ironflow apikey create agent-bot \ --role <role-id> \ --tool-scope billing-tools--tool-scope is repeatable. It sets the agent tool scopes of the key. The agent tool service checks them. They do not limit which functions the key can invoke. The policy in the next step does that. You can grant only scopes that you hold yourself, unless you are an admin.
Save the key value. Note the key ID (ak_...) in the output.
Step 2. Write the deny rule
Section titled “Step 2. Write the deny rule”InvokeFunction and InvokeFunctionSync check functions:invoke twice:
- The route gate runs first. It sees only the RPC path, so the resource is an empty string.
- The handler runs next. It checks the real function IRN, for example
irn:ironflow:org_acme:proj_default:function:env_default:fn-a.
A rule that says only “not fn-a” is true for the empty resource. The route gate then denies fn-a too. Guard the rule with input.request.resource != "":
package ironflow.authz
# The pre-route gate sees an Invoke RPC with an empty resource (the function id# is in the body); the handler asks again with the real function IRN. The# resource != "" guard lets the gate pass and leaves the decision to the handler.deny contains "agent key may only invoke fn-a" if { input.request.action == "functions:invoke" input.subject.api_key_id == "ak_agent_bot" input.request.resource != "" not endswith(input.request.resource, ":fn-a")}Replace ak_agent_bot with the ID of your key. Save the rule as agent-bot.rego in your policy directory.
At the route gate the guard makes the rule not fire, so the request passes. The handler check then decides.
To allow more than one function, test each name with endswith and use not on the group. One way is a set of allowed suffixes:
package ironflow.authz
allowed_suffixes := {":fn-a", ":fn-b"}
deny contains "agent key may only invoke fn-a and fn-b" if { input.request.action == "functions:invoke" input.subject.api_key_id == "ak_agent_bot" input.request.resource != "" not any_suffix_matches}
any_suffix_matches if { some suffix in allowed_suffixes endswith(input.request.resource, suffix)}Ironflow encodes a raw function ID once: literal % becomes %25, then : becomes %3A. A function team:fn-a is ...:function:env_default:team%3Afn-a. The literal ID team%3Afn-a instead ends in team%253Afn-a. Write the encoded identifier in your suffixes and dry-run inputs. Do not percent-decode input.request.resource. For existing rules containing literal % identifiers, follow Update existing policies when upgrading.
Step 3. Dry-run it
Section titled “Step 3. Dry-run it”Run the dry-run before you push. Check that fn-a is allowed and fn-b is denied. Also check the empty resource, which is what the route gate sees. Write one input file for each case. This is the file for fn-b, named fn-b.json:
{ "request": { "action": "functions:invoke", "resource": "irn:ironflow:org_acme:proj_default:function:env_default:fn-b", "environment": "env_default", "org_id": "org_acme" }, "subject": { "api_key_id": "ak_agent_bot", "roles": ["developer"] }}Make fn-a.json the same with fn-a at the end of the resource, and gate.json with "resource": "". Then:
# fn-b: expect decision denyironflow policy dry-run ./policies --input fn-b.json
# fn-a: expect decision allowironflow policy dry-run ./policies --input fn-a.json
# Route gate: expect decision allow (the guard lets it through)ironflow policy dry-run ./policies --input gate.jsonStep 4. Push
Section titled “Step 4. Push”ironflow policy push ./policiesA new set starts in monitor mode. Read the would_deny rows for a while to check that only the key you meant is affected, then switch: ironflow policy push ./policies --mode enforce. See Roll out with monitor mode.
Each denied call is written to the auth audit log with the function IRN and the reason agent key may only invoke fn-a.
What this does not cover
Section titled “What this does not cover”The rule limits InvokeFunction and InvokeFunctionSync only. The key can still reach other functions by other routes:
- Events.
Emit,Trigger,TriggerSync,TriggerBatchand webhooks useevents:emit. An event can start any function that has a matching trigger. - Run control.
ResumeRun,PatchStep,InjectStepOutputandPauseRunuseruns:cancel. They act on any run. - Outbox. Outbox requeue uses
outbox:manage. - Worker routes. Worker routes check
functions:invokewith no function in the resource, so this rule does not match them. - Agent tools.
agent:tools:invokeis a separate check.
Add a second rule for the same key on the actions it does not need:
package ironflow.authz
deny contains "agent key may not use this action" if { input.subject.api_key_id == "ak_agent_bot" input.request.action in {"events:emit", "runs:cancel", "outbox:manage"}}Or give the key a role that does not hold these actions.