Skip to content

Commit db1ea33

Browse files
authored
feat: add Internal support for cloud-managed roles (#1230)
* refactor(roles): extract helper functions to reduce duplication Extract two helper functions in the roles client to eliminate code duplication and improve maintainability: - validateRole: Centralizes role validation logic (nil check and empty name check) that was duplicated across 5 methods (Has, Create, Delete, Update, ClearPrincipals). Includes documentation explaining the defensive programming rationale despite Kubernetes name guarantees. - membersToStringSlice: Converts RoleMember slice to principal string slice using "PrincipalType:Name" format. Replaces duplicate conversion logic in Update and ClearPrincipals methods, reducing 10+ lines to simple function calls. All existing tests pass, confirming behavior is unchanged. * refactor(controller): extract isRoleRename helper function Extract isRoleRename helper function to eliminate duplicated complex conditional logic in the role controller. The function encapsulates the three-part condition that determines when a role rename operation is needed: - Previous effective name exists (not empty) - Effective name has changed - Role is currently managed This helper is used in two locations: - SyncResource: Triggers rename workflow when effective name changes - DeleteResource: Cleans up previous role from incomplete renames Makes the code more readable and self-documenting by replacing complex boolean expressions with a clear function name. All tests pass. * feat(api): add internal role support with prefix handling Add support for Redpanda internal roles that use the "__" prefix convention. This allows Kubernetes resources to manage roles that are internal to Redpanda. Changes: - Add Internal boolean field to RoleSpec to mark roles as internal - Add GetEffectiveRoleName() method that returns "__<name>" when Internal is true, otherwise returns "<name>" - Add EffectiveRoleName field to RoleStatus to track the last reconciled role name for detecting renames - Update GetPrincipal() to use the effective role name - Add InternalRolePrefix constant ("__") The effective role name is used throughout the reconciliation logic to ensure the correct role is created/updated/deleted in Redpanda, while the status field enables proper cleanup during rename operations. Generated files updated: - CRD schema (cluster.redpanda.com_redpandaroles.yaml) - Apply configurations (rolespec.go, rolestatus.go) - API documentation (crd-docs.adoc) * test(roles): add tests for internal role functionality Add comprehensive test coverage for internal role feature: API tests (role_types_test.go): - GetEffectiveRoleName returns name with "__" prefix when Internal=true - GetEffectiveRoleName returns plain name when Internal=false - GetPrincipal uses effective role name in principal string Controller tests (role_controller_test.go): - TestRoleRename: Validates rename workflow when Internal flag toggles - Creates role without internal flag - Renames to internal role (adds "__" prefix) - Renames back to non-internal role (removes prefix) - Verifies old roles are cleaned up after renames - Confirms proper role existence at each step All tests verify that: - Effective role names are correctly computed - Role creation uses the effective name - Renames properly create new and delete old roles - Status tracking enables cleanup of previous roles * test(acceptance): add internal role scenarios Add Gherkin acceptance tests for internal role feature with complete end-to-end validation. New scenarios in role-crds.feature: - "Role with internal flag": Validates that roles with Internal=true are created with "__" prefix in Redpanda and can be queried correctly - "Role rename via internal flag toggle": Validates the rename workflow when toggling the Internal flag, ensuring old roles are cleaned up Test implementation (roles.go): - Add roleHasInternalFlagSet step to configure Internal field - Add roleExistsInRedpandaWithName step to verify effective names - Add roleDoesNotExistInRedpandaWithName step for cleanup validation - Enhance role existence checking to support both CRD name and effective role name validation - Add internal flag support to role creation helpers Infrastructure (register.go): - Register new step definitions for internal role scenarios These acceptance tests validate the complete workflow including: - Kubernetes CRD creation with Internal flag - Redpanda role creation with correct prefix - Role rename handling and cleanup - Principal and ACL management with effective names * chore: add changelog entry for internal role feature Document the addition of the Internal field to RedpandaRole spec for managing Redpanda internal roles with "__" prefix.
1 parent cce6185 commit db1ea33

File tree

13 files changed

+761
-139
lines changed

13 files changed

+761
-139
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
project: operator
2+
kind: Added
3+
body: Added `Internal` boolean field to RedpandaRole spec to enable managing Redpanda internal roles (prefixed with "__") using standard Kubernetes resource names.
4+
time: 2026-01-21T18:05:40.867383428+01:00

acceptance/features/role-crds.feature

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,134 @@ Feature: Role CRDs
284284
And role "swap-role" should not have member "olduser1" in cluster "sasl"
285285
And role "swap-role" should not have member "olduser2" in cluster "sasl"
286286
And RedpandaRole "swap-role" should have status field "managedPrincipals" set to "true"
287+
288+
@skip:gke @skip:aks @skip:eks
289+
Scenario: Manage roles with internal flag
290+
Given there is no role "__admin-role-k8s" in cluster "sasl"
291+
And there are the following pre-existing users in cluster "sasl"
292+
| name | password | mechanism |
293+
| alice | password | SCRAM-SHA-256 |
294+
| bob | password | SCRAM-SHA-256 |
295+
When I apply Kubernetes manifest:
296+
"""
297+
apiVersion: cluster.redpanda.com/v1alpha2
298+
kind: RedpandaRole
299+
metadata:
300+
name: admin-role-k8s
301+
spec:
302+
cluster:
303+
clusterRef:
304+
name: sasl
305+
internal: true
306+
principals:
307+
- User:alice
308+
- User:bob
309+
"""
310+
And role "admin-role-k8s" is successfully synced
311+
Then role "__admin-role-k8s" should exist in cluster "sasl" with effective name "__admin-role-k8s"
312+
And role "__admin-role-k8s" should have members "alice and bob" in cluster "sasl" with effective name "__admin-role-k8s"
313+
And there should be no role "admin-role-k8s" in cluster "sasl" with effective name "admin-role-k8s"
314+
315+
@skip:gke @skip:aks @skip:eks
316+
Scenario: Manage internal roles with authorization
317+
Given there is no role "__reader-role-k8s" in cluster "sasl"
318+
And there are the following pre-existing users in cluster "sasl"
319+
| name | password | mechanism |
320+
| charlie | password | SCRAM-SHA-256 |
321+
When I create topic "internal-test" in cluster "sasl"
322+
And I apply Kubernetes manifest:
323+
"""
324+
apiVersion: cluster.redpanda.com/v1alpha2
325+
kind: RedpandaRole
326+
metadata:
327+
name: reader-role-k8s
328+
spec:
329+
cluster:
330+
clusterRef:
331+
name: sasl
332+
internal: true
333+
principals:
334+
- User:charlie
335+
authorization:
336+
acls:
337+
- type: allow
338+
resource:
339+
type: topic
340+
name: internal-
341+
patternType: prefixed
342+
operations: [Read, Describe]
343+
"""
344+
And role "reader-role-k8s" is successfully synced
345+
Then role "__reader-role-k8s" should exist in cluster "sasl" with effective name "__reader-role-k8s"
346+
And role "__reader-role-k8s" should have members "charlie" in cluster "sasl" with effective name "__reader-role-k8s"
347+
And there should be no role "reader-role-k8s" in cluster "sasl" with effective name "reader-role-k8s"
348+
And role "reader-role-k8s" should have ACLs for topic pattern "internal-" in cluster "sasl"
349+
And "charlie" should be able to read from topic "internal-test" in cluster "sasl"
350+
351+
@skip:gke @skip:aks @skip:eks
352+
Scenario: Toggle internal flag to rename role with ACLs
353+
Given there is no role "rename-test" in cluster "sasl"
354+
And there is no role "__rename-test" in cluster "sasl"
355+
And there are the following pre-existing users in cluster "sasl"
356+
| name | password | mechanism |
357+
| renameuser | password | SCRAM-SHA-256 |
358+
When I create topic "rename-test-topic-before" in cluster "sasl"
359+
And I create topic "rename-test-topic-after" in cluster "sasl"
360+
And I apply Kubernetes manifest:
361+
"""
362+
apiVersion: cluster.redpanda.com/v1alpha2
363+
kind: RedpandaRole
364+
metadata:
365+
name: rename-test
366+
spec:
367+
cluster:
368+
clusterRef:
369+
name: sasl
370+
principals:
371+
- User:renameuser
372+
authorization:
373+
acls:
374+
- type: allow
375+
resource:
376+
type: topic
377+
name: rename-test-
378+
patternType: prefixed
379+
operations: [Read, Describe]
380+
"""
381+
And role "rename-test" is successfully synced
382+
Then role "rename-test" should exist in cluster "sasl" with effective name "rename-test"
383+
And role "rename-test" should have members "renameuser" in cluster "sasl" with effective name "rename-test"
384+
And role "rename-test" should have ACLs for topic pattern "rename-test-" in cluster "sasl"
385+
And "renameuser" should be able to read from topic "rename-test-topic-before" in cluster "sasl"
386+
And RedpandaRole "rename-test" should have status field "effectiveRoleName" set to "rename-test"
387+
When I apply Kubernetes manifest:
388+
"""
389+
apiVersion: cluster.redpanda.com/v1alpha2
390+
kind: RedpandaRole
391+
metadata:
392+
name: rename-test
393+
spec:
394+
cluster:
395+
clusterRef:
396+
name: sasl
397+
internal: true
398+
principals:
399+
- User:renameuser
400+
authorization:
401+
acls:
402+
- type: allow
403+
resource:
404+
type: topic
405+
name: rename-test-
406+
patternType: prefixed
407+
operations: [Read, Describe]
408+
"""
409+
And role "rename-test" is successfully synced
410+
Then role "__rename-test" should exist in cluster "sasl" with effective name "__rename-test"
411+
And role "__rename-test" should have members "renameuser" in cluster "sasl" with effective name "__rename-test"
412+
And there should be no role "rename-test" in cluster "sasl" with effective name "rename-test"
413+
And role "rename-test" should have ACLs for topic pattern "rename-test-" in cluster "sasl"
414+
And "renameuser" should be able to read from topic "rename-test-topic-after" in cluster "sasl"
415+
And RedpandaRole "rename-test" should have status field "effectiveRoleName" set to "__rename-test"
416+
417+

acceptance/steps/register.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ func init() {
6868
framework.RegisterStep(`^RedpandaRole "([^"]*)" should have no members in( vectorized)? cluster "([^"]*)"$`, roleShouldHaveNoMembersInCluster)
6969
framework.RegisterStep(`^RedpandaRole "([^"]*)" should have status field "([^"]*)" set to "([^"]*)"$`, redpandaRoleShouldHaveStatusFieldSetTo)
7070

71+
// Direct testing with effective role names (using the role name as it appears in Redpanda)
72+
framework.RegisterStep(`^role "([^"]*)" should exist in( vectorized)? cluster "([^"]*)" with effective name "([^"]*)"$`, roleShouldExistInClusterWithEffectiveName)
73+
framework.RegisterStep(`^there should be no role "([^"]*)" in( vectorized)? cluster "([^"]*)" with effective name "([^"]*)"$`, thereShouldBeNoRoleInClusterWithEffectiveName)
74+
framework.RegisterStep(`^role "([^"]*)" should have members "([^"]*)" in( vectorized)? cluster "([^"]*)" with effective name "([^"]*)"$`, roleShouldHaveMembersWithEffectiveName)
75+
7176
// Metrics scenario steps
7277
framework.RegisterStep(`^the operator is running$`, operatorIsRunning)
7378
framework.RegisterStep(`^its metrics endpoint should reject http request with status code "([^"]*)"$`, requestMetricsEndpointPlainHTTP)

0 commit comments

Comments
 (0)