aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--weed/s3api/auth_credentials.go4
-rw-r--r--weed/s3api/policy_engine/README_POLICY_ENGINE.md119
-rw-r--r--weed/s3api/policy_engine/conditions.go10
-rw-r--r--weed/s3api/policy_engine/engine_test.go7
-rw-r--r--weed/s3api/policy_engine/types.go2
-rw-r--r--weed/s3api/s3api_bucket_handlers.go3
6 files changed, 123 insertions, 22 deletions
diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go
index ec9edb6a0..378788084 100644
--- a/weed/s3api/auth_credentials.go
+++ b/weed/s3api/auth_credentials.go
@@ -582,9 +582,7 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
// - No policy or indeterminate → fall through to IAM checks
if iam.policyEngine != nil && bucket != "" {
principal := buildPrincipalARN(identity)
- // Evaluate bucket policy with request context for accurate action resolution
- // Note: objectEntry is nil here as we don't have the entry at auth time
- // For tag-based conditions to work, the caller should re-evaluate with entry after fetching it
+ // Evaluate bucket policy (objectEntry nil - not yet fetched at auth time)
allowed, evaluated, err := iam.policyEngine.EvaluatePolicy(bucket, object, string(action), principal, r, nil)
if err != nil {
diff --git a/weed/s3api/policy_engine/README_POLICY_ENGINE.md b/weed/s3api/policy_engine/README_POLICY_ENGINE.md
index 70dbf37f1..efb19f68d 100644
--- a/weed/s3api/policy_engine/README_POLICY_ENGINE.md
+++ b/weed/s3api/policy_engine/README_POLICY_ENGINE.md
@@ -135,8 +135,34 @@ Standard AWS condition keys are supported:
- `aws:UserAgent` - Client user agent
- `s3:x-amz-acl` - Requested ACL
- `s3:VersionId` - Object version ID
+- `s3:ExistingObjectTag/<tag-key>` - Value of an existing object tag (see example below)
- And many more...
+### 5. Object Tag-Based Access Control
+
+You can control access based on object tags using `s3:ExistingObjectTag/<tag-key>`:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": "*",
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::my-bucket/*",
+ "Condition": {
+ "StringEquals": {
+ "s3:ExistingObjectTag/status": ["public"]
+ }
+ }
+ }
+ ]
+}
+```
+
+This allows anonymous access only to objects that have a tag `status=public`.
+
## Policy Evaluation
### Evaluation Order (AWS-Compatible)
@@ -212,6 +238,56 @@ Standard AWS condition keys are supported:
}
```
+### Tag-Based Access Control
+
+Allow public read only for objects tagged as public:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": "*",
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::my-bucket/*",
+ "Condition": {
+ "StringEquals": {
+ "s3:ExistingObjectTag/visibility": ["public"]
+ }
+ }
+ }
+ ]
+}
+```
+
+Deny access to confidential objects:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": "*",
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::my-bucket/*"
+ },
+ {
+ "Effect": "Deny",
+ "Principal": "*",
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::my-bucket/*",
+ "Condition": {
+ "StringEquals": {
+ "s3:ExistingObjectTag/classification": ["confidential", "secret"]
+ }
+ }
+ }
+ ]
+}
+```
+
## Integration
### For Existing SeaweedFS Users
@@ -270,10 +346,39 @@ go test -v -run TestPolicyValidation
## Compatibility
-- ✅ **Full backward compatibility** with existing `identities.json`
-- ✅ **AWS S3 API compatibility** for bucket policies
-- ✅ **Standard condition operators** and keys
-- ✅ **Proper evaluation precedence** (Deny > Allow > Default Deny)
-- ✅ **Performance optimized** with caching and compiled patterns
-
-The policy engine provides a seamless upgrade path from SeaweedFS's existing simple IAM system to full AWS S3-compatible policies, giving you the best of both worlds: simplicity for basic use cases and power for complex enterprise scenarios. \ No newline at end of file
+- Full backward compatibility with existing `identities.json`
+- AWS S3 API compatibility for bucket policies
+- Standard condition operators and keys
+- Proper evaluation precedence (Deny > Allow > Default Deny)
+- Performance optimized with caching and compiled patterns
+
+The policy engine provides a seamless upgrade path from SeaweedFS's existing simple IAM system to full AWS S3-compatible policies, giving you the best of both worlds: simplicity for basic use cases and power for complex enterprise scenarios.
+
+## Feature Status
+
+### Implemented
+
+| Feature | Description |
+|---------|-------------|
+| Bucket Policies | Full AWS S3-compatible bucket policies |
+| Condition Operators | StringEquals, IpAddress, Bool, DateGreaterThan, etc. |
+| `aws:SourceIp` | IP-based access control with CIDR support |
+| `aws:SecureTransport` | Require HTTPS |
+| `aws:CurrentTime` | Time-based access control |
+| `s3:ExistingObjectTag/<key>` | Tag-based access control for existing objects |
+| Wildcard Patterns | Support for `*` and `?` in actions and resources |
+| Principal Matching | `*`, account IDs, and user ARNs |
+
+### Planned
+
+| Feature | GitHub Issue |
+|---------|--------------|
+| `s3:RequestObjectTag/<key>` | For tag conditions on PUT requests |
+| `s3:RequestObjectTagKeys` | Check which tag keys are in request |
+| `s3:x-amz-content-sha256` | Content hash condition |
+| `s3:x-amz-server-side-encryption` | SSE condition |
+| `s3:x-amz-storage-class` | Storage class condition |
+| Cross-account access | Access across different accounts |
+| VPC Endpoint policies | Network-level policies |
+
+For feature requests or to track progress, see the [GitHub Issues](https://github.com/seaweedfs/seaweedfs/issues). \ No newline at end of file
diff --git a/weed/s3api/policy_engine/conditions.go b/weed/s3api/policy_engine/conditions.go
index feb582e89..ffbae51e6 100644
--- a/weed/s3api/policy_engine/conditions.go
+++ b/weed/s3api/policy_engine/conditions.go
@@ -10,6 +10,7 @@ import (
"time"
"github.com/seaweedfs/seaweedfs/weed/glog"
+ "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
)
// LRUNode represents a node in the doubly-linked list for efficient LRU operations
@@ -705,12 +706,9 @@ func GetConditionEvaluator(operator string) (ConditionEvaluator, error) {
}
}
-// ExistingObjectTagPrefix is the prefix for object tag condition keys
+// ExistingObjectTagPrefix is the prefix for S3 policy condition keys
const ExistingObjectTagPrefix = "s3:ExistingObjectTag/"
-// ObjectTagMetadataPrefix is the prefix used to store tags in entry.Extended
-const ObjectTagMetadataPrefix = "X-Amz-Tagging-"
-
// EvaluateConditions evaluates all conditions in a policy statement
// objectEntry is the object's metadata from entry.Extended (can be nil)
func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]string, objectEntry map[string][]byte) bool {
@@ -733,7 +731,7 @@ func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]
if strings.HasPrefix(key, ExistingObjectTagPrefix) {
// Extract tag value from entry.Extended using the tag prefix
tagKey := key[len(ExistingObjectTagPrefix):]
- metadataKey := ObjectTagMetadataPrefix + tagKey
+ metadataKey := s3_constants.AmzObjectTaggingPrefix + tagKey
if objectEntry != nil {
if tagValue, exists := objectEntry[metadataKey]; exists {
contextVals = []string{string(tagValue)}
@@ -784,7 +782,7 @@ func EvaluateConditionsLegacy(conditions map[string]interface{}, contextValues m
// Handle s3:ExistingObjectTag/<tag-key> condition keys
if strings.HasPrefix(key, ExistingObjectTagPrefix) {
tagKey := key[len(ExistingObjectTagPrefix):]
- metadataKey := ObjectTagMetadataPrefix + tagKey
+ metadataKey := s3_constants.AmzObjectTaggingPrefix + tagKey
if objectEntry != nil {
if tagValue, exists := objectEntry[metadataKey]; exists {
contextVals = []string{string(tagValue)}
diff --git a/weed/s3api/policy_engine/engine_test.go b/weed/s3api/policy_engine/engine_test.go
index 2b9565738..7ad2ca35b 100644
--- a/weed/s3api/policy_engine/engine_test.go
+++ b/weed/s3api/policy_engine/engine_test.go
@@ -5,6 +5,7 @@ import (
"net/url"
"testing"
+ "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
@@ -749,7 +750,7 @@ func TestExistingObjectTagCondition(t *testing.T) {
}
entry := make(map[string][]byte)
for k, v := range tags {
- entry["X-Amz-Tagging-"+k] = []byte(v)
+ entry[s3_constants.AmzObjectTaggingPrefix+k] = []byte(v)
}
return entry
}
@@ -840,7 +841,7 @@ func TestExistingObjectTagConditionMultipleTags(t *testing.T) {
tagsToEntry := func(tags map[string]string) map[string][]byte {
entry := make(map[string][]byte)
for k, v := range tags {
- entry["X-Amz-Tagging-"+k] = []byte(v)
+ entry[s3_constants.AmzObjectTaggingPrefix+k] = []byte(v)
}
return entry
}
@@ -934,7 +935,7 @@ func TestExistingObjectTagDenyPolicy(t *testing.T) {
}
entry := make(map[string][]byte)
for k, v := range tags {
- entry["X-Amz-Tagging-"+k] = []byte(v)
+ entry[s3_constants.AmzObjectTaggingPrefix+k] = []byte(v)
}
return entry
}
diff --git a/weed/s3api/policy_engine/types.go b/weed/s3api/policy_engine/types.go
index a8f822fb8..c6c76b55f 100644
--- a/weed/s3api/policy_engine/types.go
+++ b/weed/s3api/policy_engine/types.go
@@ -108,7 +108,7 @@ type PolicyEvaluationArgs struct {
Conditions map[string][]string
// ObjectEntry is the object's metadata from entry.Extended.
// Used for evaluating conditions like s3:ExistingObjectTag/<tag-key>.
- // Tags are stored as "X-Amz-Tagging-<key>" -> value.
+ // Tags are stored with s3_constants.AmzObjectTaggingPrefix (X-Amz-Tagging-) prefix.
// Can be nil for bucket-level operations or when object doesn't exist.
ObjectEntry map[string][]byte
}
diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go
index 928d500b0..2d67aa551 100644
--- a/weed/s3api/s3api_bucket_handlers.go
+++ b/weed/s3api/s3api_bucket_handlers.go
@@ -765,8 +765,7 @@ func (s3a *S3ApiServer) AuthWithPublicRead(handler http.HandlerFunc, action Acti
// Check bucket policy for anonymous access using the policy engine
principal := "*" // Anonymous principal
- // Evaluate bucket policy with request context for accurate action resolution
- // Note: objectEntry is nil here - for tag-based conditions, re-evaluate after fetching entry
+ // Evaluate bucket policy (objectEntry nil - not yet fetched)
allowed, evaluated, err := s3a.policyEngine.EvaluatePolicy(bucket, object, string(action), principal, r, nil)
if err != nil {
// SECURITY: Fail-close on policy evaluation errors