IAM Policy Templates
Ontul's Admin UI policy editor ships with a set of ready-to-use policy templates. Each is a complete policy document you can attach to a user or group as-is, or use as a starting point. This page documents every one of them, explains when to use it, and documents the action/resource namespaces the policies are written against.
For the underlying concepts (users, groups, attachment, column masking, row filters, evaluation order), see Identity and Access Management.
Policy document shape
Every policy is a JSON document with a Version and a list of Statements. Each statement has an Effect (Allow / Deny), one or more Actions, and one or more Resources; optional Columns and Condition add column- and row-level control.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "ReadOnlyAccess",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:*.*.*"
}
]
}
Evaluation order is Deny > Mask > Allow — an explicit Deny always wins, then column masks / row filters apply, then Allow grants. The same policy store governs every access path (Arrow Flight SQL, REST, MCP), so one statement covers all clients.
Action namespace
| Action | Meaning |
|---|---|
data:Select |
Read rows — gates an actual scan |
data:SelectTable |
Metadata-only read — gates discovery: listing or describing a semantic view, a retriever, a catalog table. Distinct from data:Select, and what a retriever invoke is authorized under |
data:Insert / data:Update / data:Delete / data:Merge |
Write / row-level DML |
data:CreateTable / data:DropTable / data:AlterTable |
DDL |
data:KillJob / data:CancelQuery |
Job / query control |
UDF:EXECUTE / UDF:CREATE / UDF:DROP |
User-defined function use / authoring |
UDF:CREATE_GLOBAL / UDF:DROP_GLOBAL |
Global (cluster-wide) UDF authoring |
ontology:Certify |
Bless an ontology subtree as CERTIFIED — an ownership decision, deliberately separate from the admin rights needed to edit the ontology. See Ontology |
ontology:InvokeAction / ontology:InvokeOperation / ontology:InvokeWorkflow |
Run a governed write-back: a single action, its external operation, or a saga workflow |
data:Write |
Audit-log label only, never authorized. The audit trail records a non-read statement under this name, but authorization emits data:Select / Insert / Update / Delete — so a Deny on data:Write can never fire. Deny the three DML verbs instead |
AI:DECIDE |
Evaluate ai_verdict() / ai_verdict_one() against one DECIDE connection. Granted per endpoint, because these are the only SQL functions that spend money per row and send column text off-site |
* |
Everything (administrator) |
Resource namespace
| Resource | Pattern | Example |
|---|---|---|
| Table | data:table:<catalog>.<schema>.<table> |
data:table:ice.*.* |
| Schema | data:schema:<catalog>.<schema> |
data:schema:ice.team_a |
| Job / query | data:job:<id> / data:query:<id> |
data:job:* |
| UDF | udf:<name> |
udf:mask_* |
| Semantic view / retriever | <catalog>.<schema>.<name> — the object's own fqn, not a data:table: prefix |
semantic.rag.* |
| Ontology object / action | ontology.<schema>.<name> |
ontology.sales.* |
| Decision endpoint | connection:<connectionId> |
connection:jev |
| Everything | * |
* |
Wildcards (*) may appear in any segment. These strings line up 1:1 with what the query engine emits at runtime, so a template's Resource matches exactly what an authorization check produces.
Broad access
AdministratorAccess
Full access to everything. Reserved — it cannot be deleted.
Superuser
Same effect as AdministratorAccess, but a normal (deletable) policy — use it when you want an admin-equivalent grant you can later revoke.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "SuperuserAccess",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
Read-Only Analyst
Read any table in any catalog — the typical BI / analyst grant.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "ReadOnlyAccess",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:*.*.*"
}
]
}
App Read-Write
Full DML (no DDL) across all tables — for application service accounts that read and write but never change schema.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "AppReadWrite",
"Effect": "Allow",
"Action": ["data:Select", "data:Insert", "data:Update", "data:Delete"],
"Resource": "data:table:*.*.*"
}
]
}
Schema Admin (DDL + DML)
DML plus DDL (CreateTable / DropTable / AlterTable / Merge) across all tables — for data engineers who own table lifecycle.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "SchemaAdmin",
"Effect": "Allow",
"Action": [
"data:Select",
"data:Insert",
"data:Update",
"data:Delete",
"data:CreateTable",
"data:DropTable",
"data:AlterTable",
"data:Merge"
],
"Resource": "data:table:*.*.*"
}
]
}
Catalog-scoped access
Scope grants to a single connector by using the catalog segment of the resource (ice.*.*, pg.*.*, kafka.*.*, …).
Iceberg Read-Only
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "IcebergReadOnly",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:ice.*.*"
}
]
}
Iceberg Read-Write
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "IcebergDML",
"Effect": "Allow",
"Action": ["data:Select", "data:Insert", "data:Update", "data:Delete", "data:Merge"],
"Resource": "data:table:ice.*.*"
}
]
}
JDBC Catalog Read-Only
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "JdbcReadOnly",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:pg.*.*"
}
]
}
JDBC Catalog Read-Write
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "JdbcDML",
"Effect": "Allow",
"Action": ["data:Select", "data:Insert", "data:Update", "data:Delete"],
"Resource": "data:table:pg.*.*"
}
]
}
Cross-Catalog (Iceberg + JDBC)
Different permissions per catalog in one policy — e.g. full write to Iceberg, insert-only to JDBC.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "IcebergAccess",
"Effect": "Allow",
"Action": ["data:Select", "data:Insert", "data:Merge"],
"Resource": "data:table:ice.*.*"
},
{
"Sid": "JdbcAccess",
"Effect": "Allow",
"Action": ["data:Select", "data:Insert"],
"Resource": "data:table:pg.*.*"
}
]
}
Kafka Stream Consumer
Read-only on a Kafka catalog — for streaming source consumers.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "KafkaConsume",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:kafka.*.*"
}
]
}
Namespace Isolation
Confine a team to a single schema (namespace) — everything within ice.team_a, nothing outside it.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "NamespaceAccess",
"Effect": "Allow",
"Action": "*",
"Resource": ["data:table:ice.team_a.*", "data:schema:ice.team_a"]
}
]
}
Guardrails (Deny)
Deny statements always win, so they enforce guardrails on top of broader Allows.
Audit-Safe (Deny Modify Logs)
Allow DML everywhere, but forbid updating/deleting audit and log tables (matched by name pattern).
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "AllowDML",
"Effect": "Allow",
"Action": ["data:Select", "data:Insert", "data:Update", "data:Delete"],
"Resource": "data:table:*.*.*"
},
{
"Sid": "DenyModifyAudit",
"Effect": "Deny",
"Action": ["data:Update", "data:Delete"],
"Resource": ["data:table:*.*.audit_*", "data:table:*.*.*_logs"]
}
]
}
Column- and row-level control
These express Ontul's column deny, column masking, and row filtering. The Admin UI ships the allow-list and row-filter forms as one-click templates; the Column Deny and Column Mask forms below are the same policy shapes, written by hand (or edited from a template). All apply server-side inside the query plan. See Column-Level Security.
Column-Restricted Read
An allow-list of columns, rather than a deny-list.
Grant Select on a table but expose only specific columns via Columns on an Allow — all other columns are hidden.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "LimitedColumns",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:pg.public.employees",
"Columns": ["id", "name", "department"]
}
]
}
Column Deny (Hide Columns)
The inverse of the allow-list: Columns on a Deny drops just those columns from the scan output, so a SELECT * returns every column except the denied ones.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "HideSsnFromAnalysts",
"Effect": "Deny",
"Action": "data:Select",
"Resource": "data:table:hr.core.employees",
"Columns": ["ssn"]
}
]
}
Column Mask (PII)
Replace a column's value with a SQL expression via MaskedColumns (Effect: "Mask"). The output column keeps its original name and type — joins, WHERE, and aggregations still work, just on the masked value. The expression is evaluated at the worker and may reference the original column and ${user.attr.*} attributes.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "MaskHrPii",
"Effect": "Mask",
"Action": "data:Select",
"Resource": "data:table:hr.core.employees",
"MaskedColumns": {
"ssn": "'***-**-' || substr(ssn, -4)",
"email": "regexp_replace(email, '(^.).*(@.*$)', '$1***$2')",
"salary": "0"
}
}
]
}
Column Mask (Conditional Unmask by Role)
Masks can be conditional — unmask for one role and mask everyone else:
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "ConditionalUnmaskSsn",
"Effect": "Mask",
"Action": "data:Select",
"Resource": "data:table:hr.core.employees",
"MaskedColumns": {
"ssn": "CASE WHEN '${user.attr.role}' = 'compliance' THEN ssn ELSE '***-**-XXXX' END"
}
}
]
}
Note
Precedence is Deny > Mask > Allow. A column named in both a Deny and a Mask statement disappears entirely — the mask never fires.
Row-Level Filter
Attach a Condition so the user only sees matching rows — the predicate is injected into every query against the table. Templated conditions like department = '${user.attr.department}' resolve per user. See Row-Level Security.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "DeptFilter",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:pg.public.employees",
"Condition": "department = 'Engineering'"
}
]
}
Semantic layer, retrievers and ontology
A semantic view, a retriever and an ontology object are authorized by their own
fqn — semantic.rag.docs_hybrid, ontology.sales.Account — not through a
data:table: prefix. Invoking a retriever is a data:SelectTable check on
that fqn: the retriever's own SQL template does the reading, so what IAM gates
here is whether the caller may reach the object at all. The rows it returns are
then filtered again by the template's ${user.id} / ${user.roles} scoping, so
two callers running the same retriever with the same arguments can legitimately
get different rows.
Retriever Invoke (Agent)
Lets an agent identity invoke every retriever in one schema — the usual shape for
a RAG application, where retrievers are grouped under semantic.rag.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "InvokeRagRetrievers",
"Effect": "Allow",
"Action": "data:SelectTable",
"Resource": "semantic.rag.*"
}
]
}
Retriever Invoke (Single Retriever)
The same grant narrowed to one retriever. Use this when an application should be able to run exactly one query shape and nothing else — a new retriever added to the schema later does not silently become reachable.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "InvokeOneRetriever",
"Effect": "Allow",
"Action": "data:SelectTable",
"Resource": "semantic.rag.docs_hybrid"
}
]
}
Semantic Layer Read-Only (Views + Retrievers)
Every semantic object readable, nothing writable anywhere. The explicit Deny
is what makes it read-only rather than merely un-granted: Deny beats Allow,
so this holds even when the identity also belongs to a group that grants writes.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "ReadSemanticObjects",
"Effect": "Allow",
"Action": "data:SelectTable",
"Resource": "semantic.*.*"
},
{
"Sid": "NoWritesAnywhere",
"Effect": "Deny",
"Action": ["data:Insert", "data:Update", "data:Delete"],
"Resource": "*"
}
]
}
Ontology Certifier (Domain Owner)
Certification is an ownership decision, not an editing one, so ontology:Certify
is a separate action from the administrator rights needed to edit the ontology.
This template lets a domain owner bless their own subtree without being made an
admin. Administrators may always certify.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "CertifySalesOntology",
"Effect": "Allow",
"Action": "ontology:Certify",
"Resource": "ontology.sales.*"
}
]
}
Every certify and decertify is written to the Audit Log, and a
refusal as ontology:certify:denied. See Ontology.
Job control
Job Submitter
Read everything, and write/create tables in Iceberg — the grant a batch/streaming job's identity needs.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "ReadAllCatalogs",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:*.*.*"
},
{
"Sid": "WriteIceberg",
"Effect": "Allow",
"Action": ["data:Insert", "data:Merge", "data:CreateTable"],
"Resource": "data:table:ice.*.*"
}
]
}
Streaming Flow (Ontul Flow)
Starting an Ontul Flow enforces the same table-level RBAC as SQL DML: the
identity needs data:Select on any Ontul-catalog source table and write access on the Ontul-catalog
sink table — data:Insert for an append flow, plus data:Update / data:Delete for a CDC / upsert
write mode. External systems (Kafka, CDC databases, JDBC, Elasticsearch, webhooks, NeorunBase) are
reached through their registered connection credentials, not IAM. This template grants a Flow that reads
any table and maintains an Iceberg CDC replica.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "FlowReadSources",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:*.*.*"
},
{
"Sid": "FlowWriteIcebergSink",
"Effect": "Allow",
"Action": ["data:Insert", "data:Update", "data:Delete"],
"Resource": "data:table:ice.*.*"
}
]
}
For an append-only flow (no CDC / upsert), the sink statement needs only data:Insert. Scope the
Resource segments down (ice.sales.*, a single table) to confine which sinks a Flow identity may
write.
Job Manager
Kill jobs / cancel queries plus read access — for operators managing running work.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "JobManagement",
"Effect": "Allow",
"Action": ["data:KillJob", "data:CancelQuery"],
"Resource": ["data:job:*", "data:query:*"]
},
{
"Sid": "ReadAllCatalogs",
"Effect": "Allow",
"Action": "data:Select",
"Resource": "data:table:*.*.*"
}
]
}
User-Defined Functions
UDF resources are udf:<name>, so udf:* covers all functions and a prefix like udf:mask_* scopes to a family. See the UDF feature page.
UDF Execute Any
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "UdfExecAny",
"Effect": "Allow",
"Action": "UDF:EXECUTE",
"Resource": "udf:*"
}
]
}
UDF Author (Create + Drop + Execute)
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "UdfAuthor",
"Effect": "Allow",
"Action": ["UDF:CREATE", "UDF:DROP", "UDF:EXECUTE"],
"Resource": "udf:*"
}
]
}
UDF Sandbox (Execute Specific Functions)
Execute only an allow-listed set of functions — nothing else.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "UdfSandbox",
"Effect": "Allow",
"Action": "UDF:EXECUTE",
"Resource": ["udf:mask_*", "udf:hash_*", "udf:length_class"]
}
]
}
UDF Deny Sensitive
Execute any function except a denied family (admin/internal), enforced by a Deny overlay.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "UdfBaseExec",
"Effect": "Allow",
"Action": "UDF:EXECUTE",
"Resource": "udf:*"
},
{
"Sid": "UdfDenyAdmin",
"Effect": "Deny",
"Action": "UDF:EXECUTE",
"Resource": ["udf:*_admin", "udf:internal_*"]
}
]
}
UDF Global Admin (Create + Drop GLOBAL)
Authoring rights for global (cluster-wide) UDFs, plus execute.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "UdfGlobalAdmin",
"Effect": "Allow",
"Action": ["UDF:CREATE_GLOBAL", "UDF:DROP_GLOBAL", "UDF:EXECUTE"],
"Resource": "udf:*"
}
]
}
UDF Global Read-Only (Execute Only)
Execute-only — for consumers who use global UDFs but cannot author them.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "UdfGlobalExec",
"Effect": "Allow",
"Action": "UDF:EXECUTE",
"Resource": "udf:*"
}
]
}
Decision endpoints (ai_verdict)
ai_verdict() / ai_verdict_one() call an external decision
model once per distinct state in every batch, so the endpoint is granted
separately from the tables the query reads — AI:DECIDE on
connection:<connectionId>. Granting per endpoint rather than per function is
what gives two controls that table grants cannot express: an allow list for who
may bill the expensive model, and a deny that stops data reaching one vendor
from SQL at all.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "AllowCheapDecisionModel",
"Effect": "Allow",
"Action": "AI:DECIDE",
"Resource": "connection:jev-small"
},
{
"Sid": "DenyVendorX",
"Effect": "Deny",
"Action": "AI:DECIDE",
"Resource": "connection:vendor-x"
}
]
}
An identity also needs data:Select on the tables the query scans — this
statement grants the endpoint, not the data. And a DECIDE connection marked
external=true requires every call to pass acknowledgeEgress, which is not an
IAM check and cannot be granted away: it records a human decision that text may
leave the cluster.
Decision Endpoint (ai_verdict)
The minimal grant — one endpoint, one identity.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "AllowJevEndpoint",
"Effect": "Allow",
"Action": "AI:DECIDE",
"Resource": "connection:jev"
}
]
}
Decision Endpoint (Allow One, Deny Another)
The shape to reach for once more than one endpoint is registered: allow the
cheap model, deny the vendor nobody may send data to. Deny beats Allow, so
the second statement holds even for an identity that also belongs to a group
granting *.
{
"Version": "2024-01-01",
"Statement": [
{
"Sid": "AllowCheapDecisionModel",
"Effect": "Allow",
"Action": "AI:DECIDE",
"Resource": "connection:jev-small"
},
{
"Sid": "DenyVendorX",
"Effect": "Deny",
"Action": "AI:DECIDE",
"Resource": "connection:vendor-x"
}
]
}
Applying a template
Templates are starting points — attach one directly, or edit it in the Admin UI policy editor and save under a new name. Policies attach to a user or a group (a group policy applies to all members). See Creating and updating policies for the REST/CLI flow.