mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-05-22 09:41:28 +00:00
s3: document s3:ExistingObjectTag support and feature status
Update policy engine documentation: - Add s3:ExistingObjectTag/<tag-key> to supported condition keys - Add 'Object Tag-Based Access Control' section with examples - Add 'Feature Status' section with implemented and planned features Planned features for future implementation: - s3:RequestObjectTag/<key> - s3:RequestObjectTagKeys - s3:x-amz-server-side-encryption - Cross-account access
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
- 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.
|
||||
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).
|
||||
@@ -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)}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user