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
43 changes: 43 additions & 0 deletions deploy/rustfs-operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,49 @@ Operator STS does not present a client certificate when calling the Tenant. Tena

When `operator.serviceMonitor.enabled=true`, the chart creates scrape targets for both the operator observability endpoint and the Console API `/metrics` endpoint.

### Tenant RPC Authentication

Use `spec.rpcSecret` to keep RustFS internode RPC authentication independent from
the administrator credentials in `spec.credsSecret`:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: rustfs-rpc-auth
namespace: storage
labels:
# Lets Secret updates enqueue this Tenant for prompt revalidation.
rustfs.tenant: rustfs-a
type: Opaque
stringData:
rpc-secret: "replace-with-a-dedicated-rpc-secret"
---
apiVersion: rustfs.com/v1alpha1
kind: Tenant
metadata:
name: rustfs-a
namespace: storage
spec:
credsSecret:
name: rustfs-admin-creds
rpcSecret:
name: rustfs-rpc-auth
key: rpc-secret
```

The operator maps the selected Secret key to `RUSTFS_RPC_SECRET` in every RustFS
Pod. Before applying workloads, it verifies that the Secret and selected key
exist and that the value is valid UTF-8, non-blank, contains no NUL bytes, and is
not the RustFS default credential value (`rustfsadmin`). Keep this value stable
while rotating administrator credentials. The `rustfs.tenant` label is not used
for authorization; it lets updates to an externally managed Secret enqueue the
Tenant for revalidation. A Secret update does not change the environment of
already-running Pods. Coordinated restart and hot reload are outside this
feature. If `spec.rpcSecret` is omitted, the operator does not set
`RUSTFS_RPC_SECRET`, RustFS resolves it from its own credential configuration,
and the operator does not report `RpcAuthReady` for that unmanaged value.

### Tenant Provisioning

Tenants can declare RustFS canned policies, regular users, and buckets directly in `spec.policies`, `spec.users`, and `spec.buckets`. Provisioning starts only after the Tenant workload is ready, uses `spec.credsSecret` as the RustFS admin credential source, and reports progress under `status.provisioning`.
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 @@ -1410,6 +1410,28 @@ spec:
priorityClassName:
nullable: true
type: string
rpcSecret:
description: |-
Optional Secret key selector for RustFS internode RPC authentication.

When configured, the operator maps this key to `RUSTFS_RPC_SECRET` for every
RustFS Pod. Keep it stable while rotating `credsSecret`. When omitted, the
operator does not set `RUSTFS_RPC_SECRET`; RustFS resolves it from its own
credential configuration. Label an externally managed Secret with
`rustfs.tenant=<Tenant name>` so Secret updates enqueue the Tenant for
prompt revalidation.
nullable: true
properties:
key:
minLength: 1
type: string
name:
minLength: 1
type: string
required:
- key
- name
type: object
scheduler:
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 @@ -1410,6 +1410,28 @@ spec:
priorityClassName:
nullable: true
type: string
rpcSecret:
description: |-
Optional Secret key selector for RustFS internode RPC authentication.

When configured, the operator maps this key to `RUSTFS_RPC_SECRET` for every
RustFS Pod. Keep it stable while rotating `credsSecret`. When omitted, the
operator does not set `RUSTFS_RPC_SECRET`; RustFS resolves it from its own
credential configuration. Label an externally managed Secret with
`rustfs.tenant=<Tenant name>` so Secret updates enqueue the Tenant for
prompt revalidation.
nullable: true
properties:
key:
minLength: 1
type: string
name:
minLength: 1
type: string
required:
- key
- name
type: object
scheduler:
nullable: true
type: string
Expand Down
38 changes: 38 additions & 0 deletions docs/operator-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,44 @@ Credential priority:
2. Explicit `RUSTFS_ACCESS_KEY` and `RUSTFS_SECRET_KEY` in `spec.env`.
3. RustFS built-in defaults. Use defaults only for development.

#### Dedicated internode RPC Secret

For production, configure `spec.rpcSecret` so internode RPC authentication does
not depend on the administrator credentials. Keep the Secret in the Tenant
namespace and label externally managed Secrets with the Tenant name:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: rustfs-rpc-auth
namespace: storage
labels:
rustfs.tenant: rustfs-a
type: Opaque
stringData:
rpc-secret: "replace-with-a-dedicated-rpc-secret"
---
apiVersion: rustfs.com/v1alpha1
kind: Tenant
metadata:
name: rustfs-a
namespace: storage
spec:
rpcSecret:
name: rustfs-rpc-auth
key: rpc-secret
# pools: ...
```

The selected value must be valid UTF-8, non-blank, contain no NUL bytes, and
must not be `rustfsadmin`. The `rustfs.tenant` label lets Secret updates enqueue
the Tenant for prompt revalidation; it is not an authorization mechanism. When
the configured Secret passes validation, the Operator reports
`RpcAuthReady=True`. Updating the Secret does not change the environment of
already-running Pods; coordinated restart and hot reload are outside this
feature.

### 7.4 Workload Settings

Useful Tenant-level fields:
Expand Down
36 changes: 36 additions & 0 deletions docs/operator-user-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,42 @@ spec:
2. `spec.env` 中显式配置 `RUSTFS_ACCESS_KEY` 和 `RUSTFS_SECRET_KEY`。
3. RustFS 内置默认值。默认值仅适合开发测试。

#### 独立的节点间 RPC Secret

生产环境建议配置 `spec.rpcSecret`,避免节点间 RPC 认证依赖管理员凭据。
Secret 必须与 Tenant 位于同一 namespace;若 Secret 由外部系统管理,请添加
Tenant 标签:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: rustfs-rpc-auth
namespace: storage
labels:
rustfs.tenant: rustfs-a
type: Opaque
stringData:
rpc-secret: "replace-with-a-dedicated-rpc-secret"
---
apiVersion: rustfs.com/v1alpha1
kind: Tenant
metadata:
name: rustfs-a
namespace: storage
spec:
rpcSecret:
name: rustfs-rpc-auth
key: rpc-secret
# pools: ...
```

所选值必须是有效 UTF-8、不能为空、不能包含 NUL 字节,并且不能是
`rustfsadmin`。`rustfs.tenant` 标签仅用于在 Secret 变化时及时触发 Tenant
重新校验,不是授权机制。配置的 Secret 校验通过后,Operator 会报告
`RpcAuthReady=True`。更新 Secret 不会改变已运行 Pod 的进程环境;协调重启和
热加载不在此功能范围内。

### 7.4 工作负载配置

常用 Tenant 级字段:
Expand Down
54 changes: 53 additions & 1 deletion e2e/src/framework/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ use crate::framework::{
tenant_factory::TenantTemplate,
};
use operator::types::v1alpha1::k8s::PodManagementPolicy;
use operator::types::v1alpha1::tenant::RpcSecretRef;

const TEST_ACCESS_KEY: &str = "testaccess";
const TEST_SECRET_KEY: &str = "testsecret";
const TEST_RPC_SECRET: &str = "test-dedicated-rpc-secret";
const RPC_SECRET_KEY: &str = "rpc-secret";
const RESOURCE_RESET_TIMEOUT: Duration = Duration::from_secs(120);
const RESOURCE_RESET_POLL_INTERVAL: Duration = Duration::from_secs(2);

pub fn credential_secret_name(config: &ClusterTestConfig) -> String {
format!("{}-credentials", config.tenant_name)
}

pub fn rpc_secret_name(config: &ClusterTestConfig) -> String {
format!("{}-rpc-auth", config.tenant_name)
}

pub fn test_credentials() -> (&'static str, &'static str) {
(TEST_ACCESS_KEY, TEST_SECRET_KEY)
}
Expand Down Expand Up @@ -66,6 +73,27 @@ stringData:
)
}

pub fn rpc_secret_manifest(config: &ClusterTestConfig) -> String {
format!(
r#"apiVersion: v1
kind: Secret
metadata:
name: {secret_name}
namespace: {namespace}
labels:
rustfs.tenant: {tenant_name}
type: Opaque
stringData:
{key}: {rpc_secret}
"#,
secret_name = rpc_secret_name(config),
namespace = config.test_namespace,
tenant_name = config.tenant_name,
key = RPC_SECRET_KEY,
rpc_secret = TEST_RPC_SECRET,
)
}

pub fn smoke_tenant_template(config: &ClusterTestConfig) -> TenantTemplate {
let mut template = TenantTemplate::kind_local(
&config.test_namespace,
Expand All @@ -81,6 +109,10 @@ pub fn smoke_tenant_template(config: &ClusterTestConfig) -> TenantTemplate {
.clone()
.unwrap_or(PodManagementPolicy::Parallel),
);
template.rpc_secret = Some(RpcSecretRef {
name: rpc_secret_name(config),
key: RPC_SECRET_KEY.to_string(),
});

template
}
Expand All @@ -99,6 +131,9 @@ pub fn apply_smoke_tenant_resources(config: &ClusterTestConfig) -> Result<()> {
kubectl
.apply_yaml_command(credential_secret_manifest(config))
.run_checked()?;
kubectl
.apply_yaml_command(rpc_secret_manifest(config))
.run_checked()?;
kubectl
.apply_yaml_command(smoke_tenant_manifest(config)?)
.run_checked()?;
Expand Down Expand Up @@ -304,7 +339,10 @@ fn is_not_found(output: &CommandOutput) -> bool {

#[cfg(test)]
mod tests {
use super::{credential_secret_manifest, credential_secret_name, smoke_tenant_manifest};
use super::{
credential_secret_manifest, credential_secret_name, rpc_secret_manifest, rpc_secret_name,
smoke_tenant_manifest,
};
use crate::framework::config::E2eConfig;

#[test]
Expand All @@ -317,6 +355,9 @@ mod tests {
assert!(manifest.contains("image: rustfs/rustfs:latest"));
assert!(manifest.contains("storageClassName: local-storage"));
assert!(manifest.contains("name: e2e-tenant-credentials"));
assert!(manifest.contains("rpcSecret:"));
assert!(manifest.contains("name: e2e-tenant-rpc-auth"));
assert!(manifest.contains("key: rpc-secret"));
}

#[test]
Expand All @@ -329,4 +370,15 @@ mod tests {
assert!(manifest.contains("accesskey:"));
assert!(manifest.contains("secretkey:"));
}

#[test]
fn rpc_secret_uses_tenant_watch_label() {
let config = E2eConfig::defaults();
let manifest = rpc_secret_manifest(&config);

assert_eq!(rpc_secret_name(&config), "e2e-tenant-rpc-auth");
assert!(manifest.contains("namespace: rustfs-e2e-smoke"));
assert!(manifest.contains("rustfs.tenant: e2e-tenant"));
assert!(manifest.contains("rpc-secret: test-dedicated-rpc-secret"));
}
}
6 changes: 5 additions & 1 deletion e2e/src/framework/tenant_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use operator::types::v1alpha1::k8s::ImagePullPolicy;
use operator::types::v1alpha1::k8s::PodManagementPolicy;
use operator::types::v1alpha1::persistence::PersistenceConfig;
use operator::types::v1alpha1::pool::{Pool, SchedulingConfig};
use operator::types::v1alpha1::tenant::{Tenant, TenantSpec};
use operator::types::v1alpha1::tenant::{RpcSecretRef, Tenant, TenantSpec};
use std::collections::BTreeMap;

#[derive(Debug, Clone)]
Expand All @@ -32,6 +32,7 @@ pub struct TenantTemplate {
pub image: String,
pub storage_class: String,
pub credential_secret_name: String,
pub rpc_secret: Option<RpcSecretRef>,
pub servers: i32,
pub volumes_per_server: i32,
pub storage_request: String,
Expand All @@ -55,6 +56,7 @@ impl TenantTemplate {
image: image.into(),
storage_class: storage_class.into(),
credential_secret_name: credential_secret_name.into(),
rpc_secret: None,
servers: 4,
volumes_per_server: 2,
storage_request: "10Gi".to_string(),
Expand Down Expand Up @@ -83,6 +85,7 @@ impl TenantTemplate {
image: image.into(),
storage_class: storage_class.into(),
credential_secret_name: credential_secret_name.into(),
rpc_secret: None,
servers: 4,
volumes_per_server: 1,
storage_request: "100Gi".to_string(),
Expand Down Expand Up @@ -146,6 +149,7 @@ impl TenantTemplate {
creds_secret: Some(LocalObjectReference {
name: self.credential_secret_name.clone(),
}),
rpc_secret: self.rpc_secret.clone(),
env,
..TenantSpec::default()
};
Expand Down
13 changes: 9 additions & 4 deletions e2e/tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::Result;
use anyhow::{Result, ensure};
use rustfs_operator_e2e::framework::{
artifacts::ArtifactCollector, config::E2eConfig, deploy, kube_client, live, resources, storage,
tools::required_tool_checks, wait,
artifacts::ArtifactCollector, assertions, config::E2eConfig, deploy, kube_client, live,
resources, storage, tools::required_tool_checks, wait,
};

#[test]
Expand Down Expand Up @@ -57,7 +57,12 @@ async fn smoke_apply_tenant_and_wait_ready() -> Result<()> {

let client = kube_client::default_client().await?;
let tenants = kube_client::tenant_api(client, &config.test_namespace);
wait::wait_for_tenant_ready(tenants, &config.tenant_name, config.timeout).await?;
let tenant =
wait::wait_for_tenant_ready(tenants, &config.tenant_name, config.timeout).await?;
ensure!(
assertions::condition_status(&tenant, "RpcAuthReady") == Some("True"),
"Tenant became Ready without RpcAuthReady=True"
);
Ok(())
}
.await;
Expand Down
Loading
Loading