Connect Trino and Starburst to the Iceberg REST catalog
Apache Gravitino exposes an Iceberg REST Catalog (IRC) endpoint that any Iceberg-compatible engine can connect to directly, without installing a Gravitino-specific connector plugin. The sections below describe how to configure Trino to use it. Starburst Enterprise uses the same Iceberg connector and the same catalog properties, and adds one way of forwarding each user's identity, described under Per-user identity with Starburst.
Most of the configuration is on the Trino side and is covered here. The storage credential setup lives on the Gravitino catalog and is covered in Credential vending, which this page links to at each point where it applies.
Quick start
Create a catalog properties file in your Trino etc/catalog/ directory. The filename determines the
catalog name in Trino, so gravitino_irc.properties creates a catalog named gravitino_irc. Some
distributions place the directory at etc/trino/catalog/. Either way, these are per-catalog
properties and do not belong in config.properties.
The file below is complete, using vended credentials with OAuth authentication. Place it in
etc/catalog/ and restart Trino. Each commented group has a matching section below, covering what
the properties do and what the alternatives are.
# Connection properties
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://{iceberg_host}/iceberg
iceberg.rest-catalog.warehouse={catalog}
# Authentication
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.credential={client_id}:{client_secret}
iceberg.rest-catalog.oauth2.server-uri={token_endpoint_uri}
iceberg.rest-catalog.oauth2.scope={scope}
# Storage access
iceberg.rest-catalog.vended-credentials-enabled=true
fs.s3.enabled=true
s3.region={region_name}
# Table defaults
iceberg.file-format=PARQUET
iceberg.compression-codec=ZSTD
On Trino 480 and earlier, fs.s3.enabled is named fs.native-s3.enabled. See
Storage access.
Prerequisites
On the Gravitino side:
- Gravitino running with the Iceberg REST service enabled. See Iceberg REST catalog service for setup.
- The Iceberg REST service published and reachable from the Trino coordinator and all workers. See Client access.
- For vended credentials, two pieces of setup, both described in Credential
vending:
- A credential provider, and the role it assumes, configured on the catalog. See
s3-token. - The trust policy and the permission policy on the vending role, in AWS IAM.
- A credential provider, and the role it assumes, configured on the catalog. See
Of everything vending needs, only the Trino properties in Storage access are configured on this page. A Trino catalog file that is correct on its own vends nothing when any of the pieces above is missing, and the resulting failures surface in Trino rather than in Gravitino. See Troubleshooting for the symptoms each one produces.
On the Trino side, the release requirements differ by feature:
| Capability | Minimum Trino release |
|---|---|
| Native S3 file system, which consumes vended credentials | 419 |
| Explicit file system activation in each catalog file | 458 |
| Vended credentials for Azure | 481 |
| Refreshable vended credentials for S3, GCS, and Azure | 481 |
Basic authentication through iceberg.rest-catalog.http-headers | 481 |
SHOW SCHEMAS with OAuth and nested namespaces | 482 |
Verified against Gravitino 1.3.0 and Trino 478 with AWS S3. Property names and behavior at Trino 481 and later come from the Trino documentation and release notes rather than from that run.
Connection properties
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://{iceberg_host}/iceberg
iceberg.rest-catalog.warehouse={catalog}
| Property | Purpose | Required |
|---|---|---|
connector.name | Always iceberg for this path | Yes |
iceberg.catalog.type | Always rest for this path | Yes |
iceberg.rest-catalog.uri | The Gravitino IRC endpoint | Yes |
iceberg.rest-catalog.warehouse | Selects which Gravitino catalog to use | Yes |
Trino documents the full set in Iceberg REST catalog configuration properties.
iceberg.rest-catalog.warehouse must match the catalog name in Gravitino. Trino sends it when it
first calls the service, and Gravitino answers with that catalog's settings and the prefix Trino uses
for every later request. Set the warehouse rather than iceberg.rest-catalog.prefix: with only a
prefix, the first call names no catalog, so Gravitino answers with the default catalog's settings,
or fails when no default catalog is set.
curl -s "https://{iceberg_host}/iceberg/v1/config?warehouse={catalog}" \
-H "Authorization: Bearer $TOKEN"
Authentication and storage access are independent choices. Pick one option from each of the two sections below.
Which Gravitino catalogs are reachable
The IRC serves the Iceberg catalogs of one metalake, named by a chart value in
my-values.yaml:
gravitino:
icebergRest:
dynamicConfigProvider:
metalake: "{metalake}"
The metalake is fixed at startup. Pointing the IRC at a different metalake means changing the value
and rerunning the helm upgrade --install command. The metalake does not have to exist when the
server starts, so naming it first and creating it afterwards works.
Catalogs are not fixed. The IRC polls the Gravitino server for the Iceberg catalogs in that metalake,
so a catalog created in the UI or through the Gravitino REST API becomes reachable without a restart.
Select one from Trino with iceberg.rest-catalog.warehouse, described in
Connection properties. The chart value
gravitino.icebergRest.dynamicConfigProvider.defaultCatalogName decides which catalog answers when a
client names no warehouse.
Every request over the IRC is authenticated and checked against Gravitino access control, the same as a request to the Gravitino API; see Authentication and access control.
Table defaults
Optional, and independent of everything else on this page. These set the defaults Trino uses when it creates tables through the IRC:
iceberg.file-format=PARQUET
iceberg.compression-codec=ZSTD
Authentication
Trino has to identify itself to the IRC, independently of the choice in
Storage access. A request with no identity is rejected with 401 before any
vending occurs. See How to authenticate for the Gravitino
side.
Use OAuth. With OAuth, Trino sends a token from the identity provider as an Authorization: Bearer
header on every request. Trino always signs in with a shared identity of its own, and can
additionally forward each user's own token so that Gravitino authorizes the user rather than Trino.
Basic authentication gives only a shared identity.
Shared identity for Trino
Trino signs in as itself and sends its own token on every request, no matter which Trino user ran the query. Gravitino authorizes every query as Trino's account, so grant that account in Gravitino only what every Trino user may read and write.
| Method | The shared identity | Renewed |
|---|---|---|
| Client credentials | A client registered for Trino at the identity provider. Its client ID and secret go in the catalog file, Trino uses them to get tokens, and every user on the Trino cluster is identified as that client | Yes, by Trino |
| Pre-issued token | A token obtained from the identity provider and placed in the catalog file. Trino sends it for every user on the Trino cluster, so all of them are identified as whoever the token was issued to | No |
Use client credentials unless Trino cannot reach the identity provider.
Client ID and client secret
Used in the Quick start above. Trino holds the client ID and secret of a client registered for it at the identity provider, requests a token itself, and requests a new one when the current token expires.
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.credential={client_id}:{client_secret}
iceberg.rest-catalog.oauth2.server-uri={token_endpoint_uri}
iceberg.rest-catalog.oauth2.scope={scope}
iceberg.rest-catalog.oauth2.server-uri is the identity provider's token endpoint, not its issuer or
realm URL. On Keycloak that is:
https://{keycloak_host}/realms/{realm}/protocol/openid-connect/token
Pre-issued token
Trino sends one fixed token, obtained from the identity provider outside Trino. Every Trino user runs as whoever the token was issued to, so issue it to a service account, not a person.
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.token={token}
Nothing renews the token. When it expires, every request fails with 401 until someone edits the
catalog file and restarts Trino. The token also sits in plaintext in the catalog file, where anyone
who can read the file can replay it against the IRC. Use it only when Trino cannot reach the
identity provider, or for a short test.
Token exchange settings
On Trino 479 and later, add this to the client credentials configuration:
iceberg.rest-catalog.oauth2.token-exchange-enabled=false
The setting controls how Trino renews its own token. With the default, true, Trino renews it by
exchanging the old token at the identity provider, which can cause repeated token requests. With
false, Trino signs in again with its client ID and secret. It does not affect a user's token under
per-user identity. Trino 478 and earlier do not have the property and refuse
to start when it is present.
Per-user identity, optional
With per-user identity, Gravitino authorizes each query as the Trino user who ran it, using that user's own token. Trino still signs in with its client ID and secret for its own calls, such as loading the catalog, and sends the user's token in their place on that user's queries.
Add one property to the client credentials configuration:
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.credential={client_id}:{client_secret}
iceberg.rest-catalog.oauth2.server-uri={token_endpoint_uri}
iceberg.rest-catalog.oauth2.scope={scope}
iceberg.rest-catalog.session=USER
Each user gets a token from the identity provider and passes it to Trino as an extra credential
named token. Trino does not sign the user in or request the token: the user gets it outside Trino,
with whatever sign-in tool the organization uses. Trino forwards it unchanged to Gravitino on every
catalog call in that user's queries.
trino --server https://{trino_host}:8443 --user {username} \
--extra-credential token=$TOKEN
jdbc:trino://{trino_host}:8443?user={username}&extraCredentials=token:{token}
The user's token must pass the same checks as any token Gravitino accepts: the audience configured for Gravitino, and a principal claim naming a member of the metalake. See How to authenticate.
What to expect:
- A query that carries no
tokendoes not fall back to Trino's own identity. Trino instead asks the identity provider to exchange a session token that Trino generates, the identity provider refuses, and the query fails before any request reaches Gravitino. With Keycloak, Trino reportsFailed to load view '{table}', caused byunsupported_grant_type. Every user of the catalog must supply a token. - Trino does not renew the user's token. When it expires, the user's queries are rejected until they get a new token and reconnect.
- Extra credentials travel from the client to Trino with the query, so connect to Trino over HTTPS.
- When Gravitino refuses a table the user may not read, Trino reports
Failed to load view '{table}', even though the object is a table.
Verified against Gravitino 1.3.1 and Trino 478: a user with a grant on one table and none on a second read the first and was refused the second, and the same query without the user's token failed at the token exchange without reaching Gravitino.
Per-user identity with Starburst
Starburst Enterprise can forward the token each user signed in to Starburst with, so users do not obtain a token outside Starburst or pass it as an extra credential. Starburst calls this OAuth 2.0 token pass-through.
On the coordinator, users sign in to Starburst through the identity provider:
http-server.authentication.type=DELEGATED-OAUTH2
web-ui.authentication.type=DELEGATED-OAUTH2
In the catalog file, Starburst forwards that token instead of sending its own:
iceberg.rest-catalog.security=OAUTH2_PASSTHROUGH
The token Starburst forwards was issued to Starburst's client at the identity provider, so it must also carry the audience configured for Gravitino; in Keycloak, add an audience mapper for that audience to Starburst's client. Starburst does not refresh a forwarded token, so each token must outlive the queries run with it. See Starburst's OAuth 2.0 token pass-through for the coordinator settings.
Basic authentication
Requires Trino 481 or later. Basic gives only a shared identity: one Gravitino local account for
every Trino user. Trino has no Basic option in iceberg.rest-catalog.security, so Trino
sends Basic credentials as a header you build yourself, through iceberg.rest-catalog.http-headers
(trinodb/trino#24236). The user name and password
are those of a Gravitino local account.
echo -n '{username}:{password}' | base64
iceberg.rest-catalog.http-headers=Authorization: Basic {base64_credentials}
Trino sends the header unchanged on every request and never renews it. Base64 is encoding, not encryption, so anyone who can read the catalog file recovers the password, and Trino documents the property for non-sensitive headers. Prefer OAuth.
Storage access
Trino needs credentials to read and write the underlying object storage. Choose vended credentials or static credentials. Do not configure both.
Whichever you choose, the native S3 file system must be enabled. It performs the request signing for
S3 access, and without it metadata operations succeed while data reads fail with
ICEBERG_FILESYSTEM_ERROR.
fs.s3.enabled=true
Trino 481 removed legacy object storage support, leaving fs.hadoop.enabled for HDFS only, and the
native file system properties lost the native- segment at the same time. Releases through 480
document fs.native-s3.enabled, and 481 and later document fs.s3.enabled. Use the name documented
for the release you run.
How credential vending works
- Trino calls
loadTableon the Gravitino IRC endpoint. Withiceberg.rest-catalog.vended-credentials-enabled=true, Trino sends theX-Iceberg-Access-Delegation: vended-credentialsheader for you. - Gravitino calls STS
AssumeRoleagainst the IAM role configured on the catalog. - Gravitino returns a
storage-credentialsblock in theloadTableresponse, holding temporary credentials scoped to the table's S3 prefix. - Trino uses those credentials for the S3 reads and writes in that query.
The vended credentials are scoped to the table's path and to what the caller may do with the table. Gravitino mints them from one role configured on the catalog, narrowed by a session policy: a caller who may modify the table receives credentials that can read and write its files, and any other caller receives credentials that can only read them. Which users reach the table at all is decided by Gravitino access control before any credential is minted.
Vended credentials
Used in the Quick start above. Gravitino mints short-lived, path-scoped credentials at query time, so no long-lived storage keys live in the Trino configuration.
iceberg.rest-catalog.vended-credentials-enabled=true
fs.s3.enabled=true
s3.region={region_name}
The catalog-side configuration this requires, covering the credential provider, the role it assumes, and the IAM policies behind it, is described in Credential vending. None of it is set from the Trino catalog file.
If s3.aws-access-key and s3.aws-secret-key are also set in the file, Trino requests vended
credentials, receives them, and then signs with the static keys anyway. No warning appears and every
query works, so the file reads as though vending is in use when it is not. Check that neither is
present, and see Verification for how to confirm which credentials are actually
reaching S3.
Backend coverage varies by Trino release. S3 is supported throughout. Trino 481 added Azure (trinodb/trino#23238) and refreshable vended credentials, which its release notes describe as covering S3, GCS, and Azure (trinodb/trino#28998). Check the release notes for the version you run before relying on GCS or Azure, and use static credentials where vending is not yet available.
Static credentials
Configure storage keys directly in Trino. Simpler to set up, but the keys are long-lived, are not scoped to a table path, and are managed outside Gravitino.
Relative to the Quick start, remove iceberg.rest-catalog.vended-credentials-enabled and configure
the keys:
fs.s3.enabled=true
s3.region={region_name}
s3.aws-access-key={access_key_id}
s3.aws-secret-key={secret_access_key}
Leaving iceberg.rest-catalog.vended-credentials-enabled=true in place alongside the keys is not an
error and produces no warning. Trino requests vended credentials and then signs with the static keys
anyway, for the reason given under Vended credentials.
Against MinIO, add the endpoint and path-style access. The same precedence applies, so remove
iceberg.rest-catalog.vended-credentials-enabled here too:
fs.s3.enabled=true
s3.endpoint=http://{minio_host}:9000
s3.path-style-access=true
s3.aws-access-key={minio_access_key}
s3.aws-secret-key={minio_secret_key}
s3.region=us-east-1
Start Trino
Trino is a server process, and the catalog is picked up when Trino starts. After placing
gravitino_irc.properties in etc/catalog/, restart Trino:
$TRINO_HOME/bin/launcher restart
Trino needs roughly 20 seconds to accept queries after a restart, which is long enough to produce misleading connection errors in scripted runs.
Once Trino is running, connect using the Trino CLI:
trino --server http://{trino_host}:8080 --catalog gravitino_irc
Without a default catalog, qualify queries fully.
trino --server http://{trino_host}:8080
Verification
Confirm the server vends credentials
Check the server before assuming Trino is using vended credentials. The storage-credentials block
in the loadTable response is the direct evidence:
curl -s -H "X-Iceberg-Access-Delegation: vended-credentials" \
-H "Authorization: Bearer $TOKEN" \
https://{iceberg_host}/iceberg/v1/{catalog}/namespaces/{namespace}/tables/{table} \
| python3 -m json.tool | grep -A8 storage-credentials
"storage-credentials": [
{
"prefix": "s3://{bucket_name}/{warehouse_path}/{namespace}/{table}",
"config": {
"s3.access-key-id": "ASIA...",
"s3.secret-access-key": "...",
"s3.session-token": "...",
"s3.session-token-expires-at-ms": "..."
}
}
]
Three markers distinguish genuine vending from static credentials passed through: the access key
begins with ASIA rather than AKIA, a session token is present, and the prefix is scoped to the
table path rather than the whole bucket.
Confirm the engine path
trino --execute "SELECT * FROM {catalog}.{namespace}.{table}"
A successful query alone does not prove vending is in use, since static keys or an instance profile
can satisfy the same reads. For independent confirmation at the AWS layer, CloudTrail shows the
AssumeRole call against the vending role, followed by S3 operations attributed to the assumed-role
session rather than to the base IAM user. CloudTrail is the authoritative check in environments
where an EC2 instance profile could otherwise satisfy the S3 reads.
Known issues
Vended credentials are not refreshed during long-running queries
Applies to Trino releases before 481.
Cause: Gravitino advertises a refresh endpoint in the loadTable response as
client.refresh-credentials-endpoint, but Trino does not call it when vended credentials expire
mid-query (trinodb/trino#25827). Scans running past
the STS session lifetime fail. The gap is client-side rather than a catalog limitation.
Solution: Upgrade to Trino 481 or later, which adds refreshable vended credentials
(trinodb/trino#28998). On earlier releases, raise
s3-token-expire-in-secs on the Gravitino catalog,
together with the maximum session duration on the IAM role, or keep individual queries shorter than
the session lifetime.
Storage credentials are exposed in query JSON
Cause: Trino serializes storage credentials into query JSON for write and table maintenance operations, where any user with write privilege can read them through the Trino UI or the query API (GHSA-x27p-5f68-m644). The exposure applies to static credentials as well as vended ones.
Solution: Review the current advisory status against your deployed Trino version. Short
s3-token-expire-in-secs values limit the window during which an exposed vended credential is
usable, which static keys do not offer.
Trino returns valid credentials but does not honor them
Cause: At least one report describes a Trino version receiving a correct storage-credentials
block from the server yet failing on S3 access, where the same endpoint works from Spark
(trinodb/trino#27416, reported on Trino 474).
Solution: If the verification curl shows a valid credentials block and Trino still fails, treat the Trino version as a suspect before revisiting configuration.
Schema lists fail with OAuth and nested namespaces
Cause: With iceberg.rest-catalog.security=OAUTH2,
iceberg.rest-catalog.nested-namespace-enabled=true, and iceberg.rest-catalog.session=NONE (the
default), SHOW SCHEMAS recursively calls Iceberg REST listNamespaces. On Trino releases before
482, each recursive call creates a separate OAuth session, which can trigger excessive token
requests and cause errors such as Connection pool shut down or StackOverflowError.
Solution: Upgrade to Trino 482 or later.
Time zone values are not adjusted to the session time zone
Cause: Trino does not adjust TIMESTAMP WITH TIME ZONE results according to the client session
time zone. Unlike Spark and Flink, it displays these values based on the stored
timestamp-with-time-zone value.
Solution: Convert with at_timezone and current_timezone():
SELECT
id,
at_timezone(timestamp_with_timezone_column, current_timezone())
FROM {catalog}.{namespace}.{table};
Trino identifiers are not case sensitive
Cause: Trino identifiers are not treated as case sensitive, so metadata names that differ only by letter case cannot be distinguished. See Trino identifier documentation. The limitation comes from Trino itself and is not specific to Gravitino.
Solution: Use lowercase metadata names, and avoid creating objects whose names differ only by
letter case. Where mixed-case names already exist in Gravitino,
iceberg.rest-catalog.case-insensitive-name-matching=true, off by default, lets Trino resolve them.
It does not make names that differ only by case distinguishable.
Troubleshooting
Failures in this path tend to surface far from their cause. The table maps symptoms back to the configuration that produces them.
| Symptom | Likely cause |
|---|---|
Catalog not visible in SHOW CATALOGS | Trino not restarted, or a parse error in the catalog file. Check the Trino server log |
Failed to list namespaces | iceberg.rest-catalog.warehouse does not match a Gravitino catalog name, or the identity was rejected |
403 ForbiddenException, principal not in metalake | Identity rejected before vending is reached. A token principal must be a member of the metalake |
Failed to load view for a table | Gravitino refused the table to the identity on the request. Check that identity's privileges |
storage-credentials absent from the loadTable response | credential-providers not set on the catalog, or the request did not ask for vended credentials |
ICEBERG_FILESYSTEM_ERROR on data reads, metadata fine | Native S3 file system not enabled, wrong property name for the release, or static keys overriding |
S3 AccessDenied despite a valid storage-credentials block | Permission policy on the vending role, or static keys taking precedence in the Trino catalog file |
STS AccessDenied on AssumeRole | Trust policy does not allow the s3-access-key-id principal to assume the vending role |
Access key in the response begins with AKIA | The catalog is using s3-secret-key rather than s3-token, so static keys are vended unchanged |
| Long queries fail after about an hour | STS session expiry with no client-side refresh. See Known issues |
| Verification curl shows valid credentials but Trino fails on S3 | Possible version-specific consumption bug. See Known issues |
The catalog-side causes are described in Credential vending.
Gravitino connector vs. the IRC
| Feature | Gravitino engine connector | IRC |
|---|---|---|
| Engine plugin required | Yes | No |
| Gravitino access control | Yes | Yes |
| Identity Gravitino authorizes | The connector's account, or each Trino user with session credential forwarding | Trino's shared identity, or each Trino user with per-user identity |
| Supported engines | Trino, Spark, Flink, Daft | Any Iceberg-compatible engine |
| Credential vending | Varies | Yes, see Trino release notes |
Both paths serve the same catalogs in the metalake and enforce the same privileges, against the identity each path presents.