AWS Builder Center

Snowflake → AWS Catalog Federation via LakeFormation

Create a table in Snowflake → it automatically appears in AWS (Glue/LakeFormation) → queryable from Athena/SageMaker without any manual registration.

In this post, we show you how to connect the AWS Glue Data Catalog to Snowflake Horizon Catalog and query the data using AWS analytics services. We cover how to set up catalogs in Horizon Catalog and configure required permissions, create and configure the federation connection in AWS Glue , implement fine-grained access controls using AWS Lake Formation, and finally, query federated tables using Amazon Athena . This step-by-step approach guides you through the complete process of establishing a integration between your Snowflake and AWS data environments.

Step 1: Create S3 Bucket (AWS)

AWS Console → S3 → Create bucket:
  • Name: sf-federation-demo-1111222333
  • Region: us-west-2

Step 2: Create IAM Role for External Volume (AWS)

AWS Console → IAM → Roles → Create role:
  • Trusted entity: Custom trust policy
  • Paste this placeholder (we'll update it in Step 5):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:user/xxxx-x-xxxxxxxx"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "PLACEHOLDER"
}
}
}]
}
  • Role name: sf-federation-external-volume-role
  • After creation, add an inline policy named s3-readwrite:
1
2
3
4
5
6
7
8
9
10
11
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket", "s3:GetBucketLocation"],
"Resource": [
"arn:aws:s3:::sf-federation-demo-111122223333",
"arn:aws:s3:::sf-federation-demo-111122223333/*"
]
}]
}

Step 3: Create External Volume (Snowflake)

1
2
3
4
5
6
7
8
CREATE OR REPLACE EXTERNAL VOLUME sf_iceberg_volume
STORAGE_LOCATIONS = ((
NAME = 'sf-s3-storage'
STORAGE_PROVIDER = 'S3'
STORAGE_BASE_URL = 's3://sf-federation-demo-111122223333/'
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::111122223333:role/sf-federation-external-volume-role'
))
ALLOW_WRITES = TRUE;

Step 4: Get External ID (Snowflake)

1
DESCRIBE EXTERNAL VOLUME sf_iceberg_volume;
Note down:
  • STORAGE_AWS_IAM_USER_ARN (e.g. arn:aws:iam::444455556666:user/xxxx-x-xxxxxxxx)
  • STORAGE_AWS_EXTERNAL_ID (e.g. AWSPARTNER_SFCRole=2_xxxxx=)

Step 5: Update IAM Role Trust Policy with Real External ID (AWS)

AWS Console → IAM → Roles → sf-federation-external-volume-role → Trust relationships → Edit:
Replace the placeholder with real values from Step 4:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "<STORAGE_AWS_IAM_USER_ARN from Step 4>"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "<STORAGE_AWS_EXTERNAL_ID from Step 4>"
}
}
}]
}
⏱ Wait 30 seconds for IAM propagation before proceeding.

Step 6: Create Database, Schema, and Iceberg Table (Snowflake)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CREATE DATABASE IF NOT EXISTS SF_FEDERATION_DEMO;
CREATE SCHEMA IF NOT EXISTS SF_FEDERATION_DEMO.PUBLIC;

-- IMPORTANT: Athena does not support Iceberg v3. Force v2.
ALTER ACCOUNT SET ICEBERG_VERSION_DEFAULT = 2;

CREATE OR REPLACE ICEBERG TABLE SF_FEDERATION_DEMO.PUBLIC.customer_complaints (
complaint_id INT,
customer_name STRING,
complaint_type STRING,
severity STRING,
status STRING,
created_at TIMESTAMP
)
CATALOG = 'SNOWFLAKE'
EXTERNAL_VOLUME = 'sf_iceberg_volume'
BASE_LOCATION = 'customer_complaints/';

INSERT INTO SF_FEDERATION_DEMO.PUBLIC.customer_complaints VALUES
(1, 'Alice Johnson', 'Billing', 'High', 'Open', '2026-06-01 10:00:00'),
(2, 'Bob Smith', 'Service', 'Medium', 'Resolved', '2026-06-10 14:30:00'),
(3, 'Carol Williams', 'Product', 'Low', 'In Progress', '2026-06-15 09:00:00');

-- Verify data is written to S3
SELECT * FROM SF_FEDERATION_DEMO.PUBLIC.customer_complaints;
If you get Error assuming AWS_ROLE → Step 5 trust policy hasn't propagated. Wait 30s and retry.

Step 7: Configure Access Control (Snowflake)

1
2
3
4
5
6
CREATE OR REPLACE ROLE sf_federation_role;
GRANT USAGE ON DATABASE SF_FEDERATION_DEMO TO ROLE sf_federation_role;
GRANT USAGE ON SCHEMA SF_FEDERATION_DEMO.PUBLIC TO ROLE sf_federation_role;
GRANT SELECT ON ALL TABLES IN SCHEMA SF_FEDERATION_DEMO.PUBLIC TO ROLE sf_federation_role;
GRANT SELECT ON FUTURE ICEBERG TABLES IN SCHEMA SF_FEDERATION_DEMO.PUBLIC TO ROLE sf_federation_role;
GRANT ROLE sf_federation_role TO ROLE ACCOUNTADMIN;

Step 8: Create Service User and PAT (Snowflake)

1
2
3
4
5
6
7
CREATE OR REPLACE USER horizon_rest_srv TYPE = SERVICE DEFAULT_ROLE = sf_federation_role;
GRANT ROLE sf_federation_role TO USER horizon_rest_srv;

ALTER USER horizon_rest_srv ADD PROGRAMMATIC ACCESS TOKEN horizon_rest_pat
DAYS_TO_EXPIRY = 30
ROLE_RESTRICTION = 'SF_FEDERATION_ROLE'
COMMENT = 'PAT for AWS Glue catalog federation';
Copy the PAT value from the output. You need it in the next step.

Step 9: Exchange PAT for Access Token (Terminal)

1
2
3
4
5
curl -s -X POST 'https://your-snowflake-account.snowflakecomputing.com/polaris/api/catalog/v1/oauth/tokens' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=session:role:SF_FEDERATION_ROLE' \
--data-urlencode 'client_secret=<YOUR_PAT_VALUE>'
The response looks like:
1
{"access_token":"eyJraWQi...long string...Qog","token_type":"bearer","expires_in":3600}
Copy ONLY the value between the quotes after "access_token":" — the long eyJ... string. Do NOT copy access_token": or any other part of the JSON.
Token expires in 1 hour. For production, automate refresh with a scheduled Lambda (see end of doc).

Step 10: Store Bearer Token in Secrets Manager (AWS)

Option A — Console:
AWS Console → Secrets Manager → sf-federation/horizon-token → Retrieve secret value → Edit:
  • Key: BEARER_TOKEN
  • Value: Paste ONLY the eyJ... token string (no quotes, no newlines, single line)
  • Click Save
If creating for the first time: Store a new secret → Other type of secret → Key: BEARER_TOKEN, Value: the token → Name: sf-federation/horizon-token
Option B — CloudShell (recommended to avoid newline issues):
⚠️ IMPORTANT: The token must be on a SINGLE LINE. Do not let the command wrap across multiple lines. Paste the entire command as one line, or use the method below:
1
2
3
4
5
6
7
8
# First, store your token in a variable (paste token after the = with no spaces)
TOKEN="eyJraWQi...paste your full token here...Qog"

# Then update the secret using the variable
aws secretsmanager put-secret-value \
--secret-id sf-federation/horizon-token \
--secret-string "{\"BEARER_TOKEN\":\"$TOKEN\"}" \
--region us-west-2
⚠️ Key MUST be BEARER_TOKEN — this is the reserved key for Custom authentication.
⚠️ The token value must have NO newlines, NO spaces, NO line breaks. If CloudShell wraps the display, that's fine — but the actual value must be continuous.

Step 11: Create IAM Role for LakeFormation (AWS)

AWS Console → IAM → Roles → Create role:
  • Trusted entity: Custom trust policy:
1
2
3
4
5
6
7
8
9
10
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Service": ["glue.amazonaws.com", "lakeformation.amazonaws.com"]
},
"Action": "sts:AssumeRole"
}]
}
  • Role name: sf-federation-lf-role
  • Add inline policy named federation-access:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:PutSecretValue"],
"Resource": ["arn:aws:secretsmanager:us-west-2:11112223333:secret:sf-federation/*"]
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"],
"Resource": [
"arn:aws:s3:::sf-federation-demo-11112223333",
"arn:aws:s3:::sf-federation-demo-11112223333/*"
]
}
]
}

Step 12: Create Federated Catalog (AWS — LakeFormation Console)

AWS Console → LakeFormation → Catalogs → Create Catalog
  1. Data source: Select Snowflake Horizon Catalog
  2. Catalog details:
    • Catalog name: sf-federation-demo
    • Catalog name in Snowflake: SF_FEDERATION_DEMO (must match exact Snowflake database name, UPPERCASE)
  3. Connection details: Select New connection configurations
    • Connection name: sf-horizon-federation-connection
    • Workspace URL: https://YOUR-SNOWFLAKE-ACCOUNT.snowflakecomputing.com
    • Casing type: Uppercase only
    • Authentication type: Custom
    • Secret: Select sf-federation/horizon-token from dropdown
  4. Registration:
    • IAM Role: Select sf-federation-lf-role
  5. Click Test Connection
    • If successful → green checkmark
    • If fails → check token validity (1hr expiry) and role permissions
  6. Click Create Catalog

Step 13: Grant LakeFormation Permissions (AWS)

AWS Console → LakeFormation → Data permissions → Grant:
  • Principal: Your IAM user/role (whoever will query from Athena)
  • Resource: Catalog → sf-federation-demo
  • Permissions: ALL

Step 14: Test from Athena (AWS)

Open Athena → Select data source AwsDataCatalog → select catalog sf-federation-demo:
1
SELECT * FROM "sf-federation-demo"."PUBLIC"."CUSTOMER_COMPLAINTS" LIMIT 10;
⚠️ Use UPPERCASE for schema and table names (Snowflake stores them in uppercase).

Step 15: Verify Auto-Discovery (Snowflake + Athena) ✅ CONFIRMED

Create a new table in Snowflake:
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE OR REPLACE ICEBERG TABLE SF_FEDERATION_DEMO.PUBLIC.service_orders (
order_id INT,
customer_name STRING,
service STRING,
amount FLOAT
)
CATALOG = 'SNOWFLAKE'
EXTERNAL_VOLUME = 'sf_iceberg_volume'
BASE_LOCATION = 'service_orders/';

INSERT INTO SF_FEDERATION_DEMO.PUBLIC.service_orders VALUES
(1, 'Dave Brown', 'Premium Support', 499.99),
(2, 'Eve Clark', 'Installation', 299.99);
Then in Athena — no manual registration needed:
1
SELECT * FROM "sf-federation-demo"."PUBLIC"."SERVICE_ORDERS" LIMIT 10;

Troubleshooting

ErrorCauseFix
Error assuming AWS_ROLE in SnowflakeTrust policy wrong or missing External IDDESCRIBE EXTERNAL VOLUME → update trust policy in Step 5 → wait 30s
invalid_scope on curl token exchangeRole name wrong (must be UPPERCASE) or Horizon not enabledUse uppercase role name. If still fails, contact Snowflake support.
ALLOW_WRITES error in SnowflakeExternal volume set to read-onlyAlready set to TRUE in Step 3. If not: ALTER EXTERNAL VOLUME sf_iceberg_volume SET ALLOW_WRITES = TRUE;
Test Connection fails in LF consoleToken expired (1hr) or secret key wrongRe-run Step 9, update secret value in Secrets Manager, retry
Query hangs in AthenaLakeFormation permission not grantedStep 13 — grant ALL on the federated catalog to your principal
Table not visibleCasing mismatchUse UPPERCASE in Athena queries: "PUBLIC"."TABLE_NAME"
Workspace URL error in consoleMissing or extra suffixUse just https://account.snowflakecomputing.com — console adds the rest

Token Refresh (for production)

The PAT-exchanged bearer token expires in 1 hour. For production use:
  1. Create a Lambda function that:
    • Reads the PAT from Secrets Manager (store PAT as a separate secret)
    • Calls the Polaris token endpoint
    • Updates the BEARER_TOKEN in sf-federation/horizon-token
  2. Schedule it via EventBridge every 50 minutes
  3. The Glue connection always reads the latest secret value

Cleanup

AWS Console:
  1. LakeFormation → Catalogs → Delete sf-federation-demo
  2. LakeFormation → Data locations → Deregister the connection
  3. Glue → Connections → Delete sf-horizon-federation-connection
  4. IAM → Roles → Delete sf-federation-lf-role and sf-federation-external-volume-role
  5. Secrets Manager → Delete sf-federation/horizon-token
  6. S3 → Empty and delete sf-federation-demo-11122223333
Snowflake:
1
2
3
4
5
6
7
DROP TABLE IF EXISTS SF_FEDERATION_DEMO.PUBLIC.customer_complaints;
DROP TABLE IF EXISTS SF_FEDERATION_DEMO.PUBLIC.service_orders;
DROP SCHEMA IF EXISTS SF_FEDERATION_DEMO.PUBLIC;
DROP DATABASE IF EXISTS SF_FEDERATION_DEMO;
DROP EXTERNAL VOLUME IF EXISTS sf_iceberg_volume;
DROP USER IF EXISTS horizon_rest_srv;
DROP ROLE IF EXISTS sf_federation_role;
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article