From a2f59f1ee9fcbb21488a7894f7e5878059ab8309 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 2 Nov 2026 12:41:21 -0800 Subject: [PATCH] S3 Vector Bucket Security: tag conditions in vector bucket policies --- S3-Vector-Bucket-Security.md | 56 ++++++++++++++++++++++++++++++++++++ S3-Vectors-API.md | 1 + 2 files changed, 57 insertions(+) diff --git a/S3-Vector-Bucket-Security.md b/S3-Vector-Bucket-Security.md index 8dfa78f..604d5f7 100644 --- a/S3-Vector-Bucket-Security.md +++ b/S3-Vector-Bucket-Security.md @@ -93,6 +93,58 @@ An identity with no vector grants of its own can then call `QueryVectors` on `id `GetVectorBucketPolicy` returns the document as it was stored; with no policy it returns `NotFoundException`. `DeleteVectorBucketPolicy` removes it, and with it every access the policy granted. +## Tag conditions + +Tag conditions need a build that includes [#12351](https://github.com/seaweedfs/seaweedfs/pull/12351). + +A statement in a vector bucket policy can have a `Condition` on the [[S3 Vectors API#tags|tags]] of the bucket or index, or on the tags a request sets: + +| Condition key | Compared with | +|---------------|---------------| +| `aws:ResourceTag/` | The bucket's tag on a bucket request. On a request that names an index, the index's own tag. | +| `s3vectors:VectorBucketTag/` | The bucket's tag, on bucket and index requests alike. | +| `aws:RequestTag/` | The tag that `TagResource` or `CreateIndex` sets. | +| `aws:TagKeys` | The keys that `TagResource` or `CreateIndex` sets, or that `UntagResource` removes. | + +Example, on bucket `team-a` tagged `env=prod`, whose index `idx` is tagged `stage=live`: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Principal": {"AWS": "reader"}, "Action": "s3vectors:GetIndex", + "Resource": "arn:aws:s3vectors:us-east-1:000000000000:bucket/team-a/index/*", + "Condition": {"StringEquals": {"aws:ResourceTag/stage": "live"}}}, + {"Effect": "Allow", "Principal": {"AWS": "reader"}, "Action": "s3vectors:QueryVectors", + "Resource": "arn:aws:s3vectors:us-east-1:000000000000:bucket/team-a/index/*", + "Condition": {"StringEquals": {"s3vectors:VectorBucketTag/env": "prod"}}}, + {"Effect": "Allow", "Principal": {"AWS": "writer"}, "Action": "s3vectors:TagResource", + "Resource": "arn:aws:s3vectors:us-east-1:000000000000:bucket/team-a/index/*", + "Condition": {"StringEquals": {"aws:RequestTag/team": "search"}, + "ForAllValues:StringEquals": {"aws:TagKeys": ["team"]}}} + ] +} +``` + +* `reader` can call `GetIndex` on `idx`. On an index without `stage=live` it gets `AccessDeniedException`. +* `reader` can query every index in the bucket. Once the bucket is retagged `env=dev`, its next query gets `AccessDeniedException`. +* `writer` can tag an index with `{"team": "search"}`. Tagging with `{"team": "other"}`, or with `{"team": "search", "env": "dev"}`, gets `AccessDeniedException`. + +A `Deny` with a tag condition protects the resources carrying the tag. This statement keeps every index tagged `protected=true` from being deleted, by any identity except the admin described above: + +```json +{"Effect": "Deny", "Principal": "*", "Action": "s3vectors:DeleteIndex", + "Resource": "arn:aws:s3vectors:us-east-1:000000000000:bucket/team-a/index/*", + "Condition": {"StringEquals": {"aws:ResourceTag/protected": "true"}}} +``` + +* Conditions apply in bucket policies only. The actions of an identity in the S3 configuration have no conditions. +* `CreateVectorBucket` cannot be limited by tags, because a new bucket has no policy yet. +* A tag that is not set matches no value. A statement that allows `CreateIndex` only with `aws:RequestTag/team` = `search` also refuses a `CreateIndex` without tags. +* A tag change applies from the next request. +* The operators are the ones in [[S3 Policy Conditions#supported-condition-operators]], including the `ForAllValues:` and `ForAnyValue:` forms. The `IfExists` forms are rejected. +* Condition keys are case-sensitive: `aws:resourcetag/env` is rejected. + ## The engine's identity `weed-vector` reads and writes every index's files with the identity passed in its `--storage-option` flags, whoever called the API. Authorization happens in the gateway before the engine is called. In our tests the engine used an identity with the `Admin` action. On a SeaweedFS without identities it needs none. @@ -107,3 +159,7 @@ An identity with no vector grants of its own can then call `QueryVectors` on `id | A resource ARN of another service, e.g. `arn:aws:s3tables:...` | `resource "arn:aws:s3tables:..." is not enforceable in a vector bucket policy` | | `NotAction` | `NotAction is not supported` | | `NotResource` | `NotResource is not supported` | +| A condition on a key other than those under [Tag conditions](#tag-conditions), e.g. `aws:SourceIp` | `condition key "aws:SourceIp" is not supported in a vector bucket policy` | +| An unsupported condition operator, e.g. `StringEqualsIfExists` | `condition operator "StringEqualsIfExists" is not supported` | +| A condition value that is `null`, an object or an empty list | `condition StringEquals on "aws:ResourceTag/env" needs a string, number or boolean, or a non-empty list of them` | +| A condition field not spelled `Condition`, e.g. `condition` | `condition must be spelled Condition` | diff --git a/S3-Vectors-API.md b/S3-Vectors-API.md index f7bbe18..9a66e96 100644 --- a/S3-Vectors-API.md +++ b/S3-Vectors-API.md @@ -53,6 +53,7 @@ s3v.list_tags_for_resource(resourceArn=bucket_arn)["tags"] # {"team": "search"} * Keys are 1 to 128 characters and values up to 256, made of letters, digits, spaces and `_ . : / = + - @`. * `TagResource` overwrites the value of a key that is already set. `UntagResource` ignores keys that are not set. * A missing bucket or index gets `NotFoundException`. +* A vector bucket policy can allow or deny requests by these tags. See [[S3 Vector Bucket Security#tag-conditions]]. ## Distance metrics