DEV Community

Aki for AWS Community Builders

Posted on

Connecting Amazon S3 Tables and Snowflake (via the S3 Tables Iceberg REST Endpoint)

Original Japanese article: Amazon S3 TablesとSnowflakeを連携する(S3 Tables Iceberg RESTエンドポイント経由)

Introduction

I'm Aki, an AWS Community Builder (@jitepengin).

In an earlier article, I verified how to read from and write to an Iceberg table on S3 Tables using Snowflake's Catalog Integration via the AWS Glue Iceberg REST endpoint.

Connecting Amazon S3 Tables with Snowflake

That setup routed through Snowflake → the Glue Iceberg REST endpoint → Lake Formation → S3 Tables, and required setting up an IAM policy, a trust policy, registering a Lake Formation data location, and granting permissions across all three levels (Catalog / Database / Table).

On 2026/8/10, Snowflake's Amazon S3 Tables Iceberg REST catalog integration went GA! This is a new route that connects with SigV4 authentication directly to S3 Tables' native Iceberg REST endpoint, without going through Glue at all.

https://docs.snowflake.com/en/release-notes/2026/other/2026-08-10-amazon-s3-tables-iceberg-rest-catalog-integration-ga

This time, I wanted to put this new route to the test.

Test Environment

I reused the same resources as the previous article.

  • Table bucket: penguin-rest-test
  • Namespace: analytics
  • Table: daily_sales (columns sales_date, amount)
  • Region: ap-northeast-1

Architecture

This time's architecture connects the S3 Tables gold layer to Snowflake. The key point is that it uses the Amazon S3 Tables Iceberg REST catalog integration instead of Glue Data Catalog.

Differences from the Glue Route (Configuration)

The CATALOG INTEGRATION parameters differ, as shown below, configured specifically for S3 Tables.

Item Via Glue (previous article) Direct to S3 Tables (this article)
CATALOG_API_TYPE AWS_GLUE AWS_S3TABLES
CATALOG_URI glue.<region>.amazonaws.com/iceberg s3tables.<region>.amazonaws.com/iceberg
CATALOG_NAME <account_id>:s3tablescatalog/<bucket> arn:aws:s3tables:<region>:<account_id>:bucket/<bucket>

Differences from the Glue Route (Setup Steps)

Of the steps required in the previous article, the following Lake Formation-related steps simply don't exist here:

  • Registering the Lake Formation data location (register-resource --with-federation --with-privileged-access)
  • Granting Lake Formation permissions across all three levels (Catalog / Database / Table)
  • Adding sts:SetSourceIdentity / sts:SetContext to the data access role's trust policy

I wanted to make sure "Lake Formation setup really isn't required" is actually true, so I later confirmed this via CloudTrail.

Creating the IAM Role

Starting from the previous article's IAM policy, I removed all Lake Formation- and Glue-related actions, and tested with a minimal-privilege policy scoped down to just s3tables:* actions.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowS3TablesDirectAccess",
            "Effect": "Allow",
            "Action": [
                "s3tables:GetTableBucket",
                "s3tables:ListNamespaces",
                "s3tables:GetNamespace",
                "s3tables:ListTables",
                "s3tables:GetTable",
                "s3tables:GetTableMetadataLocation",
                "s3tables:UpdateTableMetadataLocation",
                "s3tables:GetTableData",
                "s3tables:PutTableData"
            ],
            "Resource": [
                "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/penguin-rest-test",
                "arn:aws:s3tables:ap-northeast-1:123456789012:bucket/penguin-rest-test/table/*"
            ]
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

The key point is that there's no need to include lakeformation:GetDataAccess or any glue:* actions.

Creating the Catalog Integration

On the Snowflake side, I created a catalog integration that connects directly to the S3 Tables Iceberg REST endpoint.

CREATE OR REPLACE CATALOG INTEGRATION penguin_s3tables_direct_int
  CATALOG_SOURCE = ICEBERG_REST
  TABLE_FORMAT = ICEBERG
  REST_CONFIG = (
    CATALOG_URI = 'https://s3tables.ap-northeast-1.amazonaws.com/iceberg'
    CATALOG_API_TYPE = AWS_S3TABLES
    ACCESS_DELEGATION_MODE = VENDED_CREDENTIALS
    CATALOG_NAME = 'arn:aws:s3tables:ap-northeast-1:123456789012:bucket/penguin-rest-test'
  )
  REST_AUTHENTICATION = (
    TYPE = SIGV4
    SIGV4_IAM_ROLE = 'arn:aws:iam::123456789012:role/penguin-s3tables-direct-role'
    SIGV4_SIGNING_REGION = 'ap-northeast-1'
  )
  ENABLED = TRUE;
Enter fullscreen mode Exit fullscreen mode

Compared side by side with the Glue-based article, CATALOG_API_TYPE changes from AWS_GLUE to AWS_S3TABLES, and CATALOG_URI changes from the Glue Iceberg REST endpoint to S3 Tables' native endpoint. CATALOG_NAME also now takes the S3 Tables bucket ARN directly, so the previous article's distinctive <account_id>:s3tablescatalog/<bucket> prefix notation is not used here.

Setting Up the Trust Policy

The steps for retrieving Snowflake's IAM user ARN and external ID via DESC CATALOG INTEGRATION, then setting them on the trust policy of the role specified in SIGV4_IAM_ROLE, are the same as with the Glue route.

DESC CATALOG INTEGRATION penguin_s3tables_direct_int;
Enter fullscreen mode Exit fullscreen mode
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "<AWS_IAM_USER_ARN>"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "<AWS_EXTERNAL_ID>"
                }
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Everything up to this point is unchanged from the previous article. The difference shows up from here on: we move straight to table creation with no Lake Formation setup at all.

Creating the Table

CREATE OR REPLACE ICEBERG TABLE GOLD.GOLD.s3tables_direct_table
  CATALOG = 'penguin_s3tables_direct_int'
  CATALOG_NAMESPACE = 'analytics'
  CATALOG_TABLE_NAME = 'daily_sales'
  AUTO_REFRESH = TRUE;
Enter fullscreen mode Exit fullscreen mode

The table was created without any issues, with no Lake Formation data location registration or grants in place.

Verifying It Works (Read)

ALTER ICEBERG TABLE GOLD.GOLD.s3tables_direct_table REFRESH;
SELECT * FROM GOLD.GOLD.s3tables_direct_table;
Enter fullscreen mode Exit fullscreen mode

Data that had already been loaded via Athena was readable from Snowflake without any problem.

Confirming the Lake Formation Calls via CloudTrail

This is the heart of this article. I traced through CloudTrail to see exactly which APIs penguin-s3tables-direct-role was actually calling.

Looking at the logs right after running the read operation, this role only calls GetTableBucket, GetNamespace, ListNamespaces, GetTable, ListTables, and GetTableMetadataLocation against s3tables.amazonaws.com, all of them readOnly: true events.

Three things sealed it:

  1. No invokedBy field
    Events from the Glue-based Snowflake_Catalog_Integration role always carry "invokedBy": "glue.amazonaws.com", proof that the resource is being accessed via Glue's service role. Events from the direct role have no such field at all, showing that Snowflake is hitting s3tables.amazonaws.com directly.

  2. sourceIPAddress is a real IP
    Events from the direct role record Snowflake's actual source IP in sourceIPAddress, whereas Glue-route events show sourceIPAddress: "glue.amazonaws.com", the notation for an internal service-to-service call.

  3. Zero lakeformation.amazonaws.com events
    Across the entire test window (roughly 30 minutes, several hundred CloudTrail events including both reads and writes), there were zero lakeformation.amazonaws.com events attributable to penguin-s3tables-direct-role. During that same window, Snowflake_Catalog_Integration (the Glue route) generated a large volume of lakeformation:GetDataAccess calls, so this isn't a case of events happening to not show up in the log; it's a structural fact that the direct route never goes through Lake Formation.

Verifying It Works (Write)

Beyond reads, let's confirm writes work too. Since the IAM policy already includes s3tables:PutTableData, this can be tested with no additional permission changes.

INSERT INTO GOLD.GOLD.s3tables_direct_table (sales_date, amount)
VALUES
    ('2026-08-14', 99999),
    ('2026-08-14', 88888);
Enter fullscreen mode Exit fullscreen mode

Checking from the Athena side, the data inserted from Snowflake was visible without issue.

SELECT * FROM "analytics"."daily_sales" ORDER BY sales_date DESC LIMIT 10;
Enter fullscreen mode Exit fullscreen mode

I also checked CloudTrail for the write operation, and just as with the read, found no Lake Formation events attributable to penguin-s3tables-direct-role.

Checking Catalog-Linked Database

In the previous article, I verified automatic table discovery via a Glue-based Catalog-Linked Database (CLD), so I wanted to confirm the same works on the direct route.

CREATE OR REPLACE DATABASE penguin_s3tables_direct_linked_db
  LINKED_CATALOG = (
    CATALOG = 'penguin_s3tables_direct_int'
  );
Enter fullscreen mode Exit fullscreen mode

As soon as this was created, daily_sales and daily_sales2 under the analytics namespace showed up automatically in the table list, with no DDL required.

Confirming Auto-Discovery

I created a new daily_sales3 table from the Athena side, and it was automatically detected on the SnowSight side as well.

-- On the Athena side
CREATE TABLE `analytics`.daily_sales3 (
  sale_date date,
  product_category string,
  sales_amount double)
PARTITIONED BY (month(sale_date))
TBLPROPERTIES ('table_type' = 'iceberg')
Enter fullscreen mode Exit fullscreen mode

Confirming Bidirectional Writes

I inserted into the auto-discovered daily_sales3 from Snowflake, then checked whether it was visible from Athena.

INSERT INTO PENGUIN_S3TABLES_DIRECT_LINKED_DB.analytics.daily_sales3 (sale_date, product_category, sales_amount)
VALUES
    ('2026-08-14', 'Electronics', 15000.00),
    ('2026-08-14', 'Clothing', 8500.50),
    ('2026-08-14', 'Food', 3200.75);
Enter fullscreen mode Exit fullscreen mode

Confirmed without issue on the Athena side. Just like the CREATE ICEBERG TABLE approach, the CLD approach also works correctly on the direct route.

How the Two Routes Compare

Putting the previous article's findings together with this one's:

Item Via Glue (previous article) Direct to S3 Tables (this article)
IAM policy s3tables:* + lakeformation:GetDataAccess + glue:* actions s3tables:* actions only
Lake Formation data location registration Required (register-resource --with-federation) Not required
Lake Formation grants (3 levels) Required Not required
Trust policy SetSourceIdentity / SetContext Required Not required
LF calls on read (CloudTrail) Occur Zero
LF calls on write (CloudTrail) Occur Zero
CLD auto-discovery and bidirectional writes Succeeded (confirmed in previous article) Succeeded (confirmed here)
Read/write operations Succeeded (confirmed in previous article) Succeeded (confirmed here)

The biggest difference is a clear tradeoff: do you want unified governance through Lake Formation, or a simpler permission design?

If you want to keep things simple, go with the direct S3 Tables route. If you need unified table management, centralized governance controls, and fine-grained access control, the Glue route is the way to go.

Conclusion

This time, I used Snowflake's Amazon S3 Tables Iceberg REST catalog integration, which went GA on 2026/8/10, to verify the new Glue-free direct route.

To summarize:

  • The new direct route (CATALOG_API_TYPE = AWS_S3TABLES) has no Lake Formation-related setup steps in the official documentation.
  • Even after completely removing all Lake Formation and Glue actions from the IAM policy, both reads and writes worked without any issues.
  • Checking CloudTrail confirmed that lakeformation:GetDataAccess calls from the direct role were zero throughout the entire test window.
  • Events from the direct role carry no invokedBy: glue.amazonaws.com field, and sourceIPAddress shows a real IP, further confirming that Snowflake accesses the S3 Tables endpoint directly, without going through Glue.
  • Catalog-Linked Database auto-discovery and bidirectional reads/writes also worked correctly on the direct route, and no Lake Formation calls occurred during the CLD's background sync either.

With the integration verified in this article, using the S3 Tables Iceberg REST endpoint, no Lake Formation-related permissions are needed at all. If you're willing to forgo unified governance through Lake Formation in exchange for a simpler permission design, this looks like a strong option.

I hope this article is helpful to anyone considering connecting S3 Tables and Snowflake.

Top comments (0)