Skip to content

RustFS storage onboarding (self-hosted)

For operators connecting a RustFS object-storage instance to Cytario. RustFS is the S3-compatible provider for on-premises / self-hosted deployments. The work is split two ways. You prepare the instance once per organization: the OIDC trust, the per-organization access policies, and their groups (steps 1–3). Cytario then manages day-to-day access on its own: it holds no RustFS administrative credentials, never creates users, groups, or per-organization policies on the instance — but it does write the bucket policies that grant your users access, automatically, when buckets are registered and shared in the portal. The admin portal records the connection and its buckets (catalog-only registration).

Using the hosted offering with AWS storage? Storage on-boarding is a guided, one-click flow in the admin portal instead — see Storage management.

Prerequisites

  • RustFS 1.0.0 (GA) or later with STS (web-identity federation) enabled.
  • Cytario's Keycloak identity service (the same service your users sign in to), reachable from the RustFS instance.
  • Admin access to the RustFS instance (mc with a configured alias, or the RustFS admin API) to create policies, groups, and users.
  • The buckets the organization will use must already exist on the instance — Cytario does not create buckets. CORS must be configured by you (see step 4).

1. Configure the OIDC trust

Point the RustFS instance at Cytario's Keycloak via the RUSTFS_IDENTITY_OPENID_* configuration family:

  • RUSTFS_IDENTITY_OPENID_CONFIG_URL — the realm's .well-known/openid-configuration URL (for a Cytario-hosted deployment: https://auth.cytario.com/realms/cytario/.well-known/openid-configuration).
  • RUSTFS_IDENTITY_OPENID_CLIENT_ID=rustfs — the dedicated RustFS client. Cytario presents tokens to your instance from several surfaces (the web app's credential mint, the admin portal's connection verify, the job broker's mints, the cytario CLI's workstation federation), each issued to its own client — rustfs is the one audience Cytario injects into all of them. Do not point RustFS at any other client: RustFS rejects a token whose aud does not contain the configured client id, regardless of any other setting.
  • RUSTFS_IDENTITY_OPENID_OTHER_AUDIENCES — the client ids that appear in presented tokens alongside rustfs. RustFS's audience check is two-part: the token's aud must contain the configured client id, and every additional audience in the token must be listed here. Set it to exactly this list:
RUSTFS_IDENTITY_OPENID_OTHER_AUDIENCES="account,cytario-web,admin-portal,admin-portal-service-account,job-broker,cytario-cli"
  • RUSTFS_IDENTITY_OPENID_CLAIM_NAME=groups — the claim the credential mint resolves against IAM groups. It must be exactly groups; Cytario's mapper emits the marker names into it on every client that presents tokens.

The groups claim carries two kinds of values: the reserved organization marker cy-<alias>, and one composite entry per org-scoped group the user belongs to — cy-<alias>_<path> (e.g. Lab/TeamX → cy-acme_Lab_TeamX) — where <path> is the group's organization-relative path with every / replaced by _. A literal _ in a group name becomes :_, and a literal : becomes :: — so a group named Team_X under Lab is emitted as cy-acme_Lab_Team:_X, and groups whose names contain _ or : are fully supported, never dropped. There is no separate management marker: the organization's root admins group is emitted as its ordinary composite cy-<alias>_admins, and a sub-scope admin group (e.g. Lab/admins) as cy-<alias>_Lab_admins. The composite binds the organization and the group into a single value: bucket policies condition on it with a single-valued StringEquals, so a group path alone (a path another organization could also hold) can never satisfy another organization's policy. All of these values are legal RustFS IAM group names (no /; the encoded path uses only _, :, and alphanumerics, all within RustFS's legal claim-derived name charset), and every one of them can resolve against an IAM group — so a sub-scope admins group, once provisioned, carries the management policy itself.

Note on aliases: an organization alias must not contain _ (nor / nor whitespace) — _ is the composite separator, and an alias containing it could make a value like cy-a_b ambiguous between two organizations (org a's group b vs. org a_b's marker). Group names have no such restriction: any group name is emitted, escaped or not. An organization whose alias violates these charset rules cannot use RustFS storage — the claim values are refused fail-closed and the organization gets no credentials.

Notes:

  • No client secret is required for this trust. The credential paths above only need RustFS to verify Cytario-issued tokens, which RustFS does in public-client mode. Administer your instance with your own root credentials (console key login or mc) — that path is unaffected by the OIDC configuration. Console single sign-on through Cytario's identity service is available on request; it additionally requires a client secret and registering your console callback URL on Cytario's side.
  • Client ids are a wire contract: the rustfs client id and the OTHER_AUDIENCES list above must match what Cytario's identity service emits. If Cytario ever adds a presenting client, the onboarding documentation is updated with the new list — a token carrying an audience that is neither the configured client id nor listed in OTHER_AUDIENCES is rejected.
  • Cyberduck is not supported for RustFS connections yet — the Cyberduck profile download is offered for AWS-backed connections only. (Adding Cyberduck's client id to OTHER_AUDIENCES does not enable it: its tokens do not carry the rustfs audience, so they fail the audience check regardless.)

2. Create the per-organization policies

Two IAM policies per organization, created in the RustFS Console (Identity → Policies → Create Policy). The policy name is the binding key — it must match the claim value the Cytario mapper emits, exactly as written below.

The admission policy — cy-<org-alias>

Attached to the organization-marker group that every member holds. Its only purpose is admission: Cytario's credential mint denies any session that resolves zero policies, so the marker group must carry one — but it grants no data access. Reading, writing, and sharing data all come from the bucket policies Cytario writes automatically when you register and share buckets (step 5) — never from this document.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AdmissionOnly",
      "Effect": "Allow",
      "Action": "s3:GetBucketLocation",
      "Resource": ["arn:aws:s3:::<your-bucket>"]
    }
  ]
}

Add each bucket's ARN to the Resource list as you register more buckets — the mint must resolve a policy covering the bucket it mints against.

The management policy — cy-<org-alias>_admins

Attached to the organization's admins group. This is what lets Cytario read and write the bucket policies that grant your users access, and keep CORS current:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "BucketPolicyManagement",
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketPolicy",
        "s3:PutBucketPolicy",
        "s3:PutBucketCors",
        "s3:GetBucketLocation"
      ],
      "Resource": ["arn:aws:s3:::<your-bucket>"]
    }
  ]
}

Extend the Resource list as you register more buckets, exactly as with the admission policy.

Why GetBucketLocation and not more? Every S3 client asks for the bucket's region on connect (it exposes the region string only — no keys, no data), so it is the smallest useful action to carry the admission. Deliberately absent: any data-plane action (that would bypass the portal's share management — the bucket policies Cytario writes are the single source of data access) and s3:GetBucketOwnershipControls (RustFS's policy action registry rejects it as unimplemented).

3. Create the claim-named groups and attach the policies

In the RustFS Console (Identity → Groups), create exactly two groups per organization and attach the matching policy from step 2:

Group name Policy Who the claim value covers
cy-<org-alias> cy-<org-alias> (admission) every organization member
cy-<org-alias>_admins cy-<org-alias>_admins (management) the organization's root admins

Cytario's credential mint resolves each token groups value against IAM groups by exact name — the group name is the encoded claim value, copied verbatim. RustFS requires each group to have at least one member; a placeholder access-key user is fine (Identity → Users → Create User), since the OIDC mapping resolves by group name, not by membership of specific humans.

No other IAM groups or policies are needed. Ordinary organization groups (e.g. Lab/TeamX) never get an IAM counterpart — the bucket policies Cytario writes match them via the claim composite (e.g. cy-acme_Lab_TeamX) directly in the policy condition, so there is nothing to provision per group. The only exception: if a sub-scope has its own admins (e.g. Lab/admins) who manage shares in the portal, create one additional group named exactly as that scope's composite — cy-<org-alias>_Lab_admins (every / in the scope path becomes _, a literal _ becomes :_, a literal : becomes ::) — and attach the management policy to it too.

If a group is renamed, deleted, or loses its attached policy, the organization's sessions resolve zero policies and are denied at the mint (fail-closed) — re-check these names first when access breaks.

4. Create the buckets and configure CORS

Create each bucket the organization will register (Buckets → Create Bucket in the RustFS Console), and apply the CORS configuration so the browser can read and write directly. In the Console's bucket settings, set the CORS rules to:

{
  "CORSRules": [
    {
      "AllowedOrigins": [
        "https://<your-cytario-web-host>",
        "https://<your-cytario-app-host>"
      ],
      "AllowedMethods": ["GET", "HEAD", "PUT", "DELETE", "POST"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": [
        "ETag",
        "Content-Range",
        "Accept-Ranges",
        "Content-Length"
      ],
      "MaxAgeSeconds": 3000
    }
  ]
}

If you provision without the Console, the S3 API equivalent works too (RustFS honors the S3 PutBucketCors semantics):

aws --endpoint-url https://<rustfs-endpoint> s3api put-bucket-cors \
  --bucket <bucket> --cors-configuration file://cors.json

5. Register the connection and buckets in the admin portal

In the admin portal (Infrastructure providers → Connect account), select the RustFS provider type and enter:

  • an optional display name (defaults to RustFS <endpoint host>), and
  • the RustFS endpoint URL (https://…).

No AWS account id, region, or CloudFormation hand-off is involved.

Verify connection runs a test web-identity assume against the endpoint. A verify that resolves zero mapped policies fails with a message naming the operator prerequisite — usually a missing claim-named group or an unattached policy in step 3. A failed verify keeps the record with its cause; provision the missing piece and use Re-verify to heal it.

Then register the organization's buckets under the connection (Storage → Add bucket). Registration records the bucket and its access levels — the portal writes the bucket policies that grant your users access on top of your step-2 management policy, but it does not create buckets, users, or groups on the instance. What you provisioned in steps 2–4 is what Cytario's policies can grant.

6. How users connect

Organization members see the registered buckets in the main app's Add Connection flow — see Connect your storage. Nothing else is required on the RustFS side.

When you stop using a connected account, see Disconnecting storage / off-boarding.