
I tried validating document-level ACL for S3 knowledge bases in Amazon Q - Delivering confidential documents on a per-user basis
This page has been translated by machine translation. View original
This is Ishikawa from the Cloud Business Division. In an environment where departmental materials and company-wide materials are stored in the same S3 bucket, creating an Amazon Quick knowledge base previously required either splitting the knowledge bases or not indexing confidential documents. With document-level ACL, you can now set per-user and per-group ALLOW/DENY for individual documents within an S3 knowledge base, so let's try it out.
What is Document-Level ACL
There are two ways to configure document-level ACL. One is a global ACL configuration file that centrally manages permissions per folder, and the other is placing a metadata file for each document. Also, ACL can only be enabled at knowledge base creation time and cannot be changed afterward. Document-level ACL for Amazon Quick S3 knowledge bases is "pre-filtering" only.
In ACL-enabled knowledge bases, Quick retains the permission information read during ingestion alongside the index, and evaluates it on every search. If permissions cannot be evaluated, rather than returning unfiltered results, no documents are returned at all.
Global ACL Configuration File
This is a JSON file listing ACL entries per S3 key prefix. You specify the S3 URI of this file when creating the knowledge base.
[
{
"keyPrefix": "s3://BUCKETNAME/prefix1/",
"aclEntries": [
{
"Name": "user1@example.com",
"Type": "USER",
"Access": "ALLOW"
},
{
"Name": "group1",
"Type": "GROUP",
"Access": "DENY"
}
]
}
]
Name is the email address of a user registered in Quick when the Type is USER, or the Quick group name when the Type is GROUP. This is suited for organizations with stable permission structures, and changing the file requires re-indexing the entire corresponding prefix.
Per-Document Metadata File
This method involves preparing a <document-name>.<extension>.metadata.json file for each document and writing ACL entries in the AccessControlList field.
{
"DocumentId": "meta-allow-incident-4210",
"Title": "Security Incident Report #4210",
"ContentType": "MD",
"AccessControlList": [
{
"Name": "user1@example.com",
"Type": "USER",
"Access": "ALLOW"
}
]
}
Only documents whose permissions have changed need to be re-indexed, making this approach suitable when permissions change frequently.
Trying It Out
Prerequisites
- AWS account: Quick ENTERPRISE subscription active (authentication type: IDENTITY_POOL)
- Verification region: ap-northeast-1
- AWS CLI: aws-cli/2.36.40
Verification Configuration
I placed 6 documents in a single S3 bucket to compare how ACL behaves. Each file has an embedded management number for identification.
The ACL settings are as follows. quick-acl-blog-group is a group created for verification, and I am not a member of it.
| Document | Management Number | xxxxxxxxxx@classmethod.jp | quick-acl-blog-group |
|---|---|---|---|
| global/allow/holiday-policy.md | ALPHA-GLOBAL-ALLOW | ALLOW | - |
| global/deny/exec-compensation.md | BRAVO-GLOBAL-DENY | DENY | ALLOW |
| global/noacl/uncontrolled-memo.md | CHARLIE-GLOBAL-NOACL | No ACL entry | No ACL entry |
| meta/allow/incident-report.md | DELTA-META-ALLOW | ALLOW | - |
| meta/deny/ma-valuation.md | ECHO-META-DENY | DENY | ALLOW |
| meta/noacl/orphan-note.md | FOXTROT-META-NOACL | No metadata file | No metadata file |
docs
├── acl
│ └── acl.json
├── global
│ ├── allow
│ │ └── holiday-policy.md
│ ├── deny
│ │ └── exec-compensation.md
│ └── noacl
│ └── uncontrolled-memo.md
└── meta
├── allow
│ ├── incident-report.md
│ └── incident-report.md.metadata.json
├── deny
│ ├── ma-valuation.md
│ └── ma-valuation.md.metadata.json
└── noacl
└── orphan-note.md
Step 1: Place Documents and ACL Files in S3
The contents of the global ACL file are as follows. Only two prefixes, global/allow/ and global/deny/, are listed, and global/noacl/ is intentionally omitted.
ACL file: acl/acl.json
[
{
"keyPrefix": "s3://quick-acl-blog-1b5c2683/global/allow/",
"aclEntries": [
{
"Name": "xxxxxxxxxx@classmethod.jp",
"Type": "USER",
"Access": "ALLOW"
}
]
},
{
"keyPrefix": "s3://quick-acl-blog-1b5c2683/global/deny/",
"aclEntries": [
{
"Name": "quick-acl-blog-group",
"Type": "GROUP",
"Access": "ALLOW"
},
{
"Name": "xxxxxxxxxx@classmethod.jp",
"Type": "USER",
"Access": "DENY"
}
]
}
]
global/deny/ grants ALLOW to the group and DENY to myself. The filename is arbitrary, and the path is specified with aclConfigurationFilePath when creating the knowledge base. This time I placed it at acl/acl.json. Since the acl/ prefix is not included in the crawl targets, the ACL file itself is not indexed.
Upload the documents and ACL file to S3.
% aws s3 sync work/docs/ s3://quick-acl-blog-1b5c2683/
upload: work/docs/global/allow/holiday-policy.md to s3://quick-acl-blog-1b5c2683/global/allow/holiday-policy.md
upload: work/docs/meta/noacl/orphan-note.md to s3://quick-acl-blog-1b5c2683/meta/noacl/orphan-note.md
upload: work/docs/acl/acl.json to s3://quick-acl-blog-1b5c2683/acl/acl.json
upload: work/docs/global/noacl/uncontrolled-memo.md to s3://quick-acl-blog-1b5c2683/global/noacl/uncontrolled-memo.md
upload: work/docs/meta/deny/ma-valuation.md.metadata.json to s3://quick-acl-blog-1b5c2683/meta/deny/ma-valuation.md.metadata.json
upload: work/docs/global/deny/exec-compensation.md to s3://quick-acl-blog-1b5c2683/global/deny/exec-compensation.md
upload: work/docs/meta/allow/incident-report.md to s3://quick-acl-blog-1b5c2683/meta/allow/incident-report.md
upload: work/docs/meta/allow/incident-report.md.metadata.json to s3://quick-acl-blog-1b5c2683/meta/allow/incident-report.md.metadata.json
upload: work/docs/meta/deny/ma-valuation.md to s3://quick-acl-blog-1b5c2683/meta/deny/ma-valuation.md
I placed the metadata files in the same folder as the documents. When consolidating them in a separate folder, the same directory structure as the document side must be maintained.
Step 2: Create a Data Source for the S3 Knowledge Base
Before creating the knowledge base, create a data source pointing to the S3 bucket.
% aws quicksight create-data-source \
--aws-account-id 123456789012 \
--data-source-id quick-acl-blog-s3-1b5c2683 \
--name "quick-acl-blog-s3-integration" \
--type S3_KNOWLEDGE_BASE \
--data-source-parameters '{"S3KnowledgeBaseParameters":{"BucketUrl":"s3://quick-acl-blog-1b5c2683"}}'
{
"Status": 202,
"Arn": "arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683",
"DataSourceId": "quick-acl-blog-s3-1b5c2683",
"CreationStatus": "CREATION_IN_PROGRESS"
}
It reached CREATION_SUCCESSFUL within a few seconds. This data source will be shared by all three knowledge bases, since it is possible to create multiple knowledge bases from a single S3 data source.
In the following Steps 3 through 5, we will create knowledge bases for verification.
Step 3: Create a Knowledge Base with ACL Enabled (Global ACL Method)
Enabling ACL requires configuration in two places. One is accessControlConfiguration.crawlAcl on the connector side, and the other is --access-control-configuration isACLEnabled on the knowledge base side.
First, save the connector-side configuration as kb-a-global.json.
{
"templateConfiguration": {
"template": {
"type": "S3V2",
"connectionConfiguration": {
"bucketName": "quick-acl-blog-1b5c2683",
"bucketOwnerAccountId": "123456789012"
},
"filterConfiguration": {
"inclusionPrefixes": ["global/"]
},
"accessControlConfiguration": {
"crawlAcl": true,
"aclConfigurationFilePath": "s3://quick-acl-blog-1b5c2683/acl/acl.json"
}
}
}
}
This file, passed to --knowledge-base-configuration, defines how the connector crawls and indexes the bucket. The role of each field is as follows.
| Field | Role |
|---|---|
type |
Connector type. Amazon S3 uses S3V2. This value determines what items can be written under connectionConfiguration |
connectionConfiguration |
Specifies the connection target. For S3, bucketName and bucketOwnerAccountId are required |
filterConfiguration |
Narrows down crawl targets. In addition to inclusionPrefixes, you can specify exclusionPrefixes, inclusionPatterns, exclusionPatterns, and maxFileSizeInMegaBytes |
accessControlConfiguration |
Document-level ACL settings added in this update |
Setting crawlAcl to true causes the connector to read and apply ACL. The S3 URI of the global ACL file is specified in aclConfigurationFilePath. Omitting this path switches to the metadata file method.
There is also defaultAccessType, which determines how prefixes not listed in the ACL configuration are handled, but the only supported value is ALLOW. I did not specify it this time.
inclusionPrefixes is set to global/ in order to create three separate knowledge bases from the same bucket.
% aws quicksight create-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--name "quick-acl-blog-global-acl" \
--data-source-arn arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683 \
--knowledge-base-configuration file://kb-a-global.json \
--access-control-configuration isACLEnabled=true
{
"Status": 202,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-acl-global-1b5c2683",
"KnowledgeBaseId": "kb-acl-global-1b5c2683",
"CreationStatus": "CREATING"
}
The CLI help states regarding these two settings: "Enabling only one of the two settings does not produce a fully ACL-enforced knowledge base." Enabling only one of them will not fully apply ACL.
Step 4: Create a Knowledge Base Using the Metadata Method
The second is a knowledge base using the document metadata method. Set only crawlAcl to true without specifying aclConfigurationFilePath. The target prefix is meta/. This was saved as kb-b-metadata.json.
{
"templateConfiguration": {
"template": {
"type": "S3V2",
"connectionConfiguration": {
"bucketName": "quick-acl-blog-1b5c2683",
"bucketOwnerAccountId": "123456789012"
},
"filterConfiguration": {
"inclusionPrefixes": ["meta/"]
},
"accessControlConfiguration": {
"crawlAcl": true
}
}
}
}
Create the knowledge base quick-acl-blog-metadata-acl.
% aws quicksight create-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-meta-1b5c2683 \
--name "quick-acl-blog-metadata-acl" \
--data-source-arn arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683 \
--knowledge-base-configuration file://kb-b-metadata.json \
--access-control-configuration isACLEnabled=true
{
"Status": 202,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-acl-meta-1b5c2683",
"KnowledgeBaseId": "kb-acl-meta-1b5c2683",
"CreationStatus": "CREATING"
}
The data source created in Step 2 is being reused. --data-source-arn is the same as in Step 3.
Step 5: Create a Knowledge Base Without Enabling ACL (For Comparison)
The third is a knowledge base without ACL enabled. It is created without writing accessControlConfiguration and without specifying --access-control-configuration. The target prefixes are both global/ and meta/. This was saved as kb-c-noacl.json.
{
"templateConfiguration": {
"template": {
"type": "S3V2",
"connectionConfiguration": {
"bucketName": "quick-acl-blog-1b5c2683",
"bucketOwnerAccountId": "123456789012"
},
"filterConfiguration": {
"inclusionPrefixes": ["global/", "meta/"]
}
}
}
}
% aws quicksight create-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-noacl-1b5c2683 \
--name "quick-acl-blog-no-acl" \
--data-source-arn arn:aws:quicksight:ap-northeast-1:123456789012:datasource/quick-acl-blog-s3-1b5c2683 \
--knowledge-base-configuration file://kb-c-noacl.json
{
"Status": 202,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-noacl-1b5c2683",
"KnowledgeBaseId": "kb-noacl-1b5c2683",
"CreationStatus": "CREATING"
}
This knowledge base was prepared as a comparison baseline. When a chat with an ACL-enabled knowledge base refuses to answer, it's impossible to tell from that alone whether "ACL took effect" or "the search simply didn't return a hit." Having a knowledge base with the same bucket, same documents, and same user but with only the ACL setting differing allows us to confirm that any difference in results is due to ACL.
Step 6: Grant Owner Permissions to the Knowledge Bases
Knowledge bases created via the CLI do not have an owner set unless --primary-owner-arn is specified, and Permissions in describe-knowledge-base-permissions will be empty. In this state, they cannot be selected from the console chat, so I granted permissions to all three that were created.
The permissions to grant are written in grant.json. Principal is not an IAM user but a Quick user ARN in the user/<namespace>/<username> format.
[
{
"Principal": "arn:aws:quicksight:ap-northeast-1:123456789012:user/default/xxxxxxxxxx/xxxxxxxxxx",
"Actions": [
"quicksight:DescribeKnowledgeBase",
"quicksight:DescribeKnowledgeBasePermissions",
"quicksight:UpdateKnowledgeBase",
"quicksight:UpdateKnowledgeBasePermissions",
"quicksight:DeleteKnowledgeBase",
"quicksight:CreateKnowledgeBaseRefreshSchedule",
"quicksight:DescribeKnowledgeBaseRefreshSchedule",
"quicksight:ListKnowledgeBaseRefreshSchedules",
"quicksight:UpdateKnowledgeBaseRefreshSchedule",
"quicksight:DeleteKnowledgeBaseRefreshSchedule",
"quicksight:CreateKnowledgeBaseIngestion",
"quicksight:CancelKnowledgeBaseIngestion",
"quicksight:DescribeKnowledgeBaseIngestion",
"quicksight:ListKnowledgeBaseIngestions"
]
}
]
This combination of actions was copied directly from running describe-knowledge-base-permissions on an existing knowledge base that had already been created from the console, taking the permissions assigned to the owner as-is. It is the same permission set granted to the owner when creating from the console.
Run this for all three knowledge bases created in Steps 3 through 5.
% for KB in kb-acl-global-1b5c2683 kb-acl-meta-1b5c2683 kb-noacl-1b5c2683; do
aws quicksight update-knowledge-base-permissions \
--aws-account-id 123456789012 \
--knowledge-base-id "${KB}" \
--grant-permissions file://grant.json
done
{
"Status": 200,
"KnowledgeBaseArn": "arn:aws:quicksight:ap-northeast-1:123456789012:knowledge-base/kb-acl-global-1b5c2683",
"KnowledgeBaseId": "kb-acl-global-1b5c2683",
"Permissions": [
{
"Principal": "arn:aws:quicksight:ap-northeast-1:123456789012:user/default/xxxxxxxxxx/xxxxxxxxxx",
"Actions": [
...
]
}
]
}
...
All three return Status: 200, and my user ARN is populated in Permissions. This allows the knowledge bases to be selected from the console chat.
The fact that quicksight:CreateKnowledgeBaseIngestion is included in this action list becomes relevant in Step 12 later.
Step 7: Compare Ingestion Results
The three knowledge bases automatically started their initial ingestion immediately after creation. They completed in about 2 minutes, so let's compare DocumentCount.
% aws quicksight list-knowledge-bases --aws-account-id 123456789012
KnowledgeBaseId Name Docs
kb-acl-global-1b5c2683 quick-acl-blog-global-acl 2
kb-acl-meta-1b5c2683 quick-acl-blog-metadata-acl 2
kb-noacl-1b5c2683 quick-acl-blog-no-acl 6
The two with ACL enabled ingested only 2 out of the 3 documents placed under each target prefix. uncontrolled-memo.md with no ACL entry and orphan-note.md with no metadata file were dropped. Meanwhile, the knowledge base without ACL ingested all 6 documents. This directly confirms the behavior described in the What's New announcement: "Documents not associated with ACL entries will not be ingested."
The .metadata.json files themselves are not counted as documents. Even in the knowledge base without ACL, the count was 6 rather than 8 including the 2 metadata files, suggesting that metadata files are automatically excluded from indexing targets.
The ingestion status also showed a difference.
% aws quicksight describe-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683
Status : ACTIVE
DocumentCount : 2
ACL : {"isACLEnabled": true}
acc in template : {"crawlAcl": true, "aclConfigurationFilePath": "s3://quick-acl-blog-1b5c2683/acl/acl.json"}
filter : {"inclusionPrefixes": ["global/"]}
LatestIngestionSummary {"IngestionId": "d113464a-...", "IngestionStatus": "INCOMPLETE", "StartTime": "2026-09-11T10:22:43+09:00", "EndTime": "2026-09-11T10:24:44+09:00"}
The two with ACL enabled showed INCOMPLETE, while the one without ACL showed COMPLETED. The console list also displays "Completed with issues."

Since having documents that could not be ingested prevents it from being treated as a success, in ACL operations, "missing ACL entries" will appear as sync status warnings.
Step 8: Check the Reason for Failed Ingestion in the Sync Report
The knowledge base detail screen in the console has a Sync reports tab where you can check results at the document level.


uncontrolled-memo.md showed FAILED, with an error type of VALIDATION_ERROR and the following error message:
No ACL entries found for document in ACL-enabled data source. Please ensure the document has valid ACL entries either via per-document metadata or the global ACL file.
Since the reason for failure is explicitly shown at the document level, missing ACL entries can be identified on this screen.
Step 9: Verify ACL Effectiveness via Chat
From here, we verify using the Quick chat. First, let's ask about ALPHA-GLOBAL-ALLOW, which I was granted ALLOW for.

It cited holiday-policy.md and returned the holiday schedule content.
Next, let's ask about BRAVO-GLOBAL-DENY, where I was set to DENY.

I'm sorry, but the document with management number BRAVO-GLOBAL-DENY cannot be referenced with your access permissions, so I am unable to share its contents. For the same reason, I am also unable to answer questions about the executive compensation revision.
Not only did it refuse to answer the content, but it also explicitly stated that access permissions were the reason. No source citations were displayed either.
I also checked what would happen with CHARLIE-GLOBAL-NOACL, which was not ingested due to having no ACL entry.

The response was "the document was not found." The wording differs from the DENY case, but in either case no content is returned.
For the metadata-based knowledge base, I asked about both ALLOW and DENY in a single question.

DELTA-META-ALLOW returned the incident report content, while ECHO-META-DENY responded with "access may be restricted due to permission limitations." There was no difference in behavior between the two configuration methods.
Step 10: Compare with the Knowledge Base Without ACL
To confirm that ACL is truly taking effect, let's ask about the same documents against the knowledge base without ACL.

The executive compensation revision rate of 12.5% from BRAVO-GLOBAL-DENY and the estimated valuation of 4.8 billion yen from ECHO-META-DENY were both returned as-is. Since the same S3 bucket, same documents, and same user produced different results, the only difference is the ACL configuration.
Step 11: ACL settings cannot be changed after the fact
The documentation states that "ACL settings are permanent." I checked how this is expressed in the API.
First, I tried disabling ACL on a knowledge base that had ACL enabled.
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--access-control-configuration isACLEnabled=false
An error occurred (InvalidRequestException) when calling the UpdateKnowledgeBase operation: Update request must include at least one of: name, description, media extraction configuration, knowledge base configuration or isEmailNotificationOptedForIngestionFailures.
The access control configuration is not included in the list of updatable items. --access-control-configuration is accepted, but it is not counted as an update target.
Next, I tried specifying it along with a name change.
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--name "quick-acl-blog-global-acl-renamed" \
--access-control-configuration isACLEnabled=false
An error occurred (InvalidRequestException) when calling the UpdateKnowledgeBase operation: ACL configuration in template did not match ACL configuration in request
This time, the error indicated a mismatch with crawlAcl: true on the template side. The name change was not applied either.
With that in mind, I tried updating with crawlAcl set to false on the template side. kb-a-disable-crawlacl.json is kb-a-global.json with crawlAcl set to false and aclConfigurationFilePath removed.
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--knowledge-base-configuration file://kb-a-disable-crawlacl.json
An error occurred (InvalidRequestException) when calling the UpdateKnowledgeBase operation: ACL enablement cannot be changed
A clear error: ACL enablement cannot be changed. The same applies when trying to enable ACL on a knowledge base that has it disabled. As stated in the documentation, the presence or absence of ACL decided at creation time cannot be changed afterward.
Another point we can infer from the error messages is that when updating a knowledge base with --knowledge-base-configuration, the ACL settings in the template must match --access-control-configuration. Even if you only want to change the prefix on an ACL-enabled knowledge base, you need to pass both as follows.
aws quicksight update-knowledge-base \
--aws-account-id 123456789012 \
--knowledge-base-id kb-acl-global-1b5c2683 \
--knowledge-base-configuration file://kb-a-global.json \
--access-control-configuration isACLEnabled=true
Step 12: Update ACL and apply the changes
To change permissions, update the ACL file and re-sync. I rewrote the global/deny/ setting from DENY to ALLOW.
aws s3 cp acl.json s3://quick-acl-blog-1b5c2683/acl/acl.json
The issue here was how to re-sync. As of September 11, 2026, aws quicksight has no command to initiate a knowledge base sync. create-ingestion is for datasets and requires --data-set-id.
I considered that running update-knowledge-base with the same configuration would trigger a re-sync and tried it, but after polling for 6 minutes, no new ingestion had started. The IngestionId in LatestIngestionSummary remained from the initial run.
In the end, I manually triggered a sync using the [Sync now] button on the knowledge base detail screen in the console. The action quicksight:CreateKnowledgeBaseIngestion exists as an owner-level action, so I hope CLI support will come in the future.
Note that the ACL settings configured via CLI are displayed as-is in the Advanced settings in the console.

The manual sync completed in about 2 minutes. Looking at the sync report, you can see how documents whose only change was to the ACL are handled.

exec-compensation.md was MODIFIED, and holiday-policy.md, whose ACL was not changed, was UNMODIFIED. Since the file content itself was not changed at all, we can see that a change to the ACL alone causes the document to be treated as updated.
Step 13: Verify access with Permission Checker
Each document in the sync report has "View Access Details," which allows you to use the Permission Checker to verify access on a per-user basis. Here are the results when I tried it on exec-compensation.md before the ACL update.

It shows does not have access to this document, and I can also confirm that only quick-acl-blog-group has access.
After updating the ACL to ALLOW and syncing, the display on the same screen changed.

It changed to has access to this document, and the Users and group membership was also replaced with my own email address. This means permissions have been updated at the index level.
However, asking the chat at the same time yielded different results.

About 7 minutes after the sync completed, even asking in a new conversation, the response was still denied with "I cannot provide this as you do not have access permissions." There is a discrepancy between the Permission Checker display and the chat behavior.
The documentation states that "Quick syncs ID and document permission changes on the knowledge base refresh schedule (default is every 24 hours)." Due to time constraints during this verification, I could not investigate further, but it seems safe to assume that "updating the ACL file and manually syncing will immediately reflect in chat" is not the case.
Discussion
Here is a summary of what was learned through verification.
ACL works on two levels: at ingestion and at search
Documents without an ACL entry are not indexed in the first place. This is a design to prevent accidental ingestion of confidential documents. On the other hand, documents that have an ACL entry but are set to DENY are indexed and then filtered at search time. The former can be confirmed in the sync report as FAILED, and the latter through the Permission Checker.
There is a time lag before permission changes take effect
Updates to the ACL file are not reflected until the next sync. Additionally, in this case, the chat responses did not change for about 7 minutes even after the sync completed. In situations where access revocation is urgent, it is necessary to combine other measures such as removing knowledge base sharing settings, rather than relying solely on ACL file updates. The documentation also explicitly states that knowledge base sharing and document access permissions are separate controls.
ACL enablement cannot be changed without recreating the knowledge base
As the error ACL enablement cannot be changed indicates, changing the policy afterward requires recreating the knowledge base. Since rebuilding the index takes both time and index capacity, it is safer to either enable ACL from the start, or test with a verification knowledge base before creating the production one.
Don't forget to protect the ACL file itself
Anyone who can rewrite the ACL file can grant themselves access to any document. AWS blog posts also recommend restricting s3:PutObject to the ACL file to a limited set of administrators, and using S3 versioning to maintain a change history. For this verification, I placed it in the same bucket, but in production, write permissions to the ACL file should be separated.
Operational considerations
The official documentation lists several limitations not covered in this verification.
- ACL-enabled knowledge bases are not compatible with Quick Research
- If multiple users within the same namespace share the same email address, all users with that email address will be denied access
- ACL is resolved within the namespace of the knowledge base creator
- Email addresses are case-insensitive
Some parts cannot be completed via CLI
While knowledge base creation, configuration verification, and deletion can be completed via CLI, manual sync and ACL verification (Permission Checker) required the console. When managing knowledge bases with IaC, the initial sync runs automatically, but subsequent operational operations will require using the console in combination.
Frequently Asked Questions
Q. How do I choose between the global ACL method and the metadata method?
A. In this verification, there was no difference in behavior between the global ACL file and the metadata file. The selection criteria lie on the operational side. The difference is whether the range of re-indexing when permissions change covers the entire prefix or only the target document. When using a global ACL file in an environment with a large number of documents, it is important to be aware that a single-line change can trigger large-scale re-indexing.
Q. What happens when something falls through the cracks of the settings?
A. Document-level ACL is a mechanism that determines who gets which documents returned within that knowledge base. Unlike S3 object ACLs, permissions are not attached directly to the files themselves; they only take effect when the knowledge base reads them during ingestion. The reason uncontrolled-memo.md and orphan-note.md in Step 10 could be read from an ACL-disabled knowledge base is that the knowledge base was not reading the ACL.
In other words, a user who can create a knowledge base can create an ACL-free knowledge base against the same bucket and read out all documents, including confidential ones. Simply configuring document-level ACL does not block this path.
The official documentation describes a method of using Quick's IAM policy assignments to restrict, on a per-user or per-group basis, which S3 buckets can be used to create knowledge bases. This allows you to control who can create knowledge bases from which buckets, including ACL-enabled ones. IAM policies assigned via Quick take precedence over AWS resource-level policies.
Q. What happens when ALLOW and DENY settings conflict?
A. If both ALLOW and DENY are specified for the same user or group for the same document or prefix, DENY takes precedence. The intended use case is to broadly grant ALLOW to the entire team and then narrow down specific documents or folders with DENY. This allows you to create exceptions without reorganizing the entire ACL structure.
Prefixes and documents that are not listed in the ACL settings at all are denied even without writing DENY. This is the behavior that caused uncontrolled-memo.md and orphan-note.md to not be ingested in this verification.
However, this priority order is only stated in an AWS blog post, and no description is found in the user guide. Also, in this verification, since myself (set to DENY) and quick-acl-blog-group (set to ALLOW) do not overlap, the case where the same user has both ALLOW and DENY applied simultaneously was not tested.
Closing
I tested document-level ACL in Amazon Quick S3 knowledge bases using both the AWS CLI and the console.
I was able to confirm the difference: for the same document in the same bucket, an ACL-enabled knowledge base did not return the content of a DENY document, while an ACL-disabled knowledge base did. Documents without ACL entries are not ingested at all, and the reason is explicitly shown in the sync report.
In environments where documents of different sensitivity levels coexist in the same bucket, the only option until now was to separate knowledge bases. With this update, you can keep everything in a single knowledge base while serving content on a per-person or per-group basis.
On the other hand, ACL enablement can only be decided at creation time, and there is also a time lag before permission changes take effect. It is most reliable to first map your organization's permission structure into an ACL file using a verification knowledge base, confirm that it resolves as intended with the Permission Checker, and then deploy to production.
