Files
rustfs/crates/policy
Chris An 3b3cc018aa fix(policy): close condition, action, principal and resource gaps (#9160)
Eight scan findings in the shared policy engine, each a case where a
Deny did not apply or an Allow was broader than written:

- A bare `*` Action was parsed as `s3:*`, so `Deny *` never matched
  admin:, kms: or sts: actions. `Action::All` now denies every family
  while the historical S3-only Allow is preserved (backlog#3041).
- A bare IPv6 address in aws:SourceIp got `/32` appended, matching a
  2^96-address network; a bare address now means one host, and IPv4
  clients on a dual-stack listener are matched through their mapped
  form (backlog#3030).
- Date conditions on aws:EpochTime parsed the epoch seconds as RFC 3339
  and never matched, so a time-bounded Deny failed open (backlog#3036).
- NumericNotEquals and DateNotEquals evaluated to false when the key was
  absent; negated operators now follow the AWS rule and match
  (backlog#3123).
- Every trailing IfExists on a condition operator added a level of
  recursion without bound; the chain is rejected before parsing
  (backlog#3091).
- Policy variable substitution re-expanded its own output for up to ten
  passes, so an access key holding k placeholders grew to k^10; values
  are now substituted once as literal data (backlog#3067).
- A bucket-policy Service principal was matched against the signing
  access key, so a service account named after the service received its
  grants; Service principals round-trip but never match (backlog#3081).
- Resource matching cleaned the trailing slash off a directory object,
  so a Deny on `prefix/*` missed `prefix/` itself (backlog#3125).

Also deduplicate large Resource sets through a HashSet instead of a
quadratic scan.
2026-11-04 17:16:17 +08:00
..

RustFS

RustFS Policy - Policy Engine

Advanced policy engine and access control system for RustFS distributed object storage

CI 📖 Documentation · 🐛 Bug Reports · 💬 Discussions


📖 Overview

RustFS Policy provides advanced policy engine and access control capabilities for the RustFS distributed object storage system. For the complete RustFS experience, please visit the main RustFS repository.

✨ Features

  • AWS-compatible bucket policy engine
  • Fine-grained resource-based access control
  • Condition-based policy evaluation
  • Policy validation and syntax checking
  • Role-based access control integration
  • Dynamic policy evaluation with context

Stored bucket tags in OPA input

When the OPA authorization plugin is enabled, IAM resolves the addressed bucket's stored tags before sending its decision request. Tags appear in the existing conditions map; no additional S3 calls or credentials are needed by OPA:

{
  "input": {
    "resource": { "bucket": "financial-reports", "object": "annual/report.parquet" },
    "action": "s3:GetObject",
    "context": {
      "conditions": {
        "ExistingBucketTag/department": ["finance"],
        "ExistingBucketTag/environment": ["production"]
      }
    }
  }
}

The example omits unchanged identity and context fields. Tag names and values are case-sensitive. Values use single-element string arrays, including [""] for an empty tag value. A simple resource predicate is:

package rustfs.example
import rego.v1

finance_bucket if {
    input.context.conditions["ExistingBucketTag/department"] == ["finance"]
}

Combine this predicate with the policy's identity and action checks; tags do not establish caller roles or grant access by themselves. Restrict PutBucketTagging and DeleteBucketTagging separately when tags control access.

  • Only server-resolved, stored tags populate ExistingBucketTag/<key>. Incoming conditions in that namespace (including case variants and the s3: alias) are discarded before enrichment. Headers, claims, requested replacement tags and object tags cannot override it.
  • A confirmed missing bucket, or a bucket with no stored tags, contributes no bucket-tag conditions. New bucket creation therefore has no existing tags; a create request naming an existing bucket uses its stored tags. Bucketless requests, including STS and the global ListBuckets check, have no bucket tags.
  • Bucket metadata lookup failures, unreadable tag XML, and ambiguous/incomplete tags fail closed, even if a separate bucket policy would allow the request. This applies to requests evaluated by OPA even if the policy does not inspect tags. Boolean IAM callers deny on a lookup error; S3 access checks propagate it.
  • Object reads/writes, versions, listing and multipart checks use their addressed bucket. CopyObject and UploadPartCopy authorize source and destination with each bucket's own tags. Tag replacement/removal uses the existing stored state.
  • ListBuckets retains its existing contract: a global s3:ListAllMyBuckets allow lists all buckets. Otherwise, per-bucket s3:ListBucket/s3:GetBucketLocation checks carry each candidate's tags and can filter the result.
  • Resolution reuses the IAM storage instance's authoritative metadata reader and existing metadata cache/reload machinery, not a separate OPA cache. Local tag updates/removal update the local cache, and tag mutations wait for healthy-peer reload attempts before responding. Failed peer notification can leave that peer's cached tags stale until a later reload or distributed metadata refresh (normally every 15 minutes). This adds no stronger consistency barrier or partition-time revocation guarantee, and does not revoke in-flight requests.

This is an OPA input extension, not a new native IAM condition key. Native policy evaluation, owner/anonymous handling, and existing authorization combination rules remain unchanged. For S3 authorization, an OPA false decision is an implicit IAM denial that an applicable bucket-policy Allow may supplement. A tag mismatch alone therefore does not revoke access granted by such a bucket policy; policy authors must account for those grants when relying on tags to restrict access. ListBuckets filtering remains IAM-only. Lookup errors on the OPA path abort authorization and never fall through to a bucket-policy Allow. Custom IAM Store implementations must implement load_bucket_tags to support bucket-scoped OPA requests; the default fails closed.

📚 Documentation

For comprehensive documentation, examples, and usage guides, please visit the main RustFS repository.

📄 License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.