2026-08-12 06:06:38 +00:00
Harden authorization, object lock, storage commits, background services, secrets at rest, website serving, request limits, sessions, and the installer. x-amz-bypass-governance-retention is now honored only for admins or principals granted the new bypass_governance IAM action (or s3:BypassGovernanceRetention in a bucket policy), evaluated per key on bulk delete, so callers with plain delete access can no longer strip GOVERNANCE retention; is_admin requires an unrestricted policy prefix so a prefix-scoped wildcard policy no longer short-circuits every authorization check; bucket-policy Resource keys match case-sensitively while bucket and Action matching stay case-insensitive; and Date-based lifecycle rules are a no-op until the date passes instead of using the configured date as an age cutoff. PutBucketObjectLockConfiguration validates the XML and requires versioning (409 InvalidBucketState), and its DefaultRetention is actually applied at every user-facing object-creation path with explicit headers winning and replication/peer-pull/restore paths untouched. Retention and legal-hold updates are check-and-set under the per-object stripe lock via new update_object_retention/update_object_legal_hold trait methods, so concurrent requests can no longer shorten COMPLIANCE retention and legal-hold writes no longer clobber concurrently written metadata; same-mode extension of either mode needs no bypass while shortening, removal and mode changes keep the bypass rules. A failed PUT commit now rolls back the archived version and metadata sidecar, and the archived-null purge runs after the rename so a failed commit can never destroy the prior null version. Site sync aborts the cycle on any remote-listing failure instead of treating NoSuchBucket/404 as an empty bucket (sync_deletions could erase every locally synchronized object) and records last_error/last_error_at per bucket so failing cycles are visible on the Sites page; GC skips a bucket's segment sweep whenever the reference scan hit a read error and reports the skip instead of deleting live segment data on an incomplete scan; and the process exits non-zero when either listener task dies so systemd restarts it. IAM mutations are serialized behind a mutation lock and persisted atomically (tmp+fsync+rename), first-run and reset admin credentials are written encrypted with a one-time plaintext-to-encrypted migration at startup and env-provided secrets no longer echoed to stdout, SECRET_KEY auto-generation to .myfsio.sys/config/.secret is implemented with dev-secret-key rejected from both env and file, and iam.json, KMS master keys, kms_keys.json, .connections_key, connections.json and .secret are all written owner-only through shared atomic helpers. Static website hosting streams through the extracted handlers/object_read data plane: SSE-S3/SSE-KMS assets that were served as ciphertext are decrypted, HEAD reports plaintext Content-Length, 206 ranges are computed from plaintext offsets, SSE-C assets return 403, and no path buffers whole objects in memory; ranged S3 GETs validate SSE-C keys like the whole-object path (400/403 instead of 500). Every config/XML/JSON body ingress is capped (1 MiB config XML and Select, 8 MiB CompleteMultipartUpload/DeleteObjects, 1-2 MiB JSON, bounded relay-outbound, 1 MiB non-file multipart form fields) with a new MaxMessageLengthExceeded error, and the UI part upload streams to disk with a 5 GiB cap instead of buffering each part twice in memory. Anonymous requests use ephemeral sessions that never enter the session store, capacity eviction prefers unauthenticated sessions, GET/POST /login is rate limited per IP (RATE_LIMIT_UI_LOGIN, default 20 per minute) with a styled 429 page and Retry-After, and failed UI uploads surface the real S3 reason in the toast and the upload dialog instead of a bare failure count. install.sh and uninstall.sh canonicalize and refuse system paths, guard foreign data directories behind --adopt-data-dir, track installer-created users in a manifest so uninstall preserves pre-existing accounts, and re-installs preserve myfsio.env (--overwrite-env backs up then regenerates). Adds handlers/object_read.rs, myfsio_common::fs_util, login_rate_limited.html, the bypass_governance IAM action, S3ErrorCode::{MaxMessageLengthExceeded,InvalidBucketState}, StorageError::InvalidArgument and RATE_LIMIT_UI_LOGIN; ~90 new regression tests.
2026-08-01 17:44:19 +08:00
Harden authorization, object lock, storage commits, background services, secrets at rest, website serving, request limits, sessions, and the installer. x-amz-bypass-governance-retention is now honored only for admins or principals granted the new bypass_governance IAM action (or s3:BypassGovernanceRetention in a bucket policy), evaluated per key on bulk delete, so callers with plain delete access can no longer strip GOVERNANCE retention; is_admin requires an unrestricted policy prefix so a prefix-scoped wildcard policy no longer short-circuits every authorization check; bucket-policy Resource keys match case-sensitively while bucket and Action matching stay case-insensitive; and Date-based lifecycle rules are a no-op until the date passes instead of using the configured date as an age cutoff. PutBucketObjectLockConfiguration validates the XML and requires versioning (409 InvalidBucketState), and its DefaultRetention is actually applied at every user-facing object-creation path with explicit headers winning and replication/peer-pull/restore paths untouched. Retention and legal-hold updates are check-and-set under the per-object stripe lock via new update_object_retention/update_object_legal_hold trait methods, so concurrent requests can no longer shorten COMPLIANCE retention and legal-hold writes no longer clobber concurrently written metadata; same-mode extension of either mode needs no bypass while shortening, removal and mode changes keep the bypass rules. A failed PUT commit now rolls back the archived version and metadata sidecar, and the archived-null purge runs after the rename so a failed commit can never destroy the prior null version. Site sync aborts the cycle on any remote-listing failure instead of treating NoSuchBucket/404 as an empty bucket (sync_deletions could erase every locally synchronized object) and records last_error/last_error_at per bucket so failing cycles are visible on the Sites page; GC skips a bucket's segment sweep whenever the reference scan hit a read error and reports the skip instead of deleting live segment data on an incomplete scan; and the process exits non-zero when either listener task dies so systemd restarts it. IAM mutations are serialized behind a mutation lock and persisted atomically (tmp+fsync+rename), first-run and reset admin credentials are written encrypted with a one-time plaintext-to-encrypted migration at startup and env-provided secrets no longer echoed to stdout, SECRET_KEY auto-generation to .myfsio.sys/config/.secret is implemented with dev-secret-key rejected from both env and file, and iam.json, KMS master keys, kms_keys.json, .connections_key, connections.json and .secret are all written owner-only through shared atomic helpers. Static website hosting streams through the extracted handlers/object_read data plane: SSE-S3/SSE-KMS assets that were served as ciphertext are decrypted, HEAD reports plaintext Content-Length, 206 ranges are computed from plaintext offsets, SSE-C assets return 403, and no path buffers whole objects in memory; ranged S3 GETs validate SSE-C keys like the whole-object path (400/403 instead of 500). Every config/XML/JSON body ingress is capped (1 MiB config XML and Select, 8 MiB CompleteMultipartUpload/DeleteObjects, 1-2 MiB JSON, bounded relay-outbound, 1 MiB non-file multipart form fields) with a new MaxMessageLengthExceeded error, and the UI part upload streams to disk with a 5 GiB cap instead of buffering each part twice in memory. Anonymous requests use ephemeral sessions that never enter the session store, capacity eviction prefers unauthenticated sessions, GET/POST /login is rate limited per IP (RATE_LIMIT_UI_LOGIN, default 20 per minute) with a styled 429 page and Retry-After, and failed UI uploads surface the real S3 reason in the toast and the upload dialog instead of a bare failure count. install.sh and uninstall.sh canonicalize and refuse system paths, guard foreign data directories behind --adopt-data-dir, track installer-created users in a manifest so uninstall preserves pre-existing accounts, and re-installs preserve myfsio.env (--overwrite-env backs up then regenerates). Adds handlers/object_read.rs, myfsio_common::fs_util, login_rate_limited.html, the bypass_governance IAM action, S3ErrorCode::{MaxMessageLengthExceeded,InvalidBucketState}, StorageError::InvalidArgument and RATE_LIMIT_UI_LOGIN; ~90 new regression tests.
2026-08-01 17:44:19 +08:00
Fix path traversal, authorization, and fail-open defects across storage, auth, and UI: validate multipart uploadId/segment ids as 32-hex before any filesystem join (abort_multipart previously remove_dir_all'd caller-controlled paths, destroying .myfsio.sys/config in one request); reject reserved __/x-amz- metadata keys at all five ingress points incl. CopyObject, UI multipart init, and peer pull (x-amz-meta-__segments__ reached recursive segment deletion and could redirect segment reads); validate UI restore version_id to a single path component; re-authorize every key in DeleteObjects (a prefix-scoped principal could delete outside its prefix); fail closed on bucket-policy Condition/NotPrincipal/NotAction/NotResource at evaluation and reject them at both write paths; require presigned requests to sign every x-amz-* header except content-sha256/date/decoded-content-length, closing copy-source, ACL, SSE, and governance-bypass injection by URL bearers; exit non-zero when ENCRYPTION_ENABLED/KMS_ENABLED init fails and fail closed on bucket-default encryption instead of silently storing plaintext; route IAM disable-user/create-key/delete-key through load_config/save_config so revocation works against an encrypted iam.json; treat an unreadable .bucket.json as fail-closed across policy eval, object write/delete, and all config writes rather than as an empty config; replace data-carrying inline onclick handlers with delegated data-* listeners (verified in-browser: a key containing quote-paren no longer executes on Copy/Move/Retry/Dismiss); and add missing bucket authorization to 11 UI handlers. Replace admin-only gating of GC and integrity with new system:gc_read/gc_run/integrity_read/integrity_run IAM actions on both the admin API and the UI, moving the seven /ui/system routes out of the admin-only router group so the per-handler check applies and each dashboard card names its missing permission; generalize action_matches from a hardcoded iam:* to any namespace:*. Adds validation::{is_valid_multipart_id,is_reserved_metadata_key,is_reserved_user_metadata_key,is_safe_path_segment} and BucketConfig::unreadable; 10 new tests, all 632 green; docs.md synced
2026-07-29 23:02:46 +08:00
2026-01-12 15:52:34 +08:00
Harden authorization, object lock, storage commits, background services, secrets at rest, website serving, request limits, sessions, and the installer. x-amz-bypass-governance-retention is now honored only for admins or principals granted the new bypass_governance IAM action (or s3:BypassGovernanceRetention in a bucket policy), evaluated per key on bulk delete, so callers with plain delete access can no longer strip GOVERNANCE retention; is_admin requires an unrestricted policy prefix so a prefix-scoped wildcard policy no longer short-circuits every authorization check; bucket-policy Resource keys match case-sensitively while bucket and Action matching stay case-insensitive; and Date-based lifecycle rules are a no-op until the date passes instead of using the configured date as an age cutoff. PutBucketObjectLockConfiguration validates the XML and requires versioning (409 InvalidBucketState), and its DefaultRetention is actually applied at every user-facing object-creation path with explicit headers winning and replication/peer-pull/restore paths untouched. Retention and legal-hold updates are check-and-set under the per-object stripe lock via new update_object_retention/update_object_legal_hold trait methods, so concurrent requests can no longer shorten COMPLIANCE retention and legal-hold writes no longer clobber concurrently written metadata; same-mode extension of either mode needs no bypass while shortening, removal and mode changes keep the bypass rules. A failed PUT commit now rolls back the archived version and metadata sidecar, and the archived-null purge runs after the rename so a failed commit can never destroy the prior null version. Site sync aborts the cycle on any remote-listing failure instead of treating NoSuchBucket/404 as an empty bucket (sync_deletions could erase every locally synchronized object) and records last_error/last_error_at per bucket so failing cycles are visible on the Sites page; GC skips a bucket's segment sweep whenever the reference scan hit a read error and reports the skip instead of deleting live segment data on an incomplete scan; and the process exits non-zero when either listener task dies so systemd restarts it. IAM mutations are serialized behind a mutation lock and persisted atomically (tmp+fsync+rename), first-run and reset admin credentials are written encrypted with a one-time plaintext-to-encrypted migration at startup and env-provided secrets no longer echoed to stdout, SECRET_KEY auto-generation to .myfsio.sys/config/.secret is implemented with dev-secret-key rejected from both env and file, and iam.json, KMS master keys, kms_keys.json, .connections_key, connections.json and .secret are all written owner-only through shared atomic helpers. Static website hosting streams through the extracted handlers/object_read data plane: SSE-S3/SSE-KMS assets that were served as ciphertext are decrypted, HEAD reports plaintext Content-Length, 206 ranges are computed from plaintext offsets, SSE-C assets return 403, and no path buffers whole objects in memory; ranged S3 GETs validate SSE-C keys like the whole-object path (400/403 instead of 500). Every config/XML/JSON body ingress is capped (1 MiB config XML and Select, 8 MiB CompleteMultipartUpload/DeleteObjects, 1-2 MiB JSON, bounded relay-outbound, 1 MiB non-file multipart form fields) with a new MaxMessageLengthExceeded error, and the UI part upload streams to disk with a 5 GiB cap instead of buffering each part twice in memory. Anonymous requests use ephemeral sessions that never enter the session store, capacity eviction prefers unauthenticated sessions, GET/POST /login is rate limited per IP (RATE_LIMIT_UI_LOGIN, default 20 per minute) with a styled 429 page and Retry-After, and failed UI uploads surface the real S3 reason in the toast and the upload dialog instead of a bare failure count. install.sh and uninstall.sh canonicalize and refuse system paths, guard foreign data directories behind --adopt-data-dir, track installer-created users in a manifest so uninstall preserves pre-existing accounts, and re-installs preserve myfsio.env (--overwrite-env backs up then regenerates). Adds handlers/object_read.rs, myfsio_common::fs_util, login_rate_limited.html, the bypass_governance IAM action, S3ErrorCode::{MaxMessageLengthExceeded,InvalidBucketState}, StorageError::InvalidArgument and RATE_LIMIT_UI_LOGIN; ~90 new regression tests.
2026-08-01 17:44:19 +08:00

MyFSIO

MyFSIO is an S3-compatible object storage server with a Rust runtime and a filesystem-backed storage engine. The repository root is the Cargo workspace; the server serves both the S3 API and the built-in web UI from a single process.

Features

  • S3-compatible REST API with Signature Version 4 authentication
  • Browser UI for buckets, objects, IAM users, policies, replication, metrics, and site administration
  • Filesystem-backed storage rooted at data/
  • Bucket versioning, multipart uploads, presigned URLs, CORS, ACLs, object and bucket tagging
  • Object Lock retention and legal hold, S3 Select, bucket notifications via webhooks, SSE-C, server-side encryption, and built-in KMS support
  • Optional background services for lifecycle, garbage collection, integrity scanning, operation metrics, and system metrics history
  • Replication, site sync, cluster federation with peer relay and audit logging, and static website hosting support

Runtime Model

MyFSIO now runs as one Rust process:

  • API listener on HOST + PORT (default 127.0.0.1:5000)
  • UI listener on HOST + UI_PORT (default 127.0.0.1:5100)
  • Shared state for storage, IAM, policies, sessions, metrics, and background workers

If you want API-only mode, set UI_ENABLED=false. There is no separate "UI-only" runtime anymore.

Quick Start

From the repository root:

cargo run -p myfsio-server --

Useful URLs:

  • UI: http://127.0.0.1:5100/ui
  • API: http://127.0.0.1:5000/
  • Health: http://127.0.0.1:5000/myfsio/health

On first boot, MyFSIO creates data/.myfsio.sys/config/iam.json and prints the generated admin access key and secret key to the console.

Common CLI commands

# Show resolved configuration
cargo run -p myfsio-server -- --show-config

# Validate configuration and exit non-zero on critical issues
cargo run -p myfsio-server -- --check-config

# Reset admin credentials
cargo run -p myfsio-server -- --reset-cred

# API only
UI_ENABLED=false cargo run -p myfsio-server --

Building a Binary

cargo build --release -p myfsio-server

Binary locations:

  • Linux/macOS: target/release/myfsio-server
  • Windows: target/release/myfsio-server.exe

Run the built binary directly:

./target/release/myfsio-server

Configuration

The server reads environment variables from the process environment and also loads, when present:

  • /opt/myfsio/myfsio.env
  • .env
  • myfsio.env

Core settings:

Variable Default Description
HOST 127.0.0.1 Bind address for API and UI listeners
PORT 5000 API port
UI_PORT 5100 UI port
UI_ENABLED true Disable to run API-only
STORAGE_ROOT ./data Root directory for buckets and system metadata
OBJECT_CACHE_MAX_SIZE 1024 Object metadata LRU cache capacity
IAM_CONFIG <STORAGE_ROOT>/.myfsio.sys/config/iam.json IAM config path
API_BASE_URL derived as http://<HOST>:<PORT> Public API base used by the UI and presigned URL generation
AWS_REGION us-east-1 Region used in SigV4 scope
SIGV4_TIMESTAMP_TOLERANCE_SECONDS 900 Allowed request time skew (regular SigV4)
STRICT_STREAMING_SIGV4 true Validate streaming SigV4 chunk chains, the final zero-length chunk, and signed trailers. Set to false only as a compatibility escape hatch; checksum trailers are still verified
ALLOW_INTERNAL_ENDPOINTS false Permit relay/replication targets to resolve to loopback / RFC1918 / link-local / CGNAT addresses. Required for local cluster testing; leave disabled in production unless you intentionally federate over private networks
MULTIPART_OBJECT_LAYOUT segments Completed multipart layout. segments keeps uploaded parts as immutable segment files; concat is the legacy single-file assembly. Older binaries cannot read segments objects
GC_SEGMENT_MAX_AGE_HOURS 24 Delete orphaned multipart segment directories older than this
RATE_LIMIT_DEFAULT 50000/min Default S3 / KMS rate limit
RATE_LIMIT_LIST_BUCKETS inherits RATE_LIMIT_DEFAULT Override for GET /
RATE_LIMIT_BUCKET_OPS inherits RATE_LIMIT_DEFAULT Override for bucket-scoped operations
RATE_LIMIT_OBJECT_OPS inherits RATE_LIMIT_DEFAULT Override for object-scoped operations
RATE_LIMIT_HEAD_OPS inherits RATE_LIMIT_DEFAULT Override for HEAD requests
RATE_LIMIT_ADMIN 60/min Override for /myfsio/admin/*
RATE_LIMIT_UI_LOGIN 20/min Per-IP limit for GET/POST /login
RATE_LIMIT_STORAGE_URI memory:// Rate-limit backend. Only memory:// is supported today; any other value logs a warning and falls back to in-memory limits
PEER_SIGV4_TIMESTAMP_TOLERANCE_SECONDS 60 Stricter skew enforced for peer-credential SigV4 requests
PEER_NONCE_CACHE_SIZE 10000 Replay-detection LRU capacity for peer requests
ALLOW_LEGACY_HEADER_AUTH false Accept legacy x-access-key/x-secret-key headers (peer creds are SigV4-only regardless)
PEER_REQUIRE_HTTPS false Reject non-https peer endpoint registrations
MYFSIO_CLUSTER_PSK unset Pre-shared key enabling cross-site admin federation (/myfsio/admin/peer/* + /myfsio/admin/relay/*). Same value on every node
RELAY_IDEMPOTENCY_CACHE_SIZE 10000 LRU capacity for relay idempotency keys
RELAY_IDEMPOTENCY_TTL_SECONDS 3600 TTL for cached relay responses
AUDIT_LOG_ENABLED false Write JSONL audit lines for relayed admin actions
PRESIGNED_URL_MIN_EXPIRY_SECONDS 1 Minimum presigned URL expiry
PRESIGNED_URL_MAX_EXPIRY_SECONDS 604800 Maximum presigned URL expiry
SECRET_KEY loaded from .myfsio.sys/config/.secret if present Session signing key and IAM-at-rest encryption key
ADMIN_ACCESS_KEY unset Optional first-run or reset access key
ADMIN_SECRET_KEY unset Optional first-run or reset secret key

Feature toggles:

Variable Default
ENCRYPTION_ENABLED false
KMS_ENABLED false
GC_ENABLED false
INTEGRITY_ENABLED false
LIFECYCLE_ENABLED false
METRICS_HISTORY_ENABLED false
OPERATION_METRICS_ENABLED false
WEBSITE_HOSTING_ENABLED false
SITE_SYNC_ENABLED false

Metrics and replication tuning:

Variable Default
OPERATION_METRICS_INTERVAL_MINUTES 5
OPERATION_METRICS_RETENTION_HOURS 24
METRICS_HISTORY_INTERVAL_MINUTES 5
METRICS_HISTORY_RETENTION_HOURS 24
METRICS_STORAGE_REFRESH_MINUTES 30 (min 5)
REPLICATION_CONNECT_TIMEOUT_SECONDS 5
REPLICATION_READ_TIMEOUT_SECONDS 120
REPLICATION_PART_STALL_TIMEOUT_SECONDS 300
REPLICATION_MAX_RETRIES 2
REPLICATION_STREAMING_THRESHOLD_BYTES 10485760
REPLICATION_MAX_FAILURES_PER_BUCKET 50
REPLICATION_HEALER_ENABLED true
REPLICATION_HEALER_INTERVAL_SECONDS 60
REPLICATION_HEALER_MAX_ATTEMPTS 12
REPLICATION_FULL_RECONCILE_INTERVAL_HOURS 0 (off)
SITE_SYNC_INTERVAL_SECONDS 60
SITE_SYNC_BATCH_SIZE 100
SITE_SYNC_CONNECT_TIMEOUT_SECONDS 10
SITE_SYNC_READ_TIMEOUT_SECONDS 120
SITE_SYNC_MAX_RETRIES 2
SITE_SYNC_CLOCK_SKEW_TOLERANCE_SECONDS 1.0

UI asset overrides:

Variable Default
TEMPLATES_DIR built-in crate templates directory
STATIC_DIR built-in crate static directory

See docs.md for the full Rust-side operations guide.

Cluster Connections — Peer Creds vs S3 Creds

A connection record (created via the UI form POST /ui/connections/create, fields: name, endpoint_url, region, access_key, secret_key) holds the credentials used to talk to another MyFSIO site. There are two distinct credential types and they are NOT interchangeable:

  • Peer credentials (PEERAK…/PEERSK…, created via POST /myfsio/admin/peer-credentials). SigV4-only, scoped to /myfsio/admin/cluster/overview and /myfsio/admin/peer/*. Use these in connections referenced by site-registry peers (peer.connection_id) for cluster federation, the federated cluster overview, peer health checks, and the relay path. Peer credentials cannot list buckets or read objects, so they are not usable for replication or bidirectional site sync.
  • Regular IAM credentials (admin or scoped). Use these in connections referenced by replication rules (ReplicationRule.target_connection_id) and bidirectional site sync. These need bucket list / object read / object write permissions on the remote site.

If you federate two sites and also replicate between them, you typically need TWO connection records per peer relationship — one with peer creds for cluster federation, one with regular creds for replication / site sync.

Data Layout

data/
  <bucket>/                              # object paths; completed multipart objects may be sparse stubs
  .myfsio.sys/
    config/
      iam.json
      bucket_policies.json
      connections.json
      operation_metrics.json
      metrics_history.json
    buckets/<bucket>/
      meta/
      versions/
      segments/
      replication/
        pending.snapshot.json
        pending.journal.jsonl
    multipart/
    keys/

Docker

Build the Rust image from the repository root:

docker build -t myfsio .
docker run --rm -p 5000:5000 -p 5100:5100 -v "${PWD}/data:/app/data" myfsio

If the instance sits behind a reverse proxy, set API_BASE_URL to the public S3 endpoint.

Linux Installation

The repository includes scripts/install.sh for systemd-style Linux installs. Build the Rust binary first, then pass it to the installer:

cargo build --release -p myfsio-server

sudo ./scripts/install.sh --binary ./target/release/myfsio-server

The installer copies the binary into /opt/myfsio/myfsio, writes /opt/myfsio/myfsio.env, and can register a myfsio.service unit.

Testing

Run the Rust test suite from the workspace:

cargo test

Health Check

GET /myfsio/health returns:

{
  "status": "ok",
  "version": "x.x.x"
}

The version field is populated at build time from the Rust crate version in crates/myfsio-server/Cargo.toml.

S
Description
MyFSIO
Readme AGPL-3.0
49 MiB
Languages
Rust 70.1%
HTML 19.9%
JavaScript 7.7%
CSS 1.5%
Shell 0.8%