Gates bucket listing behind an identity policy, lets browsers reach the standalone IAM API, and turns the WebUI into a dashboard for S3, IAM, or both.
**Bucket listing.** `ListBuckets` is now gated by the new `s3:ListAllMyBuckets` action, evaluated against `arn:aws:s3:::*`. The request names no bucket, so only identity policies apply — there is no resource policy to combine with, which is the same shape `CreateBucket` already had, so both now share one identity-only evaluation path. Root and admin bypass it, and backends with no identity-policy layer keep listing as before since their listing is already narrowed to the caller's own buckets. The action is IAM-only and is deliberately absent from the bucket-policy action list.
**Fixed bucket ownership.** The standalone IAM client has no per-user ownership to express — accounts are all plain users, cannot be enumerated, and access is decided by policy rather than ACL — so it now implements `auth.FixedBucketOwner` and every bucket is owned by root. Bucket creation stops resolving an owner, `ListBuckets` returns every bucket to every caller (what they may then do with one stays a per-request policy decision), and the admin `ChangeBucketOwner` reports method-not-supported. Other IAM backends are untouched.
**IAM service CORS.** `--cors-allow-origin` now applies to the `iam` command: it answers preflights and stamps the CORS headers, mirroring back the requested method and headers rather than enumerating the SigV4 header set. Without it no browser can reach the IAM API at all, so setting `--webui` without it falls back to `*` with a warning. The chart gets `iamServer.corsAllowOrigin`.
**WebUI.** New IAM pages for users, roles and OIDC providers, signing IAM/STS query-form requests directly from the browser. Navigation is capability-gated rather than role-gated: on sign-in the session probes the S3, admin and IAM endpoints independently and each page shows only what those credentials actually reach, so one build serves an IAM-only dashboard, an S3-only dashboard, and a combined one. The login page takes an optional IAM endpoint, seeded from the new `--webui-iam-gateways` (chart: `webui.iamGateways`) — never auto-detected, since the IAM service is a separate process. The WebUI can also be hosted by `versitygw iam` itself, for deployments with no S3 gateway behind it.
**The admin API is ignored once an IAM endpoint is in play.** The IAM service is then the user directory and bucket ownership is fixed, which leaves the admin API no job: the session is given no admin endpoint at all, its login field is hidden, `users.html` redirects to its IAM counterpart, and every admin-only surface stays off screen. Dashboard and Buckets remain available to any S3 session in such a deployment, running on the S3 and IAM APIs alone and surfacing each denial per action instead of redirecting.
Also fixes two WebUI bugs: embedded assets went out with a zero modification time and no `Cache-Control`, so browsers treated them as fresh for centuries and an upgraded gateway served new HTML against stale JS — they now revalidate against an ETag; and the login page's advanced-options section clipped its last field, since it animated to a height named in the stylesheet rather than the one it measures now.
**Usage**
IAM-only dashboard, served by the IAM service:
versitygw iam --port :7076 --webui :8080 --cors-allow-origin http://localhost:8080/
IAM + S3, dashboard served by the IAM service — point it at the gateway with `--webui-gateways`, and let the gateway accept the dashboard's origin:
versitygw iam --port :7076 --webui :8080 --webui-gateways http://localhost:7070/ --cors-allow-origin http://localhost:8080/
versitygw --port :7070 --cors-allow-origin http://localhost:8080/ posix /data
IAM + S3, dashboard served by the S3 gateway — point it at the IAM service with `--webui-iam-gateways`, and let the IAM service accept the dashboard's origin:
versitygw --port :7070 --webui :8080 --webui-iam-gateways http://localhost:7076/ posix /data
versitygw iam --port :7076 --cors-allow-origin http://localhost:8080/
13 KiB
versitygw Helm Chart
Versity is an S3-compatible storage gateway that proxies S3 API requests to a variety of backend storage systems.
Note
: the chart is currently in development state and breaking changes (with regards to the Helm values structure or the chart behavior) may occur until we reach a 1.0 release of the Helm chart.
Overview
versitygw is an S3-compatible gateway that fronts POSIX filesystems, ScoutFS, S3, Azure Blob Storage, or custom plugin backends. This chart deploys versitygw on Kubernetes as a Deployment and Service, with optional support for TLS termination, Ingress, HTTPRoutes, certificate provisioning (via cert-manager CRDs), IAM, an Admin API, a browser-based WebUI, persistent storage, and NetworkPolicy.
Prerequisites
- Kubernetes 1.19+
- Helm 3.8+ (OCI registry support)
- optional: cert-manager (required when any of
certificate.create,iam.standalone.certificate.create, oriamServer.private.certificate.createis enabled)
Installation
Basic installation (single user mode) with posix backend:
helm install my-versitygw oci://ghcr.io/versity/versitygw/charts/versitygw \
--set auth.accessKey=myaccesskey \
--set auth.secretKey=mysecretkey \
--set gateway.backend.type=posix \
--set persistence.enabled=true
Production note: Passing credentials via
--setstores them in Helm's release history. For production deployments, create a Kubernetes Secret in advance and reference it withauth.existingSecret=<secret-name>. The Secret must contain the keysrootAccessKeyIdandrootSecretAccessKey.
Upgrading
The versioning of this Helm chart and of versitygw itself are currently not coupled to each other.
By default, the Helm chart uses the latest tag for the versitygw container image.
For production and multi-replica deployment, it is strongly recommended to always pin a specific version, like so:
# values.yaml
image:
repository: ghcr.io/versity/versitygw
tag: "v1.2.0"
To upgrade the versitygw version, only the image.tag value needs to be adjusted and the Helm charts needs to be re-deployed (with the same values), e.g.:
helm upgrade my-versitygw oci://ghcr.io/versity/versitygw/charts/versitygw \
--reuse-values \
--set image.tag=v1.3.1
To upgrade only the Helm chart, use the following command:
helm upgrade my-versitygw oci://ghcr.io/versity/versitygw/charts/versitygw \
--reuse-values \
--version 0.2.0
You can find the list of available Helm chart versions in the GitHub packages page.
Backend Storage
The gateway.backend.type value selects the storage backend. Use gateway.backend.args to pass backend-specific arguments. For the POSIX sidecar metadata store and POSIX/ScoutFS object versioning, prefer gateway.backend.sidecarDir and gateway.backend.versioningDir; the chart mounts those directories from persistent storage and wires the corresponding backend environment variables automatically.
| Backend | Description | Example gateway.backend.args |
|---|---|---|
| posix | POSIX-compatible local or network filesystem (default) | /mnt/data |
| scoutfs | ScoutFS high-performance filesystem | /mnt/scoutfs |
| s3 | Proxy to an existing S3-compatible object store | --access KEY --secret SECRET --endpoint https://s3.example.com |
| azure | Azure Blob Storage | --account myaccount --key mykey |
| plugin | Custom backend via shared library plugin | /path/to/plugin.so |
Example for POSIX with sidecar metadata:
gateway:
backend:
type: posix
args: /mnt/data
sidecarDir: /mnt/metadata
Example for POSIX or ScoutFS with object versioning enabled:
gateway:
backend:
type: posix
args: /mnt/data
versioningDir: /mnt/versioning
Optional Features
| Feature | Key values |
|---|---|
| TLS | tls.enabled=true — serve HTTPS; supply a TLS Secret via certificate.secretName or let cert-manager provision one |
| cert-manager | certificate.create=true, certificate.issuerRef, certificate.dnsNames |
| Ingress | ingress.enabled=true, ingress.className, ingress.hosts, ingress.tls |
| HTTPRoute | httpRoute.enabled=true — Gateway API successor to Ingress for S3 API; also admin.httpRoute.enabled=true and webui.httpRoute.enabled=true to expose the admin API and/or WebUI |
| Admin API | admin.enabled=true — exposes a separate management API on admin.port (default 7071) |
| WebUI | webui.enabled=true — browser-based management UI on webui.port (default 8080); set webui.apiGateways and webui.adminGateways to your externally reachable endpoints, and webui.iamGateways when iam.type=standalone so the login page offers the IAM service (the WebUI then ignores the admin API entirely — the IAM service manages users, and buckets are managed over the S3 API) |
| Website Hosting | website.enabled=true — static website hosting endpoint on website.port (default 8090); optionally set website.domain for virtual-host routing (e.g. example.com), or omit it for catch-all mode where the full hostname is the bucket name |
| IAM | iam.enabled=true — identity and access management. iam.type=internal (default) stores accounts in a flat file alongside backend data; iam.type=standalone delegates to a separate standalone IAM API service — see Standalone IAM Service below |
| Persistence | persistence.enabled=true — provisions a PVC for backend data and IAM storage; defaults to 10Gi, or uses a hostPath volume specified by persistence.hostPath |
| NetworkPolicy | networkPolicy.enabled=true — restricts ingress to selected pods/namespaces; allows all egress |
| Debug logging | gateway.logLevel — silent (default), debug (request/response logging, secrets masked), or unsafe (unmasked, local troubleshooting only) |
| Scheduling | nodeSelector, affinity, tolerations, and topologySpreadConstraints — control pod placement and spread replicas across nodes/zones for high availability |
Standalone IAM Service
In addition to iam.type=internal (flat-file IAM stored inside the gateway pod), the chart can deploy the standalone IAM API server — an AWS-compatible IAM Query API — as its own Deployment with separate public and private Services, and configure one or more gateways to use it via iam.type=standalone.
iam:
enabled: true
type: standalone
standalone:
# Left empty here: auto-targets the in-chart private IAM Service below.
certificate:
create: true
issuerRef:
kind: ClusterIssuer
name: internal-ca
iamServer:
enabled: true
storage:
type: internal # or vault
private:
certificate:
create: true
issuerRef:
kind: ClusterIssuer
name: internal-ca
Key points:
- Independent scaling:
iamServeris a separate Deployment (iamServer.replicaCount), so it can be centralized and scaled independently of the gateway. Manage users/roles/policies against its public control-plane API (iamServer.port, default7070) using the AWS CLI/SDK. It reuses the gateway root Secret by default; setiamServer.auth.existingSecretto separate the control-plane identity, and pointiam.standalone.credentials.existingSecretat the corresponding client identity. - Storage:
iamServer.storage.typeisinternal(file-backed, needsiamServer.persistence, is limited to one replica, and always uses aRecreaterollout) orvault(iamServer.storage.vault.*, centralized and required ifiamServer.replicaCount > 1). - Separate Services:
iamServer.service.typeapplies only to the public control-plane Service. The private listener is exposed by a separate, always-ClusterIPService, so selectingNodePortorLoadBalancerdoes not publish the private port. EnableiamServer.tlsbefore exposing the public API outside a trusted network. - Private mTLS endpoint: gateways reach the standalone IAM service over a private endpoint (
iamServer.private.port, default7443) that always requires mutual TLS on TCP. Provide certificates either viaexistingSecret(bring your owntls.crt/tls.key/ca.crt) orcertificate.create=trueto auto-provision via cert-manager. - Shared CA requirement: when using cert-manager auto-provisioning,
iamServer.private.certificate.issuerRefandiam.standalone.certificate.issuerRefmust reference the same CA-type issuer (anIssuer/ClusterIssuerof kindCA, or a Vault issuer) — one that populatesca.crtin the resulting Secret. Both sides verify their peer using their own certificate'sca.crt, which only works when both certificates share the same issuing CA. - External IAM service: to point a gateway at a standalone IAM service deployed outside this chart (or by a separate chart release), set
iam.standalone.endpointto itshost:portand provide the mTLS material viaiam.standalone.certificate.existingSecret. - WebUI access: to manage IAM users from the WebUI, set
webui.iamGatewaysto the URL a browser can reachiamServeron, andiamServer.corsAllowOriginto the WebUI's own origin. Every WebUI call to the IAM API is cross-origin, so withoutcorsAllowOriginthe browser blocks it and the WebUI's IAM navigation silently never appears. - Secret rotation: the processes load mTLS material and environment-based credentials at startup. After a referenced Secret rotates, restart both Deployments or configure a Secret-reloader controller through
deploymentAnnotationsandiamServer.deploymentAnnotations.
Scaling and Persistence
By default, this chart enables persistence via a PersistentVolumeClaim (PVC) to ensure data consistency and prevent data loss.
Alternatively, you may use hostPath volume by setting the persistence.hostPath value.
As a general rule, this setup should only be used if all nodes in the cluster have access to the same data (e.g. NFS share is mounted on all nodes) or for single-node use cases.
Special care must be taken particularly when using multiple replicas with such a setup, since Versity does not perform internal data replication ("clustering").
Horizontal Scaling (replicas > 1)
When scaling versitygw horizontally by setting replicaCount greater than 1, special care must be taken regarding the storage backend:
- POSIX: This backend stores state on the filesystem.
- Using ReadWriteOnce (RWO): All replicas must be scheduled on the same Kubernetes node to share the same volume. This is useful for process-level concurrency (e.g., when using high-performance local block storage) but limits high availability across nodes.
- Using ReadWriteMany (RWX): Replicas can be distributed across multiple nodes in the cluster. This is the recommended approach for true horizontal scaling and high availability. When using RWX, it is also recommended to use pod anti-affinity (via
affinityinvalues.yaml) or topology spread constraints (viatopologySpreadConstraintsinvalues.yaml) to ensure pods are distributed across nodes/zones.
- IAM:
iam.type=internalis limited to a single gateway replica because its file store does not coordinate concurrent writers. Use standalone IAM with Vault storage, LDAP, Vault-direct, or another external IAM backend before scaling the gateway above one replica. - Stateless Backends (S3, Azure): If you are using a stateless storage backend (e.g. proxying to another S3 store) and you are either not using IAM or using an external IAM provider (e.g. LDAP, Vault), persistence can be safely disabled by setting
persistence.enabled=false.
Deployment Strategy
By default, the RollingUpdate strategy is used.
If ReadWriteOnce (RWO) volumes are used and pods may be scheduled onto different nodes, rollouts may become
stuck because the replacement pod cannot start. Consider setting strategy.type=Recreate in this case.
Configuration
See values.yaml for the full list of parameters and their defaults.