Skip to main content

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.

etc/catalog/gravitino_irc.properties
# 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.

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:

CapabilityMinimum Trino release
Native S3 file system, which consumes vended credentials419
Explicit file system activation in each catalog file458
Vended credentials for Azure481
Refreshable vended credentials for S3, GCS, and Azure481
Basic authentication through iceberg.rest-catalog.http-headers481
SHOW SCHEMAS with OAuth and nested namespaces482

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​

gravitino_irc.properties
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://{iceberg_host}/iceberg
iceberg.rest-catalog.warehouse={catalog}
PropertyPurposeRequired
connector.nameAlways iceberg for this pathYes
iceberg.catalog.typeAlways rest for this pathYes
iceberg.rest-catalog.uriThe Gravitino IRC endpointYes
iceberg.rest-catalog.warehouseSelects which Gravitino catalog to useYes

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.

Confirm the catalog against the server
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:

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:

gravitino_irc.properties
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.

Shared identity for Trino

MethodThe shared identityRenewed
Client credentialsA 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 clientYes, by Trino
Pre-issued tokenA 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 toNo

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.

gravitino_irc.properties
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:

Keycloak token endpoint
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.

gravitino_irc.properties
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:

gravitino_irc.properties
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.

Per-user identity through Trino

Add one property to the client credentials configuration:

gravitino_irc.properties
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 CLI
trino --server https://{trino_host}:8443 --user {username} \
--extra-credential token=$TOKEN
Trino JDBC URL
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 token does 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 reports Failed to load view '{table}', caused by unsupported_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.

Per-user identity through Starburst

On the coordinator, users sign in to Starburst through the identity provider:

config.properties
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:

gravitino_irc.properties
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.

Encode the credentials in Base64
echo -n '{username}:{password}' | base64
gravitino_irc.properties
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.

gravitino_irc.properties
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​

  1. Trino calls loadTable on the Gravitino IRC endpoint. With iceberg.rest-catalog.vended-credentials-enabled=true, Trino sends the X-Iceberg-Access-Delegation: vended-credentials header for you.
  2. Gravitino calls STS AssumeRole against the IAM role configured on the catalog.
  3. Gravitino returns a storage-credentials block in the loadTable response, holding temporary credentials scoped to the table's S3 prefix.
  4. 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.

gravitino_irc.properties
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:

gravitino_irc.properties
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:

gravitino_irc.properties
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:

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:

Connect with a default catalog
trino --server http://{trino_host}:8080 --catalog gravitino_irc

Without a default catalog, qualify queries fully.

Connect without a default catalog
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:

Load a table with vended credentials
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
Expected output
"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​

Query a table through Trino
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():

Convert to the session time zone
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.

SymptomLikely cause
Catalog not visible in SHOW CATALOGSTrino not restarted, or a parse error in the catalog file. Check the Trino server log
Failed to list namespacesiceberg.rest-catalog.warehouse does not match a Gravitino catalog name, or the identity was rejected
403 ForbiddenException, principal not in metalakeIdentity rejected before vending is reached. A token principal must be a member of the metalake
Failed to load view for a tableGravitino refused the table to the identity on the request. Check that identity's privileges
storage-credentials absent from the loadTable responsecredential-providers not set on the catalog, or the request did not ask for vended credentials
ICEBERG_FILESYSTEM_ERROR on data reads, metadata fineNative S3 file system not enabled, wrong property name for the release, or static keys overriding
S3 AccessDenied despite a valid storage-credentials blockPermission policy on the vending role, or static keys taking precedence in the Trino catalog file
STS AccessDenied on AssumeRoleTrust policy does not allow the s3-access-key-id principal to assume the vending role
Access key in the response begins with AKIAThe catalog is using s3-secret-key rather than s3-token, so static keys are vended unchanged
Long queries fail after about an hourSTS session expiry with no client-side refresh. See Known issues
Verification curl shows valid credentials but Trino fails on S3Possible version-specific consumption bug. See Known issues

The catalog-side causes are described in Credential vending.

Gravitino connector vs. the IRC​

FeatureGravitino engine connectorIRC
Engine plugin requiredYesNo
Gravitino access controlYesYes
Identity Gravitino authorizesThe connector's account, or each Trino user with session credential forwardingTrino's shared identity, or each Trino user with per-user identity
Supported enginesTrino, Spark, Flink, DaftAny Iceberg-compatible engine
Credential vendingVariesYes, see Trino release notes

Both paths serve the same catalogs in the metalake and enforce the same privileges, against the identity each path presents.