Enterprise Multi-Tenancy

SeaweedFS Enterprise adds native multi-tenancy so a single cluster can safely host workloads belonging to many isolated teams, customers, or environments. Each tenant sees only their own buckets, files, and Iceberg namespaces; cross-tenant access is denied at every surface — admin UI, filer HTTP, filer gRPC, S3, and Iceberg. Turning multi-tenancy on also enables role-based access control, per-tenant byte quotas, and audit logging.

When the feature flag is off, the cluster behaves exactly like OSS SeaweedFS — there is zero impact on deployments that don’t opt in.

What It Does

  • Tenant isolation on /tenants/<id>/... filer paths and on S3 buckets owned by the tenant
  • RBAC with four built-in roles (platform-admin, tenant-admin, tenant-user, auditor) plus operator-defined custom roles
  • Unified identity across the filer, S3, and Iceberg with a single principal record per human or service account
  • Per-tenant byte quotas enforced at write time, sourced from master-aggregated collection sizes (drift-free)
  • Audit logging of administrative mutations, login events, and enforcement denies in JSON-Lines format
  • Admin UI pages under /admin/access/* for managing tenants, principals, roles, and bindings without dropping to the shell
  • OIDC principal bridge that maps an OIDC sign-in to an enterprise principal so SSO and RBAC work together

Why Customers Use It

  • Shared infrastructure, separated tenants: Consolidate multiple teams or customers onto one cluster without the risk of accidental cross-access.
  • Least-privilege by default: Grant each user the minimum permission they need — read-only analysts, tenant operators, platform administrators — using roles rather than per-path ACLs.
  • Billable, observable capacity: Per-tenant byte counters and quotas make chargeback and capacity planning straightforward.
  • Compliance-ready audit trail: Every mutation and enforcement decision is captured as a structured event, ready to ship to SIEM.
  • One identity, every surface: Operators define an identity once; the same principal works through the admin UI, the filer, S3, and Iceberg.

How It Works

Multi-tenancy layers on top of SeaweedFS’s existing storage model. A tenant owns one or more collections (each collection is a disjoint volume set, one-to-one with an S3 bucket name). Identities are stored as principals bound to roles at a given scope — either platform (cluster-wide) or tenant:<id> (single tenant).

┌────────────────────────────────────────────────────────────────────────┐
│                  SeaweedFS Enterprise Multi-Tenancy                    │
│                                                                        │
│   ┌──────────────┐     ┌──────────────┐     ┌──────────────┐           │
│   │  Principal   │────>│    Role      │────>│   Binding    │           │
│   │ (user/svc)   │     │ (permission  │     │ (who + role  │           │
│   │              │     │     set)     │     │  + scope)    │           │
│   └──────────────┘     └──────────────┘     └──────┬───────┘           │
│                                                    │                   │
│                                                    ▼                   │
│                                              ┌──────────────┐          │
│                                              │    Scope     │          │
│                                              │  platform    │          │
│                                              │  tenant:<id> │          │
│                                              └──────┬───────┘          │
│                                                     │                  │
│                                                     ▼                  │
│   ┌─────────────────────────────────────────────────────────────────┐  │
│   │                         Tenant                                  │  │
│   │ ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐   │  │
│   │ │  Collection 1   │  │  Collection 2   │  │  Collection N   │   │  │
│   │ │  = S3 bucket    │  │  = S3 bucket    │  │ (filer default) │   │  │
│   │ └─────────────────┘  └─────────────────┘  └─────────────────┘   │  │
│   └─────────────────────────────────────────────────────────────────┘  │
│                                                                        │
│   Enforcement surfaces:                                                │
│     - Admin UI middleware (session → principal → role)                 │
│     - Filer HTTP gate (JWT claims ∋ /tenants/<id>/)                    │
│     - Filer gRPC interceptor (unary + streaming)                       │
│     - S3 bucket handlers (Account.Id → tenant_id)                      │
│     - Audit emitter (every enforcement decision)                       │
└────────────────────────────────────────────────────────────────────────┘

Writes to /tenants/<id>/ carry the tenant ID onto the filer entry (protobuf field tenant_id). The filer checks the tenant’s byte quota at write time using a combination of master-polled collection sizes and a per-filer pending-writes buffer. Reads and writes to any /tenants/ subtree require a JWT whose allowedPrefixes covers the requested path — tokens issued with a mismatched tenant prefix are rejected before any data is touched.

Core Concepts

Concept Purpose
Tenant Top-level isolation boundary with an ID, display name, status, byte quotas, and a list of owned collections.
Collection SeaweedFS storage namespace, one-to-one with an S3 bucket name. Bound to at most one tenant. Cannot be renamed.
Principal Login identity: user (human) or service_account (machine). Has a home tenant, optional password hash, optional OIDC subject links.
Role Named set of permissions. Four built-ins plus any custom roles.
Binding Grants a role to a subject at a scope. Scope is either platform (cluster-wide) or tenant:<id> (one tenant).
Quota Per-tenant byte limit. bytes_soft warns; bytes_hard rejects writes. Unset means unlimited.
Audit event Structured JSON line recording an allow/deny decision or administrative mutation.

Built-in Roles

Role Permissions
platform-admin Full control of the cluster: manage tenants, principals, roles, bindings; read/write every tenant.
tenant-admin Full control within one tenant: manage the tenant’s buckets, users, and quotas; cannot touch other tenants.
tenant-user Read/write data inside the tenant’s buckets and filer paths; cannot manage identity or quotas.
auditor Read-only access to the tenant’s data and audit events; no writes.

Configuration

Multi-tenancy is configured in filer.toml:

[filer.enterprise.multi_tenancy]
enabled = true

[filer.enterprise.audit]
enabled = true
file = "/var/log/seaweedfs/audit.jsonl"
stdout = false

The admin server, filers, masters, and volume servers do not need any additional flags beyond the existing JWT signing key:

[jwt.filer_signing]
key = "replace-me-with-a-strong-random-key"

Required for enforcement

  • JWT signing key configured on every filer participating in the cluster
  • At least one platform-admin principal (for bootstrap and break-glass)
  • Per-tenant principals and role bindings for your users and service accounts

What stays the same

  • Existing OSS clusters upgrade cleanly — no data layout changes, no required migration
  • Legacy -adminUser / -readOnlyUser flags still work side-by-side with principals
  • Existing identities.json / s3.cfg IAM configs continue to work; multi-tenancy is additive

Quickstart

A minimal end-to-end setup. Assumes you have a master, volume server, filer, and admin server running.

# 1. Enable the feature flag on the filer
$ cat >> /etc/seaweedfs/filer.toml <<EOF
[filer.enterprise.multi_tenancy]
enabled = true
[filer.enterprise.audit]
enabled = true
file = "/var/log/seaweedfs/audit.jsonl"
EOF
$ systemctl restart seaweedfs-filer

# 2. Bootstrap the platform admin (one-time, creates a principal with the
#    platform-admin role binding)
$ weed shell
> platform.bootstrap -id alice -password "S3cret!" -name "Alice Admin"

# 3. Create a tenant
> tenant.create -id acme -name "Acme Corp" -quota-bytes-hard 10737418240

# 4. Attach a collection to the tenant (becomes an S3 bucket of the same name)
> tenant.collection.add -tenant acme -name acme-data

# 5. Create a user inside the tenant
> principal.create -id alice@acme -tenant acme -password "U5er!" -name "Alice at Acme"
> binding.create -subject-kind principal -subject-id alice@acme \
                 -role tenant-admin -scope tenant:acme

# 6. Verify
> tenant.list
> principal.list -tenant acme
> binding.list

Alice can now sign in to the admin UI as a platform admin, and alice@acme can sign in as a tenant admin for the acme tenant.

Access Model

Authentication flows

  • Admin UI: Local principal (username + password) or OIDC sign-in. On success, the server looks up the principal, resolves its bindings, and issues a session capped at the token lifetime.
  • Filer HTTP / mount: JWT bearer token whose allowedPrefixes lists the /tenants/<id>/ subtrees the caller may touch. The filer gate rejects any path outside the token’s prefixes.
  • Filer gRPC: Unary and streaming interceptors validate the same JWT before the handler runs.
  • S3: Existing access-key + secret-key flow. The account’s Id field is the tenant ID — S3 bucket handlers derive the tenant from the authenticated account and enforce binding + quota rules.

How the tenant is derived

Surface Source of tenant ID
Admin UI Principal’s tenant field + binding scope
Filer HTTP /tenants/<id>/... path prefix, validated against JWT allowedPrefixes
Filer gRPC Same as HTTP; enforced by interceptor
S3 create bucket X-Swfs-Tenant header, or the authenticated account’s Id field
S3 bucket ops Resolved from the bucket’s entry metadata at bucket-creation time

Enforcement points

  1. Before authentication — TLS terminates and the request enters the server.
  2. At authentication — username/password, JWT signature, or S3 signature is verified.
  3. At authorization — the principal’s bindings are looked up; the requested permission and scope must match.
  4. At the write path — the tenant’s byte quota is checked against current usage plus pending writes.
  5. At the storage layer — writes land in the tenant’s collection; reads outside the tenant’s prefix are refused.

Every enforcement decision — allow or deny — produces an audit event.

Operator Cookbook

Common day-to-day tasks. All commands run in weed shell.

Create a tenant with a hard quota

> tenant.create -id billing -name "Billing Dept" \
                -quota-bytes-soft 53687091200 \
                -quota-bytes-hard 107374182400

Attach an S3 bucket to a tenant

The S3 bucket name and the storage collection name are the same. To bind an existing bucket to a tenant:

> tenant.collection.add -tenant billing -name billing-invoices

All subsequent writes to the billing-invoices bucket count against the billing tenant quota.

Grant a user read-only access to one tenant

> role.list
> principal.create -id dana -password "R3ad0nly!" -name "Dana Auditor"
> binding.create -subject-kind principal -subject-id dana \
                 -role auditor -scope tenant:billing
> principal.update -id alice@acme \
                   -oidc-issuer https://idp.example.com/realms/acme \
                   -oidc-subject "f4b6c1a2-..."

Alice can now sign in via the OIDC button on the admin login page; the admin server resolves the ID token’s (iss, sub) back to the alice@acme principal and issues a session with her existing role bindings.

Rotate a service account password

> principal.set_password -id ingest-svc@billing -password "new-strong-password"

Raise or lower a tenant quota

> tenant.update -id billing -quota-bytes-hard 214748364800

Quota changes take effect immediately; the next write checks against the new limit.

Delete a tenant safely

> tenant.get -id billing         # review collections and usage
> tenant.collection.remove -tenant billing -name billing-invoices
> tenant.delete -id billing

The system refuses to delete a tenant that still owns collections — remove (or reassign) collections first.

End-User Workflows

S3 clients (aws-cli, boto3, etc.)

Each principal with an S3 access key gets the normal S3 flow. The admin issues the access key with the tenant stamped in the account’s Id:

> s3.user.create -name alice@acme -accessKey ALICE... -secretKey ... -tenant acme

Subsequent aws s3 ls s3://acme-data/ calls go through the standard S3 auth path; the server enforces the tenant’s bindings and quota automatically.

Mount / FUSE

weed mount with a JWT whose allowedPrefixes covers the target subtree:

weed mount -filer=filer:8888 -dir=/mnt/acme \
  -filer.path=/tenants/acme/home \
  -jwt.filer.token=eyJhbGc...

Admin UI

Sign in at /login with the principal’s credentials (or via OIDC). The navigation gains an ACCESS section with links for Tenants, Principals, Roles, Bindings, My Profile, and Audit. Platform admins see everything; tenant admins see only their tenant.

HTTP API

Direct HTTP access works with a bearer token:

curl -H "Authorization: Bearer $JWT" \
     https://filer.example.com:8888/tenants/acme/home/file.txt

weed shell

All admin tasks are available in the shell: tenant.*, principal.*, role.*, binding.*, platform.bootstrap, s3.user.create -tenant .... Operators with direct shell access bypass UI but still emit audit events.

Use Cases

1. Multi-team enterprise

A single cluster serves Engineering, Data Science, and Finance. Each team is a tenant with its own buckets, users, and quota. Engineering can’t see Finance’s data even when both are on the same filer pod. Per-team quotas prevent one team from exhausting shared disk.

2. SaaS multi-tenant

A SaaS vendor hosts customer data on a shared SeaweedFS deployment. Each customer maps to a tenant; their S3 credentials carry the tenant ID on the account, and per-customer quotas drive billing. OIDC bridges the SaaS’s own identity system into SeaweedFS principals.

3. Environment separation (dev/staging/prod)

One cluster, three tenants — dev, staging, prod. Developers have tenant-admin in dev, tenant-user in staging, and auditor in prod. The audit log shows exactly who wrote what into production.

4. Data lake with analyst read-only groups

A raw-data tenant holds Iceberg tables and Parquet objects. Analysts get auditor at tenant:data-lake; pipeline service accounts get tenant-user. Quota limits on the data-lake tenant prevent runaway ingest.

5. Kubernetes isolation

Each namespace-scoped app maps to a tenant. A weed mount CSI driver per namespace uses a JWT scoped to /tenants/<ns>/, so pods in namespace a cannot mount or read namespace b’s data even if they somehow obtain a different JWT.

Deployment Patterns

Single filer

Smallest deployment. The filer’s filer.toml enables multi-tenancy and the JWT gate. Admin server runs alongside.

HA filer

Multiple filers share the same JWT signing key and configuration-backend (e.g. meta store is filer-backed). Tenants, principals, roles, and bindings live in the shared meta store so all filers see the same access model.

Kubernetes

The Helm chart toggles multi-tenancy via values:

filer:
  config:
    enterprise:
      multiTenancy:
        enabled: true
      audit:
        enabled: true
        file: "/var/log/seaweedfs/audit.jsonl"

A sidecar forwards the audit file to fluent-bit / vector for SIEM ingestion.

Hybrid

Some tenants only use the filer (mounted by workloads), others only use S3 (integrated with an external app). The same principal and role model covers both; no separate identity store is needed.

Production Notes

  • JWT key rotation: rotate the signing key periodically. Keep the previous key valid during overlap so in-flight tokens continue to verify.
  • Backups: principals, roles, and bindings live in /etc/iam/ on the filer — back it up alongside your data and your filer.toml.
  • Audit file rotation: the audit sink writes JSON Lines; use logrotate (with copytruncate) or ship straight to fluent-bit.
  • OIDC: the OIDC bridge validates the ID token against the configured issuer; use a real OIDC discovery endpoint in production, not insecure_skip_verify.
  • Peer chunk sharing: multi-tenancy is incompatible with the peer chunk sharing feature. If you have peer chunk sharing enabled, the filer refuses to start with multi-tenancy on. Disable peer chunk sharing before enabling multi-tenancy.
  • Quotas and eventual consistency: quota enforcement combines master-polled collection sizes with a per-filer pending-writes buffer. In the window between two polls, the in-memory buffer prevents overshoot; after a poll the buffer resets to the fresh master total. This keeps enforcement tight without blocking every write on a master round-trip.

Troubleshooting

Symptom Likely cause Fix
401 principal not found on admin login Principal isn’t in the meta store, or multi-tenancy is off Check filer.enterprise.multi_tenancy.enabled; run principal.get -id ...
403 tenant mismatch on filer PUT JWT allowedPrefixes doesn’t cover the requested path Re-issue JWT with the correct /tenants/<id>/ prefix
403 quota exceeded on write Tenant’s hard quota is reached Review tenant.get -id ...; raise quota or free capacity
S3 bucket create returns 409 tenant collision Another tenant already owns a collection with that name Choose a different bucket name; collections are globally unique
OIDC login succeeds but no bindings OIDC subject not linked to an enterprise principal Run principal.update -id ... -oidc-issuer ... -oidc-subject ...
Tenant appears in tenant.list but delete is refused Tenant still owns collections Run tenant.collection.remove ... first
Audit file is empty Audit not enabled or filer can’t write the path Check filer.enterprise.audit.enabled and file permissions
Legacy -adminUser login still works after enabling MT That’s intentional — legacy admin auth stays for break-glass Remove -adminUser after all operators move to principals

Reference

Filer config keys

Key Meaning
filer.enterprise.multi_tenancy.enabled Master switch for the whole feature set. Default false.
filer.enterprise.audit.enabled Enable audit event emission.
filer.enterprise.audit.file File path for the JSON Lines audit sink.
filer.enterprise.audit.stdout Mirror audit events to stdout.
jwt.filer_signing.key HMAC key used by the filer to verify JWTs.

Shell command catalog

Command Purpose
platform.bootstrap One-time: create initial platform-admin principal and binding.
tenant.{list,create,get,update,delete} Manage tenants.
tenant.collection.{add,remove} Attach/detach collections (S3 buckets) to tenants.
tenant.init Legacy alias for creating a tenant with default settings.
principal.{list,create,get,update,set_password,delete} Manage principals.
role.{list,create,get,delete} Manage roles (built-ins are read-only).
binding.{list,create,get,delete} Manage role bindings.
s3.user.create -tenant ... Create an S3 access-key user bound to a tenant.

Admin UI routes

All pages under /admin/access/:

  • /admin/access/tenants — list / create / delete tenants
  • /admin/access/principals — list / create / delete principals
  • /admin/access/roles — list / create / delete roles
  • /admin/access/bindings — list / create / delete bindings
  • /admin/access/profile — view signed-in principal’s bindings
  • /admin/access/audit — tail the audit file (last 500 events)

Filesystem layout

  • /tenants/<id>/... — tenant-owned filer paths, enforced by the gate
  • /etc/iam/principals/<id>.json — principal records
  • /etc/iam/roles/<name>.json — custom role definitions
  • /etc/iam/bindings/<id>.json — role bindings
  • /etc/iam/tenants/<id>.json — tenant metadata
  • /etc/iam/collections/<name>.json — collection-to-tenant bindings

JWT claim format

{
  "sub": "alice@acme",
  "exp": 1716489600,
  "allowedPrefixes": ["/tenants/acme/"],
  "allowedMethods": ["GET", "PUT", "POST", "DELETE"]
}

The allowedPrefixes claim is mandatory under multi-tenancy. The filer gate rejects any request whose path is not covered by one of the listed prefixes.

Key Benefits for Enterprise

  1. Zero-impact opt-in: Clusters with the feature flag off are byte-compatible with OSS SeaweedFS.
  2. Unified identity: One principal record serves the admin UI, filer, S3, and Iceberg surfaces.
  3. Drift-free quotas: Master-aggregated collection sizes mean per-tenant byte counters never drift during crashes or split-brain.
  4. Defense in depth: Tenant isolation is enforced at HTTP, gRPC, S3, and storage surfaces — not just one.
  5. Operational clarity: Every write, every role change, every denied access lands in the audit log.
  6. Smooth adoption: Legacy auth flags stay working, so operators can roll multi-tenancy out tenant by tenant.
  7. SSO-ready: OIDC bridges existing identity providers into the RBAC model without a parallel user database.