From 62d571d173b026b23c2b02aa680d8a54edb2a8bd Mon Sep 17 00:00:00 2001 From: hrehard Date: Fri, 17 Jul 2026 15:20:58 -0700 Subject: [PATCH 1/4] fix: correct cross-account access in LF Feature Store notebook Two fixes so v3-feature-store-lake-formation-cross-account.ipynb runs end-to-end across the three accounts: 1. Online-store PutRecord/GetRecord run as the Feature Group owner (central account), not the producer. SageMaker Feature Store has no cross-account resource policy for the online store, so the producer session gets AccessDenied ('no resource-based policy allows sagemaker:PutRecord'). Cross-account sharing is via the offline store. 2. Grant the producer account cross-account Glue Data Catalog access before the PyIceberg write. GetTemporaryGlueTableCredentials requires it; without it the write fails with 'Insufficient Glue permissions to access table'. The notebook previously only granted the consumer (and only after this write). --- ...e-store-lake-formation-cross-account.ipynb | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb index 14253509a6..3c24b4abda 100644 --- a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb +++ b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb @@ -348,8 +348,10 @@ "source": [ "from datetime import datetime, timezone\n", "\n", - "# Use the producer session to call PutRecord on the shared Feature Group\n", - "sm_runtime = producer_acc_boto_session.client('sagemaker-featurestore-runtime')\n", + "# The online store is single-account: SageMaker has no cross-account resource\n", + "# policy for PutRecord/GetRecord, so these run as the Feature Group owner\n", + "# (central account). Cross-account sharing is via the OFFLINE store (below).\n", + "sm_runtime = central_acc_boto_session.client('sagemaker-featurestore-runtime')\n", "\n", "sm_runtime.put_record(\n", " FeatureGroupName=feature_group_arn,\n", @@ -512,6 +514,47 @@ "**Hybrid access mode must be disabled on Glue Table and Database in order for cross-account credential vending to work**" ] }, + { + "cell_type": "code", + "id": "nte-producer-glue-access", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Grant the producer account cross-account Glue Data Catalog access to the shared\n", + "# table BEFORE the PyIceberg write. GetTemporaryGlueTableCredentials (below) needs\n", + "# it; without it the write fails with 'Insufficient Glue permissions to access\n", + "# table'. Mirrors the consumer Glue resource policy set later in the notebook.\n", + "import json\n", + "_db_lower = database_name.lower()\n", + "_region = central_acc_boto_session.region_name\n", + "_producer_stmt = {\n", + " 'Sid': 'AllowProducerGlueAccess',\n", + " 'Effect': 'Allow',\n", + " 'Principal': {'AWS': f'arn:aws:iam::{producer_account}:root'},\n", + " 'Action': 'glue:*',\n", + " 'Resource': [\n", + " f'arn:aws:glue:{_region}:{central_account}:catalog',\n", + " f'arn:aws:glue:{_region}:{central_account}:database/{_db_lower}',\n", + " f'arn:aws:glue:{_region}:{central_account}:table/{_db_lower}/*',\n", + " ],\n", + "}\n", + "try:\n", + " _cur = central_glue_client.get_resource_policy()\n", + " _existing = json.loads(_cur['PolicyInJson'])\n", + " _stmts = [s for s in _existing.get('Statement', []) if s.get('Sid') != 'AllowProducerGlueAccess']\n", + " _stmts.append(_producer_stmt)\n", + " central_glue_client.put_resource_policy(\n", + " PolicyInJson=json.dumps({'Version': '2012-10-17', 'Statement': _stmts}),\n", + " PolicyHashCondition=_cur['PolicyHash'], EnableHybrid='TRUE')\n", + "except central_glue_client.exceptions.EntityNotFoundException:\n", + " central_glue_client.put_resource_policy(\n", + " PolicyInJson=json.dumps({'Version': '2012-10-17', 'Statement': [_producer_stmt]}),\n", + " EnableHybrid='TRUE')\n", + "print('Granted producer account cross-account Glue access')\n", + "time.sleep(8) # allow the resource policy to propagate\n" + ] + }, { "cell_type": "code", "execution_count": null, From fe15e1f7746c64da47b7a0c183313d50d350a57f Mon Sep 17 00:00:00 2001 From: hrehard Date: Fri, 17 Jul 2026 15:40:55 -0700 Subject: [PATCH 2/4] fix: make producer Glue-access cell self-contained (import time) --- .../v3-feature-store-lake-formation-cross-account.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb index 3c24b4abda..bf544eacb2 100644 --- a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb +++ b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb @@ -525,7 +525,7 @@ "# table BEFORE the PyIceberg write. GetTemporaryGlueTableCredentials (below) needs\n", "# it; without it the write fails with 'Insufficient Glue permissions to access\n", "# table'. Mirrors the consumer Glue resource policy set later in the notebook.\n", - "import json\n", + "import json, time\n", "_db_lower = database_name.lower()\n", "_region = central_acc_boto_session.region_name\n", "_producer_stmt = {\n", From 775f19a025c5d99ae37dc3950befa9a406333c39 Mon Sep 17 00:00:00 2001 From: hrehard Date: Fri, 17 Jul 2026 15:46:32 -0700 Subject: [PATCH 3/4] docs: shorten PutRecord owner comment to match notebook style --- .../v3-feature-store-lake-formation-cross-account.ipynb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb index bf544eacb2..962b6948e3 100644 --- a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb +++ b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb @@ -348,9 +348,7 @@ "source": [ "from datetime import datetime, timezone\n", "\n", - "# The online store is single-account: SageMaker has no cross-account resource\n", - "# policy for PutRecord/GetRecord, so these run as the Feature Group owner\n", - "# (central account). Cross-account sharing is via the OFFLINE store (below).\n", + "# Online store is single-account: PutRecord/GetRecord run as the FG owner (central)\n", "sm_runtime = central_acc_boto_session.client('sagemaker-featurestore-runtime')\n", "\n", "sm_runtime.put_record(\n", From dbafecd79aa608231f4e3b1782cf0e27572b9dde Mon Sep 17 00:00:00 2001 From: hrehard Date: Fri, 17 Jul 2026 16:42:32 -0700 Subject: [PATCH 4/4] docs: remove comments from producer Glue-access cell --- .../v3-feature-store-lake-formation-cross-account.ipynb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb index 962b6948e3..97a7eb7612 100644 --- a/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb +++ b/v3-examples/ml-ops-examples/v3-feature-store-examples/lake-formation/v3-feature-store-lake-formation-cross-account.ipynb @@ -519,10 +519,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Grant the producer account cross-account Glue Data Catalog access to the shared\n", - "# table BEFORE the PyIceberg write. GetTemporaryGlueTableCredentials (below) needs\n", - "# it; without it the write fails with 'Insufficient Glue permissions to access\n", - "# table'. Mirrors the consumer Glue resource policy set later in the notebook.\n", "import json, time\n", "_db_lower = database_name.lower()\n", "_region = central_acc_boto_session.region_name\n", @@ -550,7 +546,7 @@ " PolicyInJson=json.dumps({'Version': '2012-10-17', 'Statement': [_producer_stmt]}),\n", " EnableHybrid='TRUE')\n", "print('Granted producer account cross-account Glue access')\n", - "time.sleep(8) # allow the resource policy to propagate\n" + "time.sleep(8)\n" ] }, {