From aa1c73c2530a20efdc28285f66a861c4417e1106 Mon Sep 17 00:00:00 2001 From: Martin Schneppenheim <23424570+weeco@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:06:30 +0000 Subject: [PATCH 1/2] kadm: add func to decode AuthorizedOperations The authorized operations is an int32 bitfield that represents ACL operations. This commit adds a func that allows to decode the bitfield into a slice of kmsg.ACLOperation values. This bitfield is used in the metadata, describe clusters and describe groups API responses. --- pkg/kadm/acls.go | 53 ++++++++++++++++++++ pkg/kadm/acls_test.go | 109 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 pkg/kadm/acls_test.go diff --git a/pkg/kadm/acls.go b/pkg/kadm/acls.go index 62676b5b..79008684 100644 --- a/pkg/kadm/acls.go +++ b/pkg/kadm/acls.go @@ -3,6 +3,7 @@ package kadm import ( "context" "fmt" + "math" "strings" "sync" @@ -1115,3 +1116,55 @@ func createDelDescACL(b *ACLBuilder) ([]kmsg.DeleteACLsRequestFilter, []*kmsg.De } return deletions, describes, nil } + +// DecodeACLOperations decodes an int32 bitfield into a slice of +// kmsg.ACLOperation values. +// +// This function is used to interpret the `AuthorizedOperations` field returned +// by the Kafka APIs, which specifies the operations a client is allowed to +// perform on a cluster, topic, or consumer group. It is utilized in multiple +// Kafka API responses, including Metadata, DescribeCluster, and +// DescribeGroupsResponseGroup. +// +// Caveats with Metadata API +// 1. To include authorized operations in the Metadata response, the client must explicitly +// opt in by setting `IncludeClusterAuthorizedOperations` and/or `IncludeTopicAuthorizedOperations`. +// These options were introduced in Kafka 2.3.0 as part of KIP-430. +// 2. In Kafka 2.8.0 (Metadata v11), the `AuthorizedOperations` for the cluster was removed from the +// Metadata response. Instead, clients should use the DescribeCluster API to retrieve cluster-level +// permissions. +// +// Function Behavior +// - If the bitfield equals `math.MinInt32` (-2147483648), it indicates that "AUTHORIZED_OPERATIONS_OMITTED" +// is set, and the function returns an empty slice. +// - For non-omitted values, the function iterates through all 32 bits of the bitfield. Each bit that +// is set (`1`) corresponds to an ACL operation, which is then mapped to its respective `kmsg.ACLOperation` value. +// - Undefined or unknown bits (e.g., bit 0 for `kmsg.ACLOperationUnknown`) are ignored. +// +// Supported Use Cases +// - Cluster Operations: Retrieved via the DescribeCluster API or older Metadata API versions (v8–v10). +// - Topic Operations: Retrieved via the Metadata API when `IncludeTopicAuthorizedOperations` is set. +// - Group Operations: Retrieved in the DescribeGroups API response. +func DecodeACLOperations(bitfield int32) []kmsg.ACLOperation { + var operations []kmsg.ACLOperation + + // MinInt32 represents "AUTHORIZED_OPERATIONS_OMITTED" + if bitfield == math.MinInt32 { + return operations + } + + // Helper function to determine if an operation is valid. + isValidOperation := func(op kmsg.ACLOperation) bool { + return op >= kmsg.ACLOperationRead && op <= kmsg.ACLOperationDescribeTokens + } + + for i := 0; i < 32; i++ { + if bitfield&(1< Date: Fri, 10 Jan 2025 12:57:06 +0100 Subject: [PATCH 2/2] kadm: switch to kadm.ACLOperation type --- pkg/kadm/acls.go | 4 ++-- pkg/kadm/acls_test.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/kadm/acls.go b/pkg/kadm/acls.go index 79008684..9d4fdbf0 100644 --- a/pkg/kadm/acls.go +++ b/pkg/kadm/acls.go @@ -1145,8 +1145,8 @@ func createDelDescACL(b *ACLBuilder) ([]kmsg.DeleteACLsRequestFilter, []*kmsg.De // - Cluster Operations: Retrieved via the DescribeCluster API or older Metadata API versions (v8–v10). // - Topic Operations: Retrieved via the Metadata API when `IncludeTopicAuthorizedOperations` is set. // - Group Operations: Retrieved in the DescribeGroups API response. -func DecodeACLOperations(bitfield int32) []kmsg.ACLOperation { - var operations []kmsg.ACLOperation +func DecodeACLOperations(bitfield int32) []ACLOperation { + var operations []ACLOperation // MinInt32 represents "AUTHORIZED_OPERATIONS_OMITTED" if bitfield == math.MinInt32 { diff --git a/pkg/kadm/acls_test.go b/pkg/kadm/acls_test.go index 4dd8082f..3e4ffc41 100644 --- a/pkg/kadm/acls_test.go +++ b/pkg/kadm/acls_test.go @@ -12,12 +12,12 @@ func TestDecodeACLOperations(t *testing.T) { tests := []struct { name string bitfield int32 - expected []kmsg.ACLOperation + expected []ACLOperation }{ { name: "Example 264", bitfield: 264, - expected: []kmsg.ACLOperation{ + expected: []ACLOperation{ kmsg.ACLOperationRead, kmsg.ACLOperationDescribe, }, @@ -25,7 +25,7 @@ func TestDecodeACLOperations(t *testing.T) { { name: "Example 3400", bitfield: 3400, - expected: []kmsg.ACLOperation{ + expected: []ACLOperation{ kmsg.ACLOperationRead, kmsg.ACLOperationDescribe, kmsg.ACLOperationAlterConfigs, @@ -36,7 +36,7 @@ func TestDecodeACLOperations(t *testing.T) { { name: "Example 3968", bitfield: 3968, - expected: []kmsg.ACLOperation{ + expected: []ACLOperation{ kmsg.ACLOperationAlter, kmsg.ACLOperationAlterConfigs, kmsg.ACLOperationClusterAction, @@ -47,7 +47,7 @@ func TestDecodeACLOperations(t *testing.T) { { name: "Example 4000", bitfield: 4000, - expected: []kmsg.ACLOperation{ + expected: []ACLOperation{ kmsg.ACLOperationAlter, kmsg.ACLOperationAlterConfigs, kmsg.ACLOperationClusterAction, @@ -59,7 +59,7 @@ func TestDecodeACLOperations(t *testing.T) { { name: "All Operations", bitfield: math.MaxInt32, // All bits set - expected: []kmsg.ACLOperation{ + expected: []ACLOperation{ kmsg.ACLOperationRead, kmsg.ACLOperationWrite, kmsg.ACLOperationCreate, @@ -77,12 +77,12 @@ func TestDecodeACLOperations(t *testing.T) { { name: "Invalid Operations Excluded", bitfield: 1<<15 | 1<<16, // Bits beyond known operations - expected: []kmsg.ACLOperation{}, + expected: []ACLOperation{}, }, { name: "Empty Bitfield", bitfield: math.MinInt32, - expected: []kmsg.ACLOperation{}, + expected: []ACLOperation{}, }, }