Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion console-web/app/(dashboard)/tenants/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,21 @@ export default function TenantCreatePage() {
const user = asRecord(item)
const userName = asString(user?.name)
const userPolicies = asStringArray(user?.policies)
if (!user || !userName || !userPolicies || userPolicies.length === 0) {
const rawCredsSecret = user?.credsSecret ?? user?.creds_secret
const credsSecret = asRecord(rawCredsSecret)
const credsSecretName = asString(credsSecret?.name)
if (
!user ||
!userName ||
!userPolicies ||
userPolicies.length === 0 ||
(rawCredsSecret != null && !credsSecretName)
) {
throw new Error(t("YAML user provisioning fields are invalid"))
}
return {
name: userName,
credsSecret: credsSecretName ? { name: credsSecretName } : undefined,
policies: userPolicies,
}
})
Expand Down
5 changes: 5 additions & 0 deletions console-web/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export interface ProvisioningPolicy {

export interface ProvisioningUser {
name: string
credsSecret?: {
name: string
}
policies: string[]
deletionPolicy?: ProvisioningDeletionPolicy
}
Expand All @@ -136,6 +139,8 @@ export interface ProvisioningItemStatus {
lastAppliedHash?: string | null
lastAppliedGeneration?: number | null
observedSecretResourceVersion?: string | null
observedSecretName?: string | null
lastAppliedAccessKeyHash?: string | null
policies?: string[]
region?: string | null
objectLock?: boolean | null
Expand Down
8 changes: 7 additions & 1 deletion deploy/rustfs-operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,16 @@ spec:
key: policy.json
users:
- name: app-user
credsSecret:
name: rustfs-user-app-user
policies:
- app-readwrite
buckets:
- name: app-data
objectLock: true
```

Policy ConfigMaps and user Secrets must live in the Tenant namespace. If they are created outside the Operator Console, add `rustfs.tenant=<tenant-name>` so changes to those resources enqueue the owning Tenant. Provisioned resources are retained when removed from the Tenant spec.
Policy ConfigMaps and user Secrets must live in the Tenant namespace. `users[].credsSecret.name` selects the credentials Secret; when omitted, the operator falls back to a Secret named after `users[].name` for compatibility with existing manifests. The operator maintains `rustfs.tenant=<tenant-name>` on referenced policy ConfigMaps and user Secrets so changes enqueue the owning Tenant; references that do not exist yet are retried. The label triggers reconciliation but does not select the Secret. Provisioned resources are retained when removed from the Tenant spec.

### RBAC Configuration

Expand Down Expand Up @@ -333,9 +335,13 @@ kubectl apply -f examples/simple-tenant.yaml
To upgrade the operator:

```bash
kubectl apply -f deploy/rustfs-operator/crds/tenant-crd.yaml
helm upgrade rustfs-operator deploy/rustfs-operator/
```

Helm does not upgrade CRDs from the chart's `crds/` directory. Apply the updated Tenant CRD and wait for the operator rollout before using newly introduced Tenant fields such as `users[].credsSecret`.
Existing manifests that omit the field remain compatible. Older operator binaries ignore the reference and continue using the same-name Secret convention, so complete the rollout before relying on it.

## Console UI

The published `rustfs/operator` image contains both the Console backend (Rust API,
Expand Down
22 changes: 22 additions & 0 deletions deploy/rustfs-operator/crds/tenant-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1761,6 +1761,17 @@ spec:
description: Regular users that should exist in the RustFS tenant.
items:
properties:
credsSecret:
description: Optional credentials Secret reference. Defaults to a Secret named after the user.
nullable: true
properties:
name:
maxLength: 253
minLength: 1
type: string
required:
- name
type: object
deletionPolicy:
enum:
- Retain
Expand Down Expand Up @@ -1792,6 +1803,8 @@ spec:
x-kubernetes-validations:
- message: user policies must contain at least one policy
rule: self.all(x, has(x.policies) && x.policies.size() > 0)
- message: user credential Secret references must be unique
rule: 'self.all(x, self.exists_one(y, (has(x.credsSecret) ? x.credsSecret.name : x.name) == (has(y.credsSecret) ? y.credsSecret.name : y.name)))'
required:
- pools
type: object
Expand Down Expand Up @@ -2207,6 +2220,9 @@ spec:
objectLock:
nullable: true
type: boolean
observedSecretName:
nullable: true
type: string
observedSecretResourceVersion:
nullable: true
type: string
Expand Down Expand Up @@ -2266,6 +2282,9 @@ spec:
objectLock:
nullable: true
type: boolean
observedSecretName:
nullable: true
type: string
observedSecretResourceVersion:
nullable: true
type: string
Expand Down Expand Up @@ -2313,6 +2332,9 @@ spec:
objectLock:
nullable: true
type: boolean
observedSecretName:
nullable: true
type: string
observedSecretResourceVersion:
nullable: true
type: string
Expand Down
22 changes: 22 additions & 0 deletions deploy/rustfs-operator/crds/tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1761,6 +1761,17 @@ spec:
description: Regular users that should exist in the RustFS tenant.
items:
properties:
credsSecret:
description: Optional credentials Secret reference. Defaults to a Secret named after the user.
nullable: true
properties:
name:
maxLength: 253
minLength: 1
type: string
required:
- name
type: object
deletionPolicy:
enum:
- Retain
Expand Down Expand Up @@ -1792,6 +1803,8 @@ spec:
x-kubernetes-validations:
- message: user policies must contain at least one policy
rule: self.all(x, has(x.policies) && x.policies.size() > 0)
- message: user credential Secret references must be unique
rule: 'self.all(x, self.exists_one(y, (has(x.credsSecret) ? x.credsSecret.name : x.name) == (has(y.credsSecret) ? y.credsSecret.name : y.name)))'
required:
- pools
type: object
Expand Down Expand Up @@ -2207,6 +2220,9 @@ spec:
objectLock:
nullable: true
type: boolean
observedSecretName:
nullable: true
type: string
observedSecretResourceVersion:
nullable: true
type: string
Expand Down Expand Up @@ -2266,6 +2282,9 @@ spec:
objectLock:
nullable: true
type: boolean
observedSecretName:
nullable: true
type: string
observedSecretResourceVersion:
nullable: true
type: string
Expand Down Expand Up @@ -2313,6 +2332,9 @@ spec:
objectLock:
nullable: true
type: boolean
observedSecretName:
nullable: true
type: string
observedSecretResourceVersion:
nullable: true
type: string
Expand Down
16 changes: 12 additions & 4 deletions docs/operator-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,14 @@ kubectl logs -n rustfs-system \
Upgrade an existing installation:

```bash
kubectl apply -f deploy/rustfs-operator/crds/tenant-crd.yaml
helm upgrade rustfs-operator deploy/rustfs-operator/ \
--namespace rustfs-system
```

Helm does not upgrade CRDs from a chart's `crds/` directory. Apply the updated Tenant CRD before using fields introduced by a newer operator version, then wait for the operator rollout to complete.
Existing Tenant manifests that omit newer optional fields require no migration. Do not rely on `users[].credsSecret` while any older operator binary is still reconciling the Tenant, because older versions use the same-name Secret convention.

Uninstall:

```bash
Expand Down Expand Up @@ -594,13 +598,15 @@ The operator can create RustFS policies, users, and buckets after the Tenant wor
- `spec.users` for regular users. Each user must have at least one direct policy mapping.
- `spec.buckets` for buckets and optional object lock.

ConfigMaps and user Secrets must live in the Tenant namespace. If managed outside the Operator Console, label them with `rustfs.tenant=<tenant-name>` so updates enqueue the owning Tenant.
ConfigMaps and user Secrets must live in the Tenant namespace. The operator maintains `rustfs.tenant=<tenant-name>` on referenced policy ConfigMaps and user Secrets so updates enqueue the owning Tenant. References that do not exist yet are retried after they are created.

Policy documents are parsed by RustFS. Use S3 ARN resource patterns such as `arn:aws:s3:::bucket` and `arn:aws:s3:::bucket/*`; for all buckets, use `arn:aws:s3:::*`. A bare `Resource: "*"` is not accepted by RustFS policy parsing.

For each `spec.users[]` entry, the operator reads a Secret with the same name as the user. The Secret must contain `accesskey` and `secretkey`, or the MinIO-compatible keys `CONSOLE_ACCESS_KEY` and `CONSOLE_SECRET_KEY`. If both key formats are present, their values must match. User access keys must be at least 8 characters and must not contain whitespace, `=`, or `,`; user secret keys must be at least 8 characters.
For each `spec.users[]` entry, set `credsSecret.name` to select a user credentials Secret in the Tenant namespace. If `credsSecret` is omitted, the operator reads a Secret with the same name as `user.name`, preserving the behavior of older manifests. An explicit reference is authoritative and does not fall back to the same-name Secret when it is invalid or missing. Each resolved Secret name must be unique within the Tenant; the API rejects duplicate references, and reconciliation also blocks them when an older installed CRD does not enforce the validation. Distinct Secrets must also contain distinct `accesskey` values. Reconciliation validates all user credentials before modifying any RustFS user and rejects every colliding entry.

The Secret must contain `accesskey` and `secretkey`, or the MinIO-compatible keys `CONSOLE_ACCESS_KEY` and `CONSOLE_SECRET_KEY`. If both key formats are present, their values must match. `user.name` remains the declarative and status identity; the Secret's `accesskey` is the actual RustFS user. User access keys must be at least 8 characters and must not contain whitespace, `=`, or `,`; user secret keys must be at least 8 characters. The `rustfs.tenant` label only causes Secret updates to enqueue the Tenant; it does not select which Secret is read.

Updating a user Secret's `secretkey` rotates that RustFS user's credential. The `accesskey` is immutable after the first successful reconciliation; use a new user entry and Secret when it must change, then migrate clients before removing the old entry.
Updating a user Secret's `secretkey`, or changing `credsSecret.name` to another Secret with the same `accesskey`, rotates that RustFS user's credential. The `accesskey` is immutable after the first successful reconciliation; use a new user entry and Secret when it must change, then migrate clients before removing the old entry.

```yaml
apiVersion: v1
Expand All @@ -626,7 +632,7 @@ data:
apiVersion: v1
kind: Secret
metadata:
name: app-user
name: rustfs-user-app-user
namespace: storage
labels:
rustfs.tenant: rustfs-a
Expand Down Expand Up @@ -656,6 +662,8 @@ spec:
key: policy.json
users:
- name: app-user
credsSecret:
name: rustfs-user-app-user
policies:
- app-readwrite
buckets:
Expand Down
16 changes: 12 additions & 4 deletions docs/operator-user-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ kubectl logs -n rustfs-system \
升级已有安装:

```bash
kubectl apply -f deploy/rustfs-operator/crds/tenant-crd.yaml
helm upgrade rustfs-operator deploy/rustfs-operator/ \
--namespace rustfs-system
```

Helm 不会升级 Chart `crds/` 目录中的已有 CRD。使用新版本 Operator 引入的字段前,请先应用更新后的 Tenant CRD,并等待 Operator rollout 完成。
未配置新版可选字段的已有 Tenant manifest 无需迁移。如果仍有旧版 Operator binary 在 reconcile Tenant,请勿依赖 `users[].credsSecret`,因为旧版本仍会按 user 同名规则读取 Secret。

卸载:

```bash
Expand Down Expand Up @@ -594,13 +598,15 @@ Operator 可以在 Tenant workload Ready 后自动创建 RustFS policy、user
- `spec.users`:普通用户。每个 user 必须至少直接绑定一个 policy。
- `spec.buckets`:bucket,可选择开启 object lock。

ConfigMap 和 user Secret 必须位于 Tenant namespace。若这些资源不是通过 Operator Console 创建,建议添加 label:`rustfs.tenant=<tenant-name>`,这样资源变化可以触发 owning Tenant reconcile。
ConfigMap 和 user Secret 必须位于 Tenant namespace。operator 会在被引用的 policy ConfigMap 和 user Secret 上维护 `rustfs.tenant=<tenant-name>` label,使资源变化能够触发 owning Tenant reconcile;尚未创建的引用资源会在创建后重试加 label

Policy document 由 RustFS 解析。请使用 `arn:aws:s3:::bucket` 和 `arn:aws:s3:::bucket/*` 这类 S3 ARN resource 写法;如需匹配所有 bucket,请使用 `arn:aws:s3:::*`。RustFS policy parser 不接受裸 `Resource: "*"`。

每个 `spec.users[]` 条目都会读取一个与 user 名同名的 Secret。Secret 必须包含 `accesskey` 和 `secretkey`,或者 MinIO 兼容 key:`CONSOLE_ACCESS_KEY` 和 `CONSOLE_SECRET_KEY`。如果两种 key 同时存在,值必须一致。user access key 至少 8 个字符,且不能包含空白、`=` 或 `,`;user secret key 至少 8 个字符。
每个 `spec.users[]` 条目都可以通过 `credsSecret.name` 指定 Tenant namespace 中的 user credentials Secret。省略 `credsSecret` 时,Operator 继续读取与 `user.name` 同名的 Secret,以兼容旧版 manifest。显式引用是唯一来源;配置错误或 Secret 不存在时,不会再回退到同名 Secret。同一 Tenant 内解析后的 Secret 名称必须唯一;API 会拒绝重复引用,而在集群仍安装旧版 CRD、尚未启用该校验时,reconcile 也会阻止这些重复配置生效。不同 Secret 中的 `accesskey` 也必须唯一;reconcile 会先校验全部 user credentials,再修改任何 RustFS user,并拒绝所有冲突条目。

Secret 必须包含 `accesskey` 和 `secretkey`,或者 MinIO 兼容 key:`CONSOLE_ACCESS_KEY` 和 `CONSOLE_SECRET_KEY`。如果两种 key 同时存在,值必须一致。`user.name` 仍是声明和 status 中的逻辑标识,Secret 内的 `accesskey` 才是实际 RustFS user。user access key 至少 8 个字符,且不能包含空白、`=` 或 `,`;user secret key 至少 8 个字符。`rustfs.tenant` label 只负责在 Secret 更新时触发 Tenant reconcile,不参与选择要读取的 Secret。

更新 user Secret 的 `secretkey` 会轮换对应 RustFS user 的凭据。首次成功 provisioning 后,`accesskey` 不可变;如需变更,请新建 user 条目和 Secret,迁移客户端后再移除旧条目。
更新 user Secret 的 `secretkey`,或者把 `credsSecret.name` 切换到具有相同 `accesskey` 的另一个 Secret,都会轮换对应 RustFS user 的凭据。首次成功 provisioning 后,`accesskey` 不可变;如需变更,请新建 user 条目和 Secret,迁移客户端后再移除旧条目。

```yaml
apiVersion: v1
Expand All @@ -626,7 +632,7 @@ data:
apiVersion: v1
kind: Secret
metadata:
name: app-user
name: rustfs-user-app-user
namespace: storage
labels:
rustfs.tenant: rustfs-a
Expand Down Expand Up @@ -656,6 +662,8 @@ spec:
key: policy.json
users:
- name: app-user
credsSecret:
name: rustfs-user-app-user
policies:
- app-readwrite
buckets:
Expand Down
7 changes: 5 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,20 @@ Demonstrates operator-managed RustFS canned policies, regular users, and buckets
**Features demonstrated:**
- Tenant admin credentials through `spec.credsSecret`
- Policy document stored in a labeled ConfigMap
- User credentials stored in a labeled Secret
- User credentials stored in a labeled Secret selected by `users[].credsSecret.name`
- Required non-empty direct policy mapping for each user
- Bucket creation with object lock verification

For backward compatibility, omitting `users[].credsSecret` still selects a Secret
with the same name as the logical user.

**Deployment:**
```bash
kubectl apply -f examples/provisioning-tenant.yaml
kubectl wait --for=condition=Ready tenant/provisioning-demo --timeout=10m
```

ConfigMaps and user Secrets should carry `rustfs.tenant=<tenant-name>` when they are managed outside the Console so updates enqueue the owning Tenant.
The operator maintains `rustfs.tenant=<tenant-name>` on referenced policy ConfigMaps and user Secrets so updates enqueue the owning Tenant.

---

Expand Down
6 changes: 4 additions & 2 deletions examples/provisioning-tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ data:
apiVersion: v1
kind: Secret
metadata:
name: provisioning-app-user
name: rustfs-user-app-user
namespace: default
labels:
rustfs.tenant: provisioning-demo
Expand Down Expand Up @@ -80,7 +80,9 @@ spec:
key: policy.json

users:
- name: provisioning-app-user
- name: app-user
credsSecret:
name: rustfs-user-app-user
policies:
- app-readwrite

Expand Down
Loading
Loading