mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
b2a376c2d2
* fix(admin): bound IAM import archive expansion MAX_IAM_IMPORT_SIZE caps the compressed upload at 10 MB, but every member of the archive was then read with read_to_end into an unbounded Vec. Deflate ratios well above 100:1 are easy to construct, so a small authorized upload could expand without limit across the seven members ImportIam reads. Add a shared expansion budget (MAX_IAM_IMPORT_EXPANDED_SIZE, 10x the compressed cap) drawn down by every member, and route all seven reads through one helper that reads a byte past the remaining budget to detect overrun. Sharing the budget bounds the archive as a whole rather than letting each member spend the full limit independently. Covers R03-CAN-024 through R03-CAN-030 plus R04-CAN-077 (backlog #1471) — one fix rather than seven, since all seven call sites were byte-identical. * fix(kms): confine local key paths and refuse silent key replacement Local KMS key identifiers arrive from request input — the `name` tag on CreateKey, the `keyId` body field or query parameter on DeleteKey — and were joined onto `key_dir` with no validation. An identifier such as `../../tmp/evil` escaped the configured directory, making key creation a constrained arbitrary-file write and `DeleteKey` with `force_immediate` a cross-directory delete. Validate in `master_key_path` and make it fallible, so every filesystem path in this backend inherits the guard: decode_stored_key, load_master_key, save_master_key, create_key and delete_key all derive their paths there. The rule is containment rather than a character allowlist, so identifiers already in use keep resolving; only separators, NUL, absolute paths and non-single-component forms are refused. Note `.` and `..` are contained rather than refused — the `.key` suffix turns them into the ordinary filenames `..key` and `...key`. Separately, `LocalKmsBackend::create_key` had no existence check, while the sibling `KmsClient::create_key` has always had one. Since `save_master_key` renames over its destination, creating a key under an existing name silently replaced its material and destroyed the ability to decrypt everything wrapped under it — and the backend path is the one the admin API uses. It now returns KeyAlreadyExists, matching StaticKmsBackend. Covers R03-CAN-072, R03-CAN-073 and R07-CAN-103 (backlog #1475). R03-CAN-073 needed no separate change: delete_key routes both its load and its remove_file through master_key_path. * fix(swift): bound SLO manifest reads to the 2 MiB manifest limit The three Swift SLO handlers that load a stored manifest (handle_slo_get, handle_slo_get_manifest, handle_slo_delete) read the `<object>.slo-manifest` object to EOF with AsyncReadExt::read_to_end. That key is predictable and writable through the ordinary object PUT path, so a tenant can replace the manifest with an arbitrarily large object and then make the server allocate its full size on every SLO GET, multipart-manifest=get, or multipart-manifest=delete request - a memory amplification bounded only by the stored object size (CWE-400 / CWE-770). The 2 MiB manifest limit that handle_slo_put enforces was not applied on the read side. Introduce MAX_SLO_MANIFEST_SIZE (the existing 2 MiB PUT limit, now a named constant) and a shared read_manifest_bytes helper that reads through a `take(limit + 1)` and rejects anything larger, so an oversized manifest is refused instead of being buffered first. All three call sites go through the helper. handle_slo_put now checks the size before parsing the JSON. Regression tests: test_read_manifest_bytes_rejects_oversized_manifest and test_read_manifest_bytes_stops_reading_oversized_manifest (which asserts the reader is not consumed past the limit), plus a boundary test that a manifest at exactly 2 MiB is still accepted. * fix(protocols): authorize every object in FTPS/WebDAV recursive deletes The FTPS and WebDAV gateways authorized only the container before a recursive delete and then destroyed everything inside it without a further check: - FTPS RMD (and DELE on a bucket path ending in '/') cleared s3:DeleteBucket, then delete_bucket_recursively listed the bucket and deleted every object. - WebDAV DELETE on a bucket did the same via its own delete_bucket_recursively. - WebDAV DELETE on a directory cleared s3:DeleteObject for the directory marker key ("dir/") only, then listed that prefix and deleted every child under it. A principal holding s3:DeleteBucket (or s3:DeleteObject on a single marker key) could therefore erase objects it had no s3:DeleteObject permission for, and the operation reported success. Deletion stays recursive - that is the expected behaviour for these protocols - but each object now clears s3:DeleteObject on its own key before it is removed, and the enumeration clears s3:ListBucket. A denial aborts the whole operation with access denied rather than being skipped, so the caller can never be told the delete succeeded while objects were left behind or removed without authorization. The test double gained shared-state cloning, delete_object/delete_bucket call logs, and list/delete queue helpers so the regression tests can observe that nothing is deleted once a deny lands. * fix(server,ecstore): bound TLS handshakes and remote volume RPC waits Three call sites let an unauthenticated client or a misbehaving peer hold server resources with no deadline. TLS listener (R03-CAN-035): process_connection awaited `acceptor.accept(socket)` with no bound. A client that opens a TCP connection and never finishes the handshake parks a Tokio task and a socket forever, and the connection cap (RUSTFS_API_MAX_CONNECTIONS) is unlimited by default, so nothing else sheds it. The handshake now runs under accept_tls_with_deadline(), reusing the existing HTTP/1 header-read budget — the established slow-client bound for the pre-request phase — and the expiry is recorded through the same log/metric path as a handshake error, under a new TIMEOUT failure kind. Remote disk RPCs (R03-CAN-049, R03-CAN-050): list_volumes and delete_volume passed Duration::ZERO, which execute_with_timeout treats as "no deadline", so a peer that accepts the request and never answers stalls the coordinator (and, for delete_volume, the bucket-deletion workflow). Both now pass get_max_timeout_duration(), matching every sibling method in the file. Regression tests: a silent TLS peer must be shed by the handshake deadline; list_volumes/delete_volume against a peer that completes the TCP connect and then goes silent must fail with DiskError::Timeout instead of hanging. * fix(security): stop leaking signed headers and bound OIDC/KMS credentials Three independent hygiene fixes found by the security review. R03-CAN-018 (crates/signer): try_get_canonical_headers and get_signed_headers logged the complete header map at DEBUG before signing. Runtime callers pass session credentials and SSE-C key material through these headers, so anyone able to raise the log level (or read DEBUG logs) recovered X-Amz-Security-Token and SSE-C keys verbatim. The statements were debugging leftovers with no operational value and are deleted rather than redacted. R03-CAN-014 (crates/iam): the OIDC HTTP adapter buffered provider responses with an unbounded Response::bytes(), so a configured, compromised or attacker-pointed IdP endpoint could stream an arbitrarily large or endless body into memory (the ValidateOidcConfig admin handler lets a ServerInfo caller choose the endpoint). Responses are now read incrementally and fail closed past MAX_OIDC_RESPONSE_SIZE, and the already SSRF-hardened client builder gains request and connect timeouts so a stalled provider cannot pin the calling task indefinitely. R07-CAN-105 (helm): the Vault KMS token was serialized into the chart ConfigMap, exposing it to every subject allowed to get ConfigMaps in the namespace. It now renders into a dedicated Secret that the Deployment and StatefulSet consume via envFrom; the Secret is separate from the main credentials Secret so it also works when secret.existingSecret is set. Regression tests: - rustfs-signer: signing_never_logs_signed_header_material - rustfs-iam: oidc_response_body_past_the_limit_is_rejected, oidc_response_body_at_the_limit_is_accepted - scripts/test_helm_templates.sh: KMS token must never render in plaintext * fix(webdav): enforce body limit, request timeout and connection cap The configured WebDAV maximum body size was enforced from Content-Length, so a chunked request declared no length and bypassed it entirely. The configured request timeout was never applied to the connection at all, and the accept loop spawned a task per connection with no bound, so an unauthenticated client could hold resources indefinitely and in unbounded number. Enforce the limit on bytes actually read rather than the declared length, apply the configured timeout to the request, and bound accepted connections with a new RUSTFS_WEBDAV_MAX_CONNECTIONS (default 1024) surfaced in the config report. Covers R03-CAN-051, R03-CAN-052, R03-CAN-067, R04-CAN-089, R05-CAN-094 and R05-CAN-097 (backlog #1471, #1474). * fix(security): stop STS credentials from crossing the parent trust boundary Two related credential-boundary holes let a short-lived STS credential act with the full, unrestricted authority of the long-term user it was minted from. AddUser (R03-CAN-021, CWE-269/863): should_check_deny_only relaxes the admin policy check to deny-only when a Console/STS session targets the IAM user it represents. Nothing then stopped that session from calling AddUser with its own parent's access key, so the handler wrote an attacker-chosen secret key and status over the parent's stored Credentials via create_user -> save_user_identity. A session that expires in minutes became permanent control of the account. AddUser now rejects any temp or service-account requester whose resolved parent equals the target access key, resolving the parent the same way should_check_deny_only does (parent_user field, else the JWT `parent` claim, since some stores persist the parent only in the token). FTPS/SFTP/WebDAV password auth (R04-CAN-086, CWE-287/862): these protocols looked the access key up with check_key, which falls back to the STS account cache, and then compared only the stored secret. An STS access key plus secret therefore authenticated with no session token presented and no session-policy claims applied - the holder got the parent's full permissions. Password authentication now rejects temporary credentials before the secret comparison. The discriminator is is_temp() && !is_service_account(), the same one IamCache::update_user_with_claims uses to route an identity into the STS cache, so service accounts - which resolve policy from stored IAM state rather than a client-presented token - keep working over these protocols. Regression tests cover both predicates and pin the guards to their call sites so neither can be dropped without a test failure.
402 lines
24 KiB
Markdown
402 lines
24 KiB
Markdown
# RustFS Helm Mode
|
||
|
||
RustFS helm chart supports **standalone** and **distributed** mode.
|
||
|
||
- **Standalone mode**: one pod with one PVC (single node, single disk).
|
||
- **Distributed mode** (**default**): multiple pods with multiple PVCs (multiple nodes, multiple disks).
|
||
|
||
## Distributed topology
|
||
|
||
The distributed topology is defined by two parameters:
|
||
|
||
- `replicaCount` — number of pods (nodes) in the StatefulSet.
|
||
- `drivesPerNode` — number of data PVCs mounted on each pod.
|
||
|
||
Total drives in the cluster = `replicaCount * drivesPerNode`.
|
||
|
||
When `drivesPerNode` is left unset, the chart automatically infers a
|
||
backward-compatible value from each pool's replica count (with pools
|
||
disabled there is a single pool driven by the top-level `replicaCount`):
|
||
|
||
| `replicaCount` | Inferred `drivesPerNode` | Legacy equivalent |
|
||
|----------------|--------------------------|-------------------|
|
||
| 4 | 4 | old default 4×4 |
|
||
| anything else | 1 | old 16×1, etc. |
|
||
|
||
You can override the inference by setting `drivesPerNode` explicitly, e.g.
|
||
`--set drivesPerNode=2` for an 8×2 cluster.
|
||
|
||
**IMPORTANT**: Kubernetes does **not** allow changes to
|
||
`volumeClaimTemplates` in an existing StatefulSet. If you want to change
|
||
`drivesPerNode` after installation you must delete the StatefulSet
|
||
(with `--cascade=orphan` to keep pods and PVCs) and recreate it, or perform a
|
||
full reinstall.
|
||
|
||
---
|
||
|
||
## Upgrade notes
|
||
|
||
Upgrading from chart versions that did **not** have `drivesPerNode` is safe
|
||
without manual intervention:
|
||
|
||
- Existing 4×4 deployments (default `replicaCount=4`) continue to receive 4
|
||
drives per node because the chart infers `drivesPerNode=4`.
|
||
- Existing 16×1 deployments (`replicaCount=16`) continue to receive 1 drive
|
||
per node because the chart infers `drivesPerNode=1`.
|
||
|
||
If you previously set `replicaCount=16` and now want a different topology,
|
||
set both `replicaCount` and `drivesPerNode` explicitly.
|
||
|
||
---
|
||
|
||
# Parameters Overview
|
||
|
||
| Parameter | Type | Default value | Description |
|
||
|-----|------|---------|-------------|
|
||
| affinity.nodeAffinity | object | `{}` | |
|
||
| affinity.podAntiAffinity.enabled | bool | `true` | |
|
||
| affinity.podAntiAffinity.topologyKey | string | `"kubernetes.io/hostname"` | |
|
||
| clusterDomain | string | `"cluster.local"` | Kubernetes cluster DNS domain used to build in-cluster FQDNs for `RUSTFS_VOLUMES` (distributed mode) and mTLS server certificate SANs. Override for clusters not using the default `cluster.local`. Provide the DNS root only, without a `svc.` prefix or leading/trailing dots. |
|
||
| commonLabels | object | `{}` | Labels to add to all deployed objects. |
|
||
| config.rustfs.address | string | `":9000"` | |
|
||
| config.rustfs.console_address | string | `":9001"` | |
|
||
| config.rustfs.console_enable | string | `"true"` | |
|
||
| config.rustfs.domains | string | `""` | Enable virtual host mode. |
|
||
| config.rustfs.log_level | string | `"info"` | |
|
||
| config.rustfs.obs_environment | string | `"development"` | |
|
||
| config.rustfs.obs_log_directory | string | `"/logs"` | Log directory inside the RustFS container. Set to `""` to disable log PVCs and mounts. |
|
||
| config.rustfs.region | string | `"us-east-1"` | |
|
||
| config.rustfs.volumes | string | `""` | |
|
||
| config.rustfs.log_rotation.size | int | `"100"` | Default log rotation size mb for rustfs. |
|
||
| config.rustfs.log_rotation.time | string | `"hour"` | Default log rotation time for rustfs. |
|
||
| config.rustfs.log_rotation.keep_files | int | `"30"` | Default log keep files for rustfs. |
|
||
| config.rustfs.metrics.enabled | bool | `false` | Toggle metrics export. |
|
||
| config.rustfs.metrics.endpoint | string | `""` | Dedicated metrics endpoint. |
|
||
| config.rustfs.scanner.speed | string | `""` | Scanner speed preset: `fastest`, `fast`, `default`, `slow`, `slowest`. |
|
||
| config.rustfs.scanner.delay | string | `""` | Override scanner sleep multiplier with `RUSTFS_SCANNER_DELAY` (`0` through `10000`). |
|
||
| config.rustfs.scanner.max_wait_secs | string | `""` | Override maximum scanner sleep in seconds with `RUSTFS_SCANNER_MAX_WAIT_SECS`. |
|
||
| config.rustfs.scanner.cycle_secs | string | `""` | Override scanner cycle interval in seconds with `RUSTFS_SCANNER_CYCLE`. |
|
||
| config.rustfs.scanner.start_delay_secs | string | `""` | Override scanner cycle interval in seconds with `RUSTFS_SCANNER_START_DELAY_SECS`. |
|
||
| config.rustfs.scanner.cycle_max_duration_secs | string | `""` | Cap one scanner cycle's runtime in seconds with `RUSTFS_SCANNER_CYCLE_MAX_DURATION_SECS` (`0` disables). |
|
||
| config.rustfs.scanner.cycle_max_objects | string | `""` | Cap objects processed by one scanner cycle with `RUSTFS_SCANNER_CYCLE_MAX_OBJECTS` (`0` disables). |
|
||
| config.rustfs.scanner.cycle_max_directories | string | `""` | Cap directories entered by one scanner cycle with `RUSTFS_SCANNER_CYCLE_MAX_DIRECTORIES` (`0` disables). |
|
||
| config.rustfs.scanner.bitrot_cycle_secs | string | `""` | Override periodic deep bitrot cycle with `RUSTFS_SCANNER_BITROT_CYCLE_SECS`; `false`, `off`, `no`, or `disabled` disables it. |
|
||
| config.rustfs.scanner.idle_mode | string | `""` | Override scanner idle throttling flag (`RUSTFS_SCANNER_IDLE_MODE`). |
|
||
| config.rustfs.scanner.cache_save_timeout_secs | string | `""` | Override scanner cache save timeout in seconds with `RUSTFS_SCANNER_CACHE_SAVE_TIMEOUT_SECS` (minimum `1`). |
|
||
| config.rustfs.scanner.max_concurrent_set_scans | string | `""` | Cap concurrent scanner set tasks with `RUSTFS_SCANNER_MAX_CONCURRENT_SET_SCANS` (`0` keeps topology-derived concurrency). |
|
||
| config.rustfs.scanner.max_concurrent_disk_scans | string | `""` | Cap concurrent scanner disk bucket walks per set with `RUSTFS_SCANNER_MAX_CONCURRENT_DISK_SCANS` (`0` keeps disk-count-derived concurrency). |
|
||
| config.rustfs.scanner.yield_every_n_objects | string | `""` | Yield to the async runtime every N scanned objects with `RUSTFS_SCANNER_YIELD_EVERY_N_OBJECTS` (`0` disables extra yield). |
|
||
| config.rustfs.scanner.alert_excess_versions | string | `""` | Set version count threshold for scanner alerts with `RUSTFS_SCANNER_ALERT_EXCESS_VERSIONS`. |
|
||
| config.rustfs.scanner.alert_excess_version_size | string | `""` | Set retained version byte threshold for scanner alerts with `RUSTFS_SCANNER_ALERT_EXCESS_VERSION_SIZE`. |
|
||
| config.rustfs.scanner.alert_excess_folders | string | `""` | Set direct subfolder threshold for scanner alerts with `RUSTFS_SCANNER_ALERT_EXCESS_FOLDERS`. |
|
||
| config.rustfs.obs_endpoint.enabled | bool | `false` | Whether to send metrics/logs/traces/profilings to remote endpoint, eg, OLTP. |
|
||
| config.rustfs.obs_endpoint.base_endpoint | string | `""` | Root OTLP/HTTP endpoint, e.g. http://otel-collector:4318. |
|
||
| config.rustfs.obs_endpoint.use_stdout | bool | `false` | Whether to output logs to stdout in addition the OLTP. |
|
||
| config.rustfs.obs_endpoint.metrics.enabled | bool | `false` | Whether to send metrics to remote endpoint. |
|
||
| config.rustfs.obs_endpoint.metrics.endpoint | string | `""` | Remote endpoint url for metrics. |
|
||
| config.rustfs.obs_endpoint.trace.enabled | bool | `false` | Whether to send trace to remote endpoint. |
|
||
| config.rustfs.obs_endpoint.trace.endpoint | string | `""` | Remote endpoint url for trace. |
|
||
| config.rustfs.obs_endpoint.logs.enabled | bool | `false` | Whether to send logs to remote endpoint. |
|
||
| config.rustfs.obs_endpoint.logs.endpoint | string | `""` | Remote endpoint url for logs. |
|
||
| config.rustfs.obs_endpoint.profiling.enabled | bool | `false` | Whether to send profiling to remote endpoint. |
|
||
| config.rustfs.obs_endpoint.profiling.endpoint | string | `""` | Remote endpoint url for profiling. |
|
||
| config.rustfs.kms.enabled | bool | `false`| Whether to enable kms. |
|
||
| config.rustfs.kms.type | string | `vault`| The kms type that RustFS supported. |
|
||
| config.rustfs.kms.vault.vault_backend | string | `""`| The vault backend, `vault-kv2` or `vault-transit`. |
|
||
| config.rustfs.kms.vault.vault_address | string | `""`| The vault address. |
|
||
| config.rustfs.kms.vault.vault_token | string | `""`| The vault token. Rendered into a dedicated Secret (`<fullname>-kms-secret`), never into the ConfigMap. |
|
||
| config.rustfs.kms.vault.vault_mount_path | string | `"transit"`| The vault mount path, only works if `vault_backend` equals `vault-transit` . |
|
||
| config.rustfs.kms.vault.default_key | string | `"transit"`| The master key id for RustFS. |
|
||
| extraEnv | map | `[]` | Extra environment variables for RustFS container. |
|
||
| extraVolumes | list | `[]` | Extra volumes to add to the pod spec. Supported in both standalone (Deployment) and distributed (StatefulSet) modes. |
|
||
| extraVolumeMounts | list | `[]` | Extra volume mounts to add to the RustFS container. Supported in both standalone (Deployment) and distributed (StatefulSet) modes. |
|
||
| containerSecurityContext.capabilities.drop[0] | string | `"ALL"` | |
|
||
| containerSecurityContext.readOnlyRootFilesystem | bool | `true` | |
|
||
| containerSecurityContext.runAsNonRoot | bool | `true` | |
|
||
| priorityClassName | string | `""` | |
|
||
| enableServiceLinks | bool | `false` | |
|
||
| extraManifests | list | `[]` | List of additional k8s manifests. |
|
||
| fullnameOverride | string | `""` | |
|
||
| image.rustfs.pullPolicy | string | `"IfNotPresent"` | |
|
||
| image.rustfs.repository | string | `"rustfs/rustfs"` | RustFS docker image repository. |
|
||
| image.rustfs.tag | string | `""` | Chart appVersion default if unset. |
|
||
| imagePullSecrets | list | `[]` | A List of secrets to pull image from private registry. |
|
||
| imageRegistryCredentials.email | string | `""` | The email to pull rustfs image from private registry. |
|
||
| imageRegistryCredentials.enabled | bool | `false` | To indicate whether pull image from private registry. |
|
||
| imageRegistryCredentials.password | string | `""` | The password to pull rustfs image from private registry. |
|
||
| imageRegistryCredentials.registry | string | `""` | Private registry url to pull rustfs image. |
|
||
| imageRegistryCredentials.username | string | `""` | The username to pull rustfs image from private registry. |
|
||
| ingress.className | string | `"nginx"` | Specify the ingress class, traefik or nginx. |
|
||
| ingress.enabled | bool | `true` | |
|
||
| ingress.hosts[0].host | string | `"example.rustfs.com"` | |
|
||
| ingress.hosts[0].paths[0].path | string | `"/"` | |
|
||
| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | |
|
||
| ingress.nginxAnnotations."nginx.ingress.kubernetes.io/affinity" | string | `"cookie"` | |
|
||
| ingress.nginxAnnotations."nginx.ingress.kubernetes.io/session-cookie-expires" | string | `"3600"` | |
|
||
| ingress.nginxAnnotations."nginx.ingress.kubernetes.io/session-cookie-hash" | string | `"sha1"` | |
|
||
| ingress.nginxAnnotations."nginx.ingress.kubernetes.io/session-cookie-max-age" | string | `"3600"` | |
|
||
| ingress.nginxAnnotations."nginx.ingress.kubernetes.io/session-cookie-name" | string | `"rustfs"` | |
|
||
| ingress.customAnnotations | dict | `{}` | Additional custom annotations, merged with class-specific stickiness annotations. |
|
||
| ingress.traefikAnnotations."traefik.ingress.kubernetes.io/service.sticky.cookie" | string | `"true"` | |
|
||
| ingress.traefikAnnotations."traefik.ingress.kubernetes.io/service.sticky.cookie.httponly" | string | `"true"` | |
|
||
| ingress.traefikAnnotations."traefik.ingress.kubernetes.io/service.sticky.cookie.name" | string | `"rustfs"` | |
|
||
| ingress.traefikAnnotations."traefik.ingress.kubernetes.io/service.sticky.cookie.samesite" | string | `"none"` | |
|
||
| ingress.traefikAnnotations."traefik.ingress.kubernetes.io/service.sticky.cookie.secure" | string | `"true"` | |
|
||
| ingress.tls.enabled | bool | `false` | Enable tls and access rustfs via https. |
|
||
| ingress.tls.certManager.enabled | string | `false` | Enable cert manager support to generate certificate automatically. |
|
||
| ingress.tls.crt | string | "" | The content of certificate file. |
|
||
| ingress.tls.key | string | "" | The content of key file. |
|
||
| livenessProbe.failureThreshold | int | `3` | |
|
||
| livenessProbe.httpGet.path | string | `"/health"` | |
|
||
| livenessProbe.httpGet.port | string | `"endpoint"` | |
|
||
| livenessProbe.initialDelaySeconds | int | `10` | |
|
||
| livenessProbe.periodSeconds | int | `5` | |
|
||
| livenessProbe.successThreshold | int | `1` | |
|
||
| livenessProbe.timeoutSeconds | int | `3` | |
|
||
| mode.distributed.enabled | bool | `true` | RustFS distributed mode support, namely multiple pod multiple pvc. |
|
||
| mode.standalone.enabled | bool | `false` | RustFS standalone mode support, namely one pod one pvc. |
|
||
| mode.standalone.existingClaim.dataClaim |string |`""` |Whether to use existing pvc claim for data storage. |
|
||
| mode.standalone.existingClaim.logsClaim |string |`""` |Whether to use existing pvc claim for logs storage. |
|
||
| mtls.enabled | bool | `false` | Enable mtls betweens pods. |
|
||
| mtls.clientCertPath | string | `/opt/tls/client_cert.pem` | The path for client cert. |
|
||
| mtls.clientKeyPath | string | `/opt/tls/client_key.pem` | The path for client key. |
|
||
| mtls.existingIssuerRef.enabled | bool | `false` | Enable to use external/existing certificate issuer.|
|
||
| mtls.existingIssuerRef.name | string | `""` | The name of external/existing certificate issuer. |
|
||
| mtls.existingIssuerRef.kind | string | `""` | The kind of external/existing certificate iss
|
||
uer. `ClusterIssuer` or `Issuer`. |
|
||
| mtls.existingIssuerRef.group | string | `""` | The group of external/existing certificate issuer. |
|
||
| nameOverride | string | `""` | |
|
||
| nodeSelector | object | `{}` | |
|
||
| pdb.create | bool | `false` | Enable/disable a Pod Disruption Budget creation |
|
||
| pdb.maxUnavailable | string | `1` | |
|
||
| pdb.minAvailable | string | `""` | |
|
||
| podAnnotations | object | `{}` | |
|
||
| pools.enabled | bool | `false` | Enable multiple server pools (capacity expansion, distributed mode only). |
|
||
| pools.list | list | `[]` | One entry per pool; entries may set `replicaCount` (>= 2) and `storageclass`, omitted fields inherit top-level values. Append-only. |
|
||
| podLabels | object | `{}` | |
|
||
| podSecurityContext.fsGroup | int | `10001` | |
|
||
| podSecurityContext.runAsGroup | int | `10001` | |
|
||
| podSecurityContext.runAsUser | int | `10001` | |
|
||
| readinessProbe.failureThreshold | int | `3` | |
|
||
| readinessProbe.httpGet.path | string | `"/health/ready"` | |
|
||
| readinessProbe.httpGet.port | string | `"endpoint"` | |
|
||
| readinessProbe.initialDelaySeconds | int | `30` | |
|
||
| readinessProbe.periodSeconds | int | `5` | |
|
||
| readinessProbe.successThreshold | int | `1` | |
|
||
| readinessProbe.timeoutSeconds | int | `3` | |
|
||
| replicaCount | int | `4` | Number of cluster nodes. Distributed mode requires >= 2. |
|
||
| drivesPerNode | int | `null` | Number of data PVCs per pod. Inferred from replicaCount when unset (see Distributed topology above). |
|
||
| resources.limits.cpu | string | `"200m"` | |
|
||
| resources.limits.memory | string | `"512Mi"` | |
|
||
| resources.requests.cpu | string | `"100m"` | |
|
||
| resources.requests.memory | string | `"128Mi"` | |
|
||
| secret.existingSecret | string | `""` | Use existing secret with a credentials. |
|
||
| secret.rustfs.access_key | string | `"rustfsadmin"` | RustFS Access Key ID |
|
||
| secret.rustfs.secret_key | string | `"rustfsadmin"` | RustFS Secret Key ID |
|
||
| service.type | string | `"ClusterIP"` | |
|
||
| service.console.nodePort | int | `32001` | |
|
||
| service.console.port | int | `9001` | |
|
||
| service.endpoint.nodePort | int | `32000` | |
|
||
| service.endpoint.port | int | `9000` | |
|
||
| serviceAccount.annotations | object | `{}` | |
|
||
| serviceAccount.automount | bool | `true` | |
|
||
| serviceAccount.create | bool | `true` | |
|
||
| serviceAccount.name | string | `""` | |
|
||
| storageclass.dataStorageSize | string | `"256Mi"` | The storage size for data PVC. |
|
||
| storageclass.logStorageSize | string | `"256Mi"` | The storage size for logs PVC. |
|
||
| storageclass.name | string | `"local-path"` | The name for StorageClass. |
|
||
| storageclass.pvcAnnotations.data | map | `{}` | Data pvc customized annotations. |
|
||
| storageclass.pvcAnnotations.logs | map | `{}` | Logs pvc customized annotations. |
|
||
| tolerations | list | `[]` | |
|
||
| topologySpreadConstraints.enabled | bool | `false` | Enable custom topology spread constraints on distributed-mode StatefulSet pods. |
|
||
| topologySpreadConstraints.constraints | list | `[]` | Raw `spec.template.spec.topologySpreadConstraints` entries applied to the distributed StatefulSet when enabled. |
|
||
| gatewayApi.enabled | bool | `false` | To enable/disable gateway api support. |
|
||
| gatewayApi.gatewayClass | string | `traefik` | Gateway class implementation. |
|
||
| gatewayApi.listeners.http.name | string | `web` | Gateway API http listener name. |
|
||
| gatewayApi.listeners.http.port| int | `8000` | Gateway API http listener port. |
|
||
| gatewayApi.listeners.https.name | string | `websecure` | Gateway API https listener name. |
|
||
| gatewayApi.listeners.https.port| int | `8443` | Gateway API https listener port. |
|
||
| gatewayApi.hostname | string | Hostname to access RustFS via gateway api. |
|
||
| gatewayApi.secretName | string | Secret tls to via RustFS using HTTPS. |
|
||
| gatewayApi.existingGateway.name | string | `""` | The existing gateway name, instead of creating a new one. |
|
||
| gatewayApi.existingGateway.namespace | string | `""` | The namespace of the existing gateway, if not the local namespace. |
|
||
|
||
Scanner values map directly to scanner environment variables. For tuning
|
||
workflow and `/v3/scanner/status` interpretation, see
|
||
[Scanner Runtime Controls](../docs/operations/scanner-runtime-controls.md). For
|
||
repeatable scanner-pressure validation, see
|
||
[Scanner Benchmark Runbook](../docs/operations/scanner-benchmark-runbook.md).
|
||
|
||
---
|
||
|
||
**NOTE**:
|
||
|
||
The chart pulls the rustfs image from Docker Hub by default. For private registries, provide either:
|
||
|
||
- **Existing secrets**: Set `imagePullSecrets` with an array of secret names
|
||
```yaml
|
||
imagePullSecrets:
|
||
- name: my-existing-secret
|
||
```
|
||
|
||
- **Auto-generated secret**: Enable `imageRegistryCredentials.enabled: true` and specify credentials plus your image details
|
||
```yaml
|
||
imageRegistryCredentials:
|
||
enabled: true
|
||
registry: myregistry.com
|
||
username: myuser
|
||
password: mypass
|
||
email: user@example.com
|
||
```
|
||
|
||
Both approaches support pulling from private registries seamlessly and you can also combine them.
|
||
|
||
- The chart default pull rustfs image from dockerhub, if your rustfs image stores in private registry, you can use either existing image Pull secrets with parameter `imagePullSecrets` or create one setting `imageRegistryCredentials.enabled` to `true`,and then specify the `imageRegistryCredentials.registry/username/password/email` as well as `image.rustfs.repository`,`image.rustfs.tag` to pull rustfs image from your private registry.
|
||
|
||
- The default storageclass is [`local-path`](https://github.com/rancher/local-path-provisioner),if you want to specify your own storageclass, try to set parameter `storageclass.name`.
|
||
|
||
- The default size for data and logs dir is **256Mi** which must satisfy the production usage,you should specify `storageclass.dataStorageSize` and `storageclass.logStorageSize` to change the size, for example, 1Ti for data and 1Gi for logs.
|
||
|
||
# Server pools (capacity expansion)
|
||
|
||
In distributed mode the chart can run multiple **server pools** — independent
|
||
StatefulSets whose drives together form one cluster, the same expansion model
|
||
the RustFS server already supports via space-separated `RUSTFS_VOLUMES`
|
||
expressions (`rc admin pool ls` / `expand` / `rebalance` / `decommission`).
|
||
|
||
With `pools.enabled=false` (default) the chart behaves exactly as before:
|
||
one StatefulSet driven by the top-level `replicaCount`/`storageclass`.
|
||
|
||
To expand an existing deployment, enable pools and describe the current
|
||
layout as pool 0 plus your new capacity:
|
||
|
||
```yaml
|
||
pools:
|
||
enabled: true
|
||
list:
|
||
- {} # pool 0: inherits top-level values and keeps the
|
||
# existing StatefulSet/pod/PVC names and data
|
||
- replicaCount: 4 # pool 1: new capacity
|
||
storageclass:
|
||
dataStorageSize: 10Gi
|
||
```
|
||
|
||
Each entry may set `replicaCount` (>= 2) and/or a `storageclass` block;
|
||
omitted fields inherit the top-level values. Additional pools render as
|
||
`<fullname>-pool<N>` StatefulSets; all pools share the headless service,
|
||
the main service, the configuration and the credentials.
|
||
|
||
Notes:
|
||
|
||
* **Pools are append-only.** The list index determines the StatefulSet name —
|
||
never remove or reorder entries. Retire a pool with
|
||
`rc admin decommission` before removing it from the list.
|
||
* During the expansion rollout, pods restart until every pod of every pool is
|
||
resolvable — the server refuses to start with unresolvable peers, so expect
|
||
a few crash/restart cycles before the cluster converges. This is harmless.
|
||
* After the cluster converges, run `rc admin rebalance start <alias>` to
|
||
spread existing objects across the new pool.
|
||
* Pod anti-affinity in pool mode is scoped per pool and **preferred**
|
||
(soft), not required: two pools can share nodes, and each pool's own pods
|
||
spread across distinct nodes when capacity allows. Soft affinity is
|
||
load-bearing for in-place expansion — with required rules, the
|
||
not-yet-rolled pods of the existing pool block the new pool's pods from
|
||
their nodes while the rolled pods crash on the unresolvable (Pending)
|
||
peers, deadlocking the rollout on any cluster with fewer nodes than the
|
||
total pod count. Single-pool deployments (`pools.enabled=false`) keep the
|
||
chart's existing required anti-affinity unchanged.
|
||
* The PodDisruptionBudget spans all pools: with the default
|
||
`pdb.maxUnavailable: 1`, at most one pod of the whole cluster may be
|
||
evicted at a time. This is deliberately conservative — quorum safety
|
||
matters across the union of all pools.
|
||
|
||
# Installation
|
||
|
||
## Requirement
|
||
|
||
* Helm V3
|
||
* RustFS >= 1.0.0-alpha.69
|
||
|
||
Due to the traefik and ingress has different session sticky/affinity annotations, and rustfs support both those two controller, you should specify parameter `ingress.className` to select the right one which suits for you.
|
||
|
||
## Installation with traefik controller
|
||
|
||
If your ingress class is `traefik`, running the command:
|
||
|
||
```
|
||
helm install rustfs -n rustfs --create-namespace ./ --set ingress.className="traefik"
|
||
```
|
||
|
||
## Installation with nginx controller
|
||
|
||
If your ingress class is `nginx`, running the command:
|
||
|
||
```
|
||
helm install rustfs -n rustfs --create-namespace ./ --set ingress.className="nginx"
|
||
```
|
||
|
||
# Installation check and rustfs login
|
||
|
||
Check the pod status
|
||
|
||
```
|
||
kubectl -n rustfs get pods -w
|
||
NAME READY STATUS RESTARTS AGE
|
||
rustfs-0 1/1 Running 0 2m27s
|
||
rustfs-1 1/1 Running 0 2m27s
|
||
rustfs-2 1/1 Running 0 2m27s
|
||
rustfs-3 1/1 Running 0 2m27s
|
||
```
|
||
|
||
Check the ingress status
|
||
|
||
```
|
||
kubectl -n rustfs get ing
|
||
NAME CLASS HOSTS ADDRESS PORTS AGE
|
||
rustfs nginx example.rustfs.com 10.43.237.152 80, 443 29m
|
||
```
|
||
|
||
Access the rustfs cluster via `https://example.rustfs.com` with the default username and password `rustfsadmin`.
|
||
|
||
> Replace the `example.rustfs.com` with your own domain as well as the certificates.
|
||
|
||
# TLS configuration
|
||
|
||
By default, tls is not enabled. If you want to enable tls(recommendated),you can follow below steps:
|
||
|
||
* Step 1: Certification generation
|
||
|
||
You can request cert and key from CA or use the self-signed cert(**not recommendated on prod**), and put those two files(eg, `tls.crt` and `tls.key`) under some directory on server, for example `tls` directory.
|
||
|
||
* Step 2: Certification specifying
|
||
|
||
You should use `--set-file` parameter when running `helm install` command, for example, running the below command can enable ingress tls and generate tls secret:
|
||
|
||
```
|
||
helm install rustfs rustfs/rustfs -n rustfs --set tls.enabled=true,--set-file tls.crt=./tls.crt,--set-file tls.key=./tls.key
|
||
```
|
||
|
||
# Gateway API support (alpha)
|
||
|
||
Due to [ingress nginx retirement](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/) in March 2026, so RustFS adds support for [gateway api](https://gateway-api.sigs.k8s.io/). Currently, RustFS only supports traefik as gateway class, more and more gateway class support will be added in the future after those classes are tested. If you want to enable gateway api, specify `gatewayApi.enabled` to `true` while specify `ingress.enabled` to `false`. After installation, you can find the `Gateway` and `HttpRoute` resources,
|
||
|
||
```
|
||
$ kubectl -n rustfs get gateway
|
||
NAME CLASS ADDRESS PROGRAMMED AGE
|
||
rustfs-gateway traefik True 169m
|
||
|
||
$ kubectl -n rustfs get httproute
|
||
NAME HOSTNAMES AGE
|
||
rustfs-route ["example.rustfs.com"] 172m
|
||
```
|
||
|
||
Then, via RustFS instance via `https://example.rustfs.com` or `http://example.rustfs.com`.
|
||
|
||
# Uninstall
|
||
|
||
Uninstalling the rustfs installation with command,
|
||
|
||
```
|
||
helm uninstall rustfs -n rustfs
|
||
```
|