A correct IAM role is necessary but not sufficient. Access to an external location goes through two separate checks, and the wording of the error tells you which one failed. Here's how to find it quickly.
Step 0 โ Capture the exact error. Run a direct path read from the same compute and identity that fails:
LIST 's3://<bucket>/<prefix>/';
Then match the message:
Error text containsFailing layerGo to
PERMISSION_DENIED: User does not have READ FILES on External Location ... (or USE CATALOG / USE SCHEMA / SELECT) | Unity Catalog privileges | Step 1 |
"not accessible in the current workspace" / location greyed out in Catalog Explorer | Workspace binding | Step 2 |
"AWS IAM role does not have READ permissions on url ...", "403 Forbidden from cloud storage provider", "Failed on assuming role" | AWS (trust, S3, KMS, bucket policy) | Step 4 |
Errors about the Unity API token, or access mode | Compute | Step 3 |
Step 1 โ Unity Catalog privileges (the most common cause). A storage credential does not grant users anything. The calling principal needs privileges on the external location, and for tables, on the catalog, schema and table too. For a job, that means the job's run-as identity.
SHOW GRANTS ON EXTERNAL LOCATION `my_location`;
GRANT READ FILES ON EXTERNAL LOCATION `my_location` TO `data_engineers`;
GRANT WRITE FILES ON EXTERNAL LOCATION `my_location` TO `data_engineers`; -- writes need READ FILES too
-- for external tables / volumes on that path:
GRANT USE CATALOG ON CATALOG my_cat TO `data_engineers`;
GRANT USE SCHEMA ON SCHEMA my_cat.my_sch TO `data_engineers`;
GRANT SELECT ON TABLE my_cat.my_sch.my_tbl TO `data_engineers`; -- or READ VOLUME on a volume
Notes:
- A principal with WRITE FILES but no READ FILES still gets PERMISSION_DENIED on writes.
- Databricks recommends granting READ VOLUME on a volume rather than READ FILES on raw paths for most users.
Step 2 โ Workspace bindings. If the location (or its catalog) is set to isolated, it only works from bound workspaces.
databricks external-locations get my_location # check isolation_mode
databricks workspace-bindings get-bindings external-location my_location
Either add your workspace ID, or tick "All workspaces have access" on the location's Workspaces tab.
Step 3 โ Compute. Use a UC-capable access mode:
- Standard (formerly Shared) or Dedicated (formerly Single user) clusters, a SQL warehouse, or serverless.
- "No isolation shared" and legacy clusters can't use UC external locations.
Step 4 โ AWS side. Start with Catalog > External data > Credentials > your credential > Validate configuration, then Test connection on the external location. The results show which step fails: self-assume, read, list, write or delete. Then check, in this order:
- Trust policy: it must trust the Databricks UC master role ARN shown in the docs or on the credential page, and the role itself (self-assuming), with sts:ExternalId equal to the external ID shown on the credential page. Databricks blocks non-self-assuming roles. If you recreated the role, re-copy the external ID.
- Permissions policy: s3:ListBucket and s3:GetBucketLocation on arn:aws:s3:::<bucket>, and object actions (GetObject, plus PutObject/DeleteObject for writes) on arn:aws:s3:::<bucket>/<prefix>/*. A common mistake is scoping ListBucket to the /* object ARN.
- KMS (SSE-KMS buckets): the role needs kms:Decrypt/GenerateDataKey/Encrypt, and the KMS key policy must name the role.
- Bucket policy explicit Deny: look for conditions such as aws:SourceVpce, aws:SourceVpc, aws:PrincipalOrgID or encryption headers.
- An explicit Deny beats every Allow.
- UC metadata and validation calls can come from the Databricks control plane rather than your workspace VPC endpoint. A "deny unless my VPCE" policy can therefore break UC even when your clusters reach S3.
- If the policy requires encryption headers, set the encryption algorithm on the external location.
- SCPs and the bucket ACL: an Organizations SCP can deny S3 or KMS actions. Also, the docs require that the bucket has no S3 ACL attached; use Object Ownership = Bucket owner enforced.
Logs to check:
- AWS CloudTrail: filter on the role ARN, looking at AssumeRole events and S3/KMS AccessDenied events. The errorMessage usually names the denying policy type (identity, resource, SCP or VPC endpoint). S3 data events must be enabled to see object-level denies.
- Databricks: system.access.audit with service_name = 'unityCatalog' shows which principal and which action UC evaluated:
SELECT event_time, user_identity.email, action_name, request_params, response.status_code, response.error_message
FROM system.access.audit
WHERE service_name = 'unityCatalog'
AND event_date >= current_date() - 1
AND response.status_code <> 200
ORDER BY event_time DESC;
If Validate configuration passes, the bucket policy has no Deny, and you still get a 403, open a support ticket with the request ID from the error. That case needs a server-side look at the assume-role attempt.
Thank You
Pradeep Singh - https://www.linkedin.com/in/dbxdev