From a6b5b8f47d912f6b21600e8184216082003802c1 Mon Sep 17 00:00:00 2001 From: xiaolu Date: Mon, 20 Apr 2026 07:53:36 +0000 Subject: [PATCH 1/3] feat: add Kafka user password configuration solution Document the end-to-end flow for configuring a custom SCRAM-SHA-512 password on an RdsKafkaUser and rotating it via the changePasswordTimestamp annotation. Includes an OCP / AMQ Streams parity section for migration. Verified on ACP 4.2.x with rds-operator v4.2.0 and Kafka 4.1.1. --- .../Kafka_User_Password_Configuration.md | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 docs/en/solutions/Kafka_User_Password_Configuration.md diff --git a/docs/en/solutions/Kafka_User_Password_Configuration.md b/docs/en/solutions/Kafka_User_Password_Configuration.md new file mode 100644 index 0000000..f822b28 --- /dev/null +++ b/docs/en/solutions/Kafka_User_Password_Configuration.md @@ -0,0 +1,182 @@ +--- +products: + - Alauda Container Platform +kind: + - Solution +id: KB1776670415-KAFU +--- + +# Kafka User Password Configuration + +## Background + +By default the Alauda middleware operator (`rds-operator`) delegates SCRAM-SHA-512 credential generation to the Strimzi User Operator, which produces a random password when a `KafkaUser` is created. Some use cases — migrations from an existing Kafka cluster, integrations with external systems that already have a fixed password, or centrally managed credential rotation — require setting a known password on the user and rotating it on demand. + +Starting with ACP 3.15, `RdsKafkaUser` exposes `spec.authentication.password.valueFrom.secretKeyRef` so a user's SCRAM-SHA-512 password can be sourced from a user-managed `Secret`. Updating the `Secret` together with a `changePasswordTimestamp` annotation on the `RdsKafkaUser` triggers an in-place rotation. + +## Applicable Version + +Verified on ACP 4.2.x (rds-operator `v4.2.0`, Kafka `4.1.1`). The `password.valueFrom.secretKeyRef` field was introduced in rds-operator 3.15, so earlier ACP releases that bundle a rds-operator >= 3.15 support the same flow. + +## Prerequisites + +The target `RdsKafka` cluster must have a listener with SCRAM-SHA-512 authentication and authorization enabled so user credentials and ACLs are enforced: + +```yaml +apiVersion: middleware.alauda.io/v1 +kind: RdsKafka +metadata: + name: my-cluster +spec: + kafka: + listeners: + plain: + authentication: + type: scram-sha-512 + authorization: + type: simple + # ... other fields omitted +``` + +Note: `kafka.authorization.type: simple` is required for per-user ACLs to take effect. Without it, a `KafkaUser`'s ACL rules are accepted but not enforced by the brokers. + +## Steps + +### 1. Create the password Secret + +The `Secret` must live in the same namespace as the `RdsKafka` instance. The `password` field is base64-encoded: + +```shell +# base64 of 'Passw0rd123!' +kubectl -n create secret generic my-user-password \ + --from-literal=password='Passw0rd123!' +``` + +Equivalent declarative form: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-user-password + namespace: +type: Opaque +data: + password: UGFzc3cwcmQxMjMh +``` + +### 2. Create the RdsKafkaUser + +Create the user with `authentication.type: scram-sha-512` and reference the password `Secret`. The cluster binding uses the label `middleware.alauda.io/cluster: `: + +```yaml +apiVersion: middleware.alauda.io/v1 +kind: RdsKafkaUser +metadata: + name: my-user + namespace: + labels: + middleware.alauda.io/cluster: my-cluster +spec: + authentication: + type: scram-sha-512 + password: + valueFrom: + secretKeyRef: + name: my-user-password + key: password + authorization: + type: simple + acls: + - host: '*' + operation: All + resource: + name: '*' + patternType: literal + type: topic +``` + +> Use the kind `RdsKafkaUser` (group `middleware.alauda.io/v1`), not the downstream Strimzi `KafkaUser`. The operator owns the Strimzi object and overwrites any user edits to it. The cluster label key is `middleware.alauda.io/cluster`; the operator translates this to `strimzi.io/cluster` on the downstream `KafkaUser`. + +### 3. Verify reconcile + +```shell +kubectl -n get rdskafkauser my-user -o jsonpath='{.status.phase}' +# Active + +kubectl -n get kafkauser my-user -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' +# True +``` + +The Strimzi User Operator publishes a same-named `Secret` that contains the SASL/JAAS config with the configured password: + +```shell +kubectl -n get secret my-user -o jsonpath='{.data.sasl\.jaas\.config}' | base64 -d +# org.apache.kafka.common.security.scram.ScramLoginModule required username="my-user" password="Passw0rd123!"; +``` + +Client applications can mount this `Secret` directly to obtain a ready-to-use JAAS configuration. + +### 4. Rotate the password + +Update the password `Secret` and set the `changePasswordTimestamp` annotation on the `RdsKafkaUser`. The operator reconciles the downstream `KafkaUser` within seconds, and the Strimzi User Operator then republishes the SASL credentials: + +```shell +NEW_B64=$(printf '%s' 'NewRotated456!' | base64) +kubectl -n patch secret my-user-password \ + -p "{\"data\":{\"password\":\"$NEW_B64\"}}" + +kubectl -n annotate rdskafkauser my-user \ + "changePasswordTimestamp=$(date +%s)" --overwrite +``` + +After rotation, clients using the old password fail with `SaslAuthenticationException: Authentication failed during authentication due to invalid credentials with SASL mechanism SCRAM-SHA-512`. Clients using the new password authenticate successfully. + +## Notes + +- The password `Secret` is automatically tagged with an `OwnerReference` to the `RdsKafkaUser`. Deleting the `RdsKafkaUser` garbage-collects the password `Secret` along with it. +- `spec.authentication.type` accepts only `tls` or `scram-sha-512`. TLS authentication (mutual TLS) uses a different credential flow and does not consume the `password` field. +- When the Strimzi User Operator processes the downstream `KafkaUser`, it may emit `DeprecatedFields` or `UnknownFields` status conditions regarding the ACL schema (`operation` vs `operations`, and `resource.name`/`resource.patternType`). These are warnings and do not prevent the user from becoming `Ready`. + +## OCP Parity + +Red Hat OpenShift Container Platform uses AMQ Streams (the productized Strimzi distribution) for Kafka. The same capability is exposed natively on the upstream `KafkaUser` resource: + +```yaml +apiVersion: kafka.strimzi.io/v1beta2 +kind: KafkaUser +metadata: + name: my-user + labels: + strimzi.io/cluster: my-cluster +spec: + authentication: + type: scram-sha-512 + password: + valueFrom: + secretKeyRef: + name: my-user-password + key: password + authorization: + type: simple + acls: + - resource: + type: topic + name: '*' + patternType: literal + operations: [All] + host: '*' +``` + +Key differences when migrating between platforms: + +| Aspect | ACP (`RdsKafkaUser`) | OCP / AMQ Streams (`KafkaUser`) | +| --- | --- | --- | +| API group / kind | `middleware.alauda.io/v1` `RdsKafkaUser` | `kafka.strimzi.io/v1beta2` `KafkaUser` | +| Cluster binding label | `middleware.alauda.io/cluster` | `strimzi.io/cluster` | +| Password `secretKeyRef` field | Identical (`spec.authentication.password.valueFrom.secretKeyRef`) | Identical | +| Rotation trigger | `changePasswordTimestamp` annotation on `RdsKafkaUser` | Update the password `Secret`; the User Operator re-syncs automatically. A no-op annotation change on the `KafkaUser` can force an immediate reconcile. | +| ACL schema | Single `operation` + `resource.{name,patternType,type}` (translated to Strimzi) | `operations` array + `resource.{type,name,patternType}` | +| Ownership of password `Secret` | Operator sets `OwnerReference` to `RdsKafkaUser` | No default owner reference; application-managed lifecycle | + +The credential flow on the broker side is unchanged: the User Operator writes the SCRAM credentials into Kafka and publishes a `Secret` containing `sasl.jaas.config` for clients to consume. Moving an existing `KafkaUser` manifest from OCP to ACP is a mechanical rewrite of the `kind`, `apiVersion`, cluster label, and ACL shape — no behavioral differences in authentication or rotation. From 185fce2940a5b6af43d04675e84c7037cfb94281 Mon Sep 17 00:00:00 2001 From: xiaolu Date: Mon, 20 Apr 2026 09:34:44 +0000 Subject: [PATCH 2/3] docs: correct feature-introduction version from 3.15 to v3.16.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Confluence source said "3.15增加配置密码功能" but rds-operator rdskafkauser_types.go at tag v3.15.10 (last v3.15.x patch, 2023-12-25) has no Password field. The commit adding the field (e289ccab "MIDDLEWARE-18371 add kafka admin and kafkauser set password") landed on 2024-01-31, and the earliest tag containing the field is v3.16.0. Fix conflicting claims ("ACP 3.15" in Background vs "rds-operator 3.15" in Applicable Version) by removing the unverified ACP version claim and pinning to the verifiable rds-operator version. --- docs/en/solutions/Kafka_User_Password_Configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/solutions/Kafka_User_Password_Configuration.md b/docs/en/solutions/Kafka_User_Password_Configuration.md index f822b28..58ba5f8 100644 --- a/docs/en/solutions/Kafka_User_Password_Configuration.md +++ b/docs/en/solutions/Kafka_User_Password_Configuration.md @@ -12,11 +12,11 @@ id: KB1776670415-KAFU By default the Alauda middleware operator (`rds-operator`) delegates SCRAM-SHA-512 credential generation to the Strimzi User Operator, which produces a random password when a `KafkaUser` is created. Some use cases — migrations from an existing Kafka cluster, integrations with external systems that already have a fixed password, or centrally managed credential rotation — require setting a known password on the user and rotating it on demand. -Starting with ACP 3.15, `RdsKafkaUser` exposes `spec.authentication.password.valueFrom.secretKeyRef` so a user's SCRAM-SHA-512 password can be sourced from a user-managed `Secret`. Updating the `Secret` together with a `changePasswordTimestamp` annotation on the `RdsKafkaUser` triggers an in-place rotation. +`RdsKafkaUser` exposes `spec.authentication.password.valueFrom.secretKeyRef` so a user's SCRAM-SHA-512 password can be sourced from a user-managed `Secret`. Updating the `Secret` together with a `changePasswordTimestamp` annotation on the `RdsKafkaUser` triggers an in-place rotation. ## Applicable Version -Verified on ACP 4.2.x (rds-operator `v4.2.0`, Kafka `4.1.1`). The `password.valueFrom.secretKeyRef` field was introduced in rds-operator 3.15, so earlier ACP releases that bundle a rds-operator >= 3.15 support the same flow. +Verified on ACP 4.2.x (rds-operator `v4.2.0`, Kafka `4.1.1`). The `password.valueFrom.secretKeyRef` field was introduced in rds-operator `v3.16.0`; any ACP release bundling rds-operator `v3.16.0` or later supports this flow. The `v3.15.x` series does not contain the field. ## Prerequisites From b645b045f0df41b756ec886318d4e2882c1bc391 Mon Sep 17 00:00:00 2001 From: xiaolu Date: Mon, 20 Apr 2026 09:45:12 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20address=20senior=20review=20?= =?UTF-8?q?=E2=80=94=20tighten=20OCP=20parity=20and=20add=20cleanup=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes from review of PR #142: - Correct the ACL-shape row in the OCP Parity table. Strimzi v1beta2 accepts both `acls[*].operation` (singular, deprecated) and `acls[*].operations` (plural array) — not either/or as previously implied. Same `resource` sub-fields in both. - Correct the rotation trigger row. Upstream Strimzi User Operator watches the referenced source Secret natively; the no-op annotation dance is not a workaround users need to know about. - Add a Supported `authentication.type` row surfacing that upstream Strimzi supports `tls-external` which RdsKafkaUser does not — a real migration gap for OCP users of that type. - Drop the `resource.{name,patternType,type}` vs `resource.{type,name,patternType}` "difference" — the fields are identical, only the listing order differed. - Broaden Notes on `tls-external` migration implication. - Add a Prerequisites clarification that SCRAM auth can live on any listener (plain/tls/external), not only the one shown. - Add a brief note after Step 3 explaining why RdsKafkaUser uses `status.phase` while the downstream KafkaUser uses `status.conditions`. - Add a new Step 5 "Remove the user" covering delete + GC behavior. Kafka 4.1.1 version claim in Applicable Version is now runtime-verified via `kafka-topics.sh --version` against the kafka-4-1-x:v4.2.0 image. --- .../Kafka_User_Password_Configuration.md | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/en/solutions/Kafka_User_Password_Configuration.md b/docs/en/solutions/Kafka_User_Password_Configuration.md index 58ba5f8..f7b6c24 100644 --- a/docs/en/solutions/Kafka_User_Password_Configuration.md +++ b/docs/en/solutions/Kafka_User_Password_Configuration.md @@ -38,6 +38,8 @@ spec: # ... other fields omitted ``` +The example uses the `plain` listener. SCRAM-SHA-512 authentication works identically on the `tls` and `external` listeners — set `authentication.type: scram-sha-512` on whichever listener the client will connect to. At least one listener must have the SCRAM auth type configured for the credentials provisioned by this flow to be usable. + Note: `kafka.authorization.type: simple` is required for per-user ACLs to take effect. Without it, a `KafkaUser`'s ACL rules are accepted but not enforced by the brokers. ## Steps @@ -108,6 +110,8 @@ kubectl -n get kafkauser my-user -o jsonpath='{.status.conditions[?(@.type= # True ``` +`RdsKafkaUser` reports readiness via `status.phase`; the downstream Strimzi `KafkaUser` uses the standard condition-based `status.conditions[type=Ready]`. Both must be green before clients can authenticate. + The Strimzi User Operator publishes a same-named `Secret` that contains the SASL/JAAS config with the configured password: ```shell @@ -132,11 +136,19 @@ kubectl -n annotate rdskafkauser my-user \ After rotation, clients using the old password fail with `SaslAuthenticationException: Authentication failed during authentication due to invalid credentials with SASL mechanism SCRAM-SHA-512`. Clients using the new password authenticate successfully. +### 5. Remove the user + +```shell +kubectl -n delete rdskafkauser my-user +``` + +Deleting the `RdsKafkaUser` removes the downstream Strimzi `KafkaUser`, revokes the SCRAM credentials in the broker, and (because the operator has tagged it with an `OwnerReference`) garbage-collects the password `Secret`. The auto-published SASL `Secret` created by the Strimzi User Operator is deleted as part of the `KafkaUser` teardown. + ## Notes - The password `Secret` is automatically tagged with an `OwnerReference` to the `RdsKafkaUser`. Deleting the `RdsKafkaUser` garbage-collects the password `Secret` along with it. -- `spec.authentication.type` accepts only `tls` or `scram-sha-512`. TLS authentication (mutual TLS) uses a different credential flow and does not consume the `password` field. -- When the Strimzi User Operator processes the downstream `KafkaUser`, it may emit `DeprecatedFields` or `UnknownFields` status conditions regarding the ACL schema (`operation` vs `operations`, and `resource.name`/`resource.patternType`). These are warnings and do not prevent the user from becoming `Ready`. +- `spec.authentication.type` on `RdsKafkaUser` accepts only `tls` or `scram-sha-512`. TLS authentication (mutual TLS) uses a different credential flow and does not consume the `password` field. Upstream Strimzi `KafkaUser` additionally supports `tls-external`; an OCP user of that type cannot be migrated 1:1 and must be rewritten as `tls` with operator-managed certificates. +- When the Strimzi User Operator processes the downstream `KafkaUser`, it may emit `DeprecatedFields` or `UnknownFields` status conditions regarding the ACL schema (the operator propagates the singular `operation` field; upstream Strimzi now prefers the plural `operations` array). These are warnings and do not prevent the user from becoming `Ready`. ## OCP Parity @@ -175,8 +187,9 @@ Key differences when migrating between platforms: | API group / kind | `middleware.alauda.io/v1` `RdsKafkaUser` | `kafka.strimzi.io/v1beta2` `KafkaUser` | | Cluster binding label | `middleware.alauda.io/cluster` | `strimzi.io/cluster` | | Password `secretKeyRef` field | Identical (`spec.authentication.password.valueFrom.secretKeyRef`) | Identical | -| Rotation trigger | `changePasswordTimestamp` annotation on `RdsKafkaUser` | Update the password `Secret`; the User Operator re-syncs automatically. A no-op annotation change on the `KafkaUser` can force an immediate reconcile. | -| ACL schema | Single `operation` + `resource.{name,patternType,type}` (translated to Strimzi) | `operations` array + `resource.{type,name,patternType}` | -| Ownership of password `Secret` | Operator sets `OwnerReference` to `RdsKafkaUser` | No default owner reference; application-managed lifecycle | +| Supported `authentication.type` | `tls`, `scram-sha-512` | `tls`, `tls-external`, `scram-sha-512` | +| Rotation trigger | Update the password `Secret` and bump the `changePasswordTimestamp` annotation on the `RdsKafkaUser` to force a reconcile of the downstream `KafkaUser`. | Update the password `Secret` — the User Operator watches the referenced source `Secret` and republishes credentials automatically. | +| ACL shape | Same `resource` sub-fields (`type`, `name`, `patternType`). `acls[*].operation` (singular) is propagated unchanged to Strimzi and triggers a `DeprecatedFields` warning. | Prefers `acls[*].operations` (plural array); `acls[*].operation` (singular) still accepted but deprecated. | +| Ownership of password `Secret` | Operator sets `OwnerReference` to `RdsKafkaUser` — deleting the user garbage-collects the `Secret`. | User Operator reads but does not own the source `Secret`; lifecycle is application-managed. | The credential flow on the broker side is unchanged: the User Operator writes the SCRAM credentials into Kafka and publishes a `Secret` containing `sasl.jaas.config` for clients to consume. Moving an existing `KafkaUser` manifest from OCP to ACP is a mechanical rewrite of the `kind`, `apiVersion`, cluster label, and ACL shape — no behavioral differences in authentication or rotation.