IAM Policy Reference
ItdaStream's authorization layer accepts AWS IAM-style JSON policy documents. This page is the reference for writing those documents: the JSON schema, the resource ARN format, every supported action, and the evaluation rules.
For a high-level overview of the IAM subsystem (users, groups, access keys) see AWS IAM-Compatible Access Control.
Policy Document Structure
A policy is a JSON object with a Version and a list of Statement objects.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowProduceToOrders",
"Effect": "Allow",
"Action": ["kafka:Produce", "kafka:Metadata"],
"Resource": "arn:stream:kafka:topic:orders-*"
}
]
}
| Field | Required | Description |
|---|---|---|
Version |
Yes | Policy language version. Use "2012-10-17". |
Statement |
Yes | One or more statements. Each is evaluated independently and the overall decision is combined per the rules below. |
Statement[].Sid |
No | Free-form identifier for the statement. Useful for audit and diff. |
Statement[].Effect |
Yes | "Allow" or "Deny". Case-insensitive. |
Statement[].Action |
Yes | A single action string or a list of action strings. Wildcards (*, ?) supported. |
Statement[].Resource |
Yes | A single ARN/string or list. Wildcards (*, ?) supported. |
Statement[].Condition |
No | Reserved. The field is accepted by the parser but not evaluated in the current release. |
Resource ARN Format
Control-plane (Admin HTTP API) calls authorize against ARN-shaped resource strings:
| Resource type | Example ARN | Guards |
|---|---|---|
topic |
arn:stream:kafka:topic:orders |
Topic-scoped admin operations |
cluster |
arn:stream:kafka:cluster:cluster-1 |
Cluster-wide operations (CreateTopic, DeleteTopic, UpdateTopic, Rebalance, Cleanup) |
iam |
arn:stream:kafka:iam:* |
User/group/policy/password management |
kms |
arn:stream:kafka:kms:* |
KMS key listing, creation, rotation |
The cluster id segment is the cluster.id configured on the broker.
Data-plane resources are bare topic names
The Kafka wire-protocol handlers (Produce, Fetch, CreateTopics, DeleteTopics on the Kafka port) authorize against the bare topic name, not an ARN. This is intentional — the wire protocol carries topic names, and translating to an ARN at request time would force every client to know the cluster id.
| Action | Resource string actually checked |
|---|---|
kafka:Produce |
orders (the topic name) |
kafka:Fetch |
orders |
kafka:CreateTopic (wire protocol) |
orders |
kafka:DeleteTopic (wire protocol) |
orders |
A policy intended to cover both data-plane and control-plane access for a topic must therefore include both forms in Resource, or use a wildcard:
{
"Effect": "Allow",
"Action": ["kafka:Produce", "kafka:Fetch"],
"Resource": ["orders-*", "arn:stream:kafka:topic:orders-*"]
}
Or, the simplest catch-all:
Action Catalog
All actions live under the kafka: namespace. Wildcards work at any depth: kafka:*, kafka:Create*, kafka:?etch.
Data plane (Kafka wire protocol, port 9092)
| Action | Resource form | Enforced at |
|---|---|---|
kafka:Produce |
bare topic name | ProduceHandler |
kafka:Fetch |
bare topic name | FetchHandler |
kafka:CreateTopic |
bare topic name | CreateTopicsHandler |
kafka:DeleteTopic |
bare topic name | DeleteTopicsHandler |
Control plane — Topics (Admin HTTP API, port 8080)
| Action | Resource form |
|---|---|
kafka:CreateTopic |
arn:stream:kafka:cluster:<cluster-id> |
kafka:DeleteTopic |
arn:stream:kafka:cluster:<cluster-id> |
kafka:UpdateTopic |
arn:stream:kafka:cluster:<cluster-id> |
kafka:Rebalance |
arn:stream:kafka:cluster:<cluster-id> |
kafka:Cleanup |
arn:stream:kafka:cluster:<cluster-id> |
Control plane — IAM
| Action | Resource form |
|---|---|
kafka:CreateUser |
arn:stream:kafka:iam:* |
kafka:DeleteUser |
arn:stream:kafka:iam:* |
kafka:UpdatePassword |
arn:stream:kafka:iam:* |
kafka:CreateGroup |
arn:stream:kafka:iam:* |
kafka:DeleteGroup |
arn:stream:kafka:iam:* |
kafka:CreatePolicy |
arn:stream:kafka:iam:* |
kafka:DeletePolicy |
arn:stream:kafka:iam:* |
kafka:UpdateIAM |
arn:stream:kafka:iam:* |
Control plane — KMS
| Action | Resource form |
|---|---|
kafka:ListKms |
arn:stream:kafka:kms:* |
kafka:CreateKey |
arn:stream:kafka:kms:* |
kafka:RotateKey |
arn:stream:kafka:kms:* |
Defined but not yet enforced
These action names appear in policy templates and are reserved for future enforcement. They are not currently checked at request time, so policies using them are accepted but have no runtime effect: kafka:Metadata, kafka:InitProducerId, kafka:JoinGroup, kafka:SyncGroup, kafka:Heartbeat, kafka:LeaveGroup, kafka:OffsetCommit, kafka:OffsetFetch.
Evaluation Rules
For each request, the broker collects every policy attached to every group the user belongs to, then iterates statements:
- A statement applies only if both its
ActionandResourcepatterns match the requested action and resource. - If any matching statement has
Effect: "Deny", the decision is immediately DENY. - Otherwise, if any matching statement has
Effect: "Allow", the decision is ALLOW. - If no statement matches, the decision is ABSTAIN, which the authorizer treats as deny (default-deny).
In short: explicit Deny > explicit Allow > implicit Deny.
Wildcard matching
*matches any sequence of characters (including empty).?matches exactly one character.- Without
*or?, the comparison is a case-sensitive exact match. - Patterns are compiled once per process and cached (up to 10,000 entries).
Defaults
On first startup of a fresh cluster the leader broker seeds:
| Object | Value |
|---|---|
| Policy | AdministratorAccess — Action: "*", Resource: "*" |
| Group | admin-group — bound to AdministratorAccess |
| User | admin (password admin, must change on first login) — member of admin-group |
This is the only way to bootstrap; rotate the password immediately after first login.
Worked Examples
Full producer access to a single topic
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OrdersProducer",
"Effect": "Allow",
"Action": ["kafka:Produce", "kafka:Metadata", "kafka:InitProducerId"],
"Resource": ["orders", "arn:stream:kafka:topic:orders"]
}
]
}
Full consumer access to a topic family
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OrdersConsumer",
"Effect": "Allow",
"Action": [
"kafka:Fetch",
"kafka:Metadata",
"kafka:JoinGroup",
"kafka:SyncGroup",
"kafka:Heartbeat",
"kafka:LeaveGroup",
"kafka:OffsetCommit",
"kafka:OffsetFetch"
],
"Resource": ["orders-*", "arn:stream:kafka:topic:orders-*"]
}
]
}
Read-only admin operator
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOnlyAdmin",
"Effect": "Allow",
"Action": ["kafka:ListKms", "kafka:Metadata"],
"Resource": "*"
},
{
"Sid": "BlockMutations",
"Effect": "Deny",
"Action": [
"kafka:CreateTopic", "kafka:DeleteTopic", "kafka:UpdateTopic",
"kafka:CreateUser", "kafka:DeleteUser", "kafka:UpdatePassword",
"kafka:CreateGroup", "kafka:DeleteGroup",
"kafka:CreatePolicy", "kafka:DeletePolicy", "kafka:UpdateIAM",
"kafka:CreateKey", "kafka:RotateKey",
"kafka:Rebalance", "kafka:Cleanup"
],
"Resource": "*"
}
]
}
Allow producing everywhere, deny one sensitive topic
Because explicit Deny wins, a broad Allow paired with a narrow Deny is the idiomatic way to carve out exceptions.
{
"Version": "2012-10-17",
"Statement": [
{ "Sid": "ProduceAnywhere", "Effect": "Allow", "Action": "kafka:Produce", "Resource": "*" },
{ "Sid": "BlockPii", "Effect": "Deny", "Action": "kafka:Produce", "Resource": ["pii-*", "arn:stream:kafka:topic:pii-*"] }
]
}
Limitations
Conditionis parsed but not evaluated. Authoring conditions today does nothing; the field exists for forward compatibility.- Anonymous requests bypass policy evaluation only when authentication is disabled at the listener level; once SASL is enabled, every request is mapped to a user before authorization runs.
- Resource form is asymmetric between data plane (bare names) and control plane (ARNs). Use both forms in
Resource, or use"*", when a policy spans both surfaces.