diff --git a/backend/go.mod b/backend/go.mod index 776ac2797..1082fabb8 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,6 +13,7 @@ require ( github.com/carlmjohnson/requests v0.23.5 github.com/cloudhut/common v0.10.0 github.com/cloudhut/connect-client v0.0.0-20240122153328-02a3103805d8 + github.com/coreos/go-semver v0.3.1 github.com/docker/go-connections v0.5.0 github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 github.com/getkin/kin-openapi v0.124.0 diff --git a/backend/go.sum b/backend/go.sum index ef8456654..6a5a03ea9 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -118,6 +118,8 @@ github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K3 github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= diff --git a/backend/pkg/api/hooks.go b/backend/pkg/api/hooks.go index dce3739ce..5e5b2f495 100644 --- a/backend/pkg/api/hooks.go +++ b/backend/pkg/api/hooks.go @@ -42,6 +42,7 @@ type Hooks struct { type ConfigConnectRPCRequest struct { BaseInterceptors []connect.Interceptor GRPCGatewayMux *runtime.ServeMux + Services map[string]any } // ConfigConnectRPCResponse configures connect services. @@ -49,12 +50,15 @@ type ConfigConnectRPCResponse struct { // Instructs OSS to use these intercptors for all connect services Interceptors []connect.Interceptor - // Instructs OSS to register these services in addition to the OSS ones - AdditionalServices []ConnectService - // HTTPMiddlewares are middlewares that shall be used for the router // that serves all ConnectRPC requests. HTTPMiddlewares []func(http.Handler) http.Handler + + // Original, possibly mutated, services + Services map[string]any + + // Instructs OSS to register these services in addition to the OSS ones + AdditionalServices []ConnectService } // ConnectService is a Connect handler along with its metadata @@ -144,6 +148,11 @@ type AuthorizationHooks interface { CanCreateSchemas(ctx context.Context) (bool, *rest.Error) CanDeleteSchemas(ctx context.Context) (bool, *rest.Error) CanManageSchemaRegistry(ctx context.Context) (bool, *rest.Error) + + // Kafka Role Hooks + CanListRedpandaRoles(ctx context.Context) (bool, *rest.Error) + CanCreateRedpandaRoles(ctx context.Context) (bool, *rest.Error) + CanDeleteRedpandaRoles(ctx context.Context) (bool, *rest.Error) } // ConsoleHooks are hooks for providing additional context to the Frontend where needed. @@ -209,6 +218,7 @@ func (*defaultHooks) ConfigConnectRPC(req ConfigConnectRPCRequest) ConfigConnect return ConfigConnectRPCResponse{ Interceptors: req.BaseInterceptors, AdditionalServices: []ConnectService{}, + Services: req.Services, } } @@ -379,3 +389,15 @@ func (*defaultHooks) AdditionalLogFields(_ context.Context) []zapcore.Field { func (*defaultHooks) EnabledConnectClusterFeatures(_ context.Context, _ string) []pkgconnect.ClusterFeature { return nil } + +func (*defaultHooks) CanListRedpandaRoles(_ context.Context) (bool, *rest.Error) { + return true, nil +} + +func (*defaultHooks) CanCreateRedpandaRoles(_ context.Context) (bool, *rest.Error) { + return true, nil +} + +func (*defaultHooks) CanDeleteRedpandaRoles(_ context.Context) (bool, *rest.Error) { + return true, nil +} diff --git a/backend/pkg/api/hooks/hooks.go b/backend/pkg/api/hooks/hooks.go index d66bc945e..e8f270344 100644 --- a/backend/pkg/api/hooks/hooks.go +++ b/backend/pkg/api/hooks/hooks.go @@ -130,6 +130,11 @@ type AuthorizationHooks interface { CanCreateSchemas(ctx context.Context) (bool, *rest.Error) CanDeleteSchemas(ctx context.Context) (bool, *rest.Error) CanManageSchemaRegistry(ctx context.Context) (bool, *rest.Error) + + // Redpanda Role Hooks + CanListRedpandaRoles(ctx context.Context) (bool, *rest.Error) + CanCreateRedpandaRoles(ctx context.Context) (bool, *rest.Error) + CanDeleteRedpandaRoles(ctx context.Context) (bool, *rest.Error) } // ConsoleHooks are hooks for providing additional context to the Frontend where needed. diff --git a/backend/pkg/api/mocks/authz_hooks.go b/backend/pkg/api/mocks/authz_hooks.go index 46d37fdf1..d6cc818d8 100644 --- a/backend/pkg/api/mocks/authz_hooks.go +++ b/backend/pkg/api/mocks/authz_hooks.go @@ -118,6 +118,21 @@ func (mr *MockAuthorizationHooksMockRecorder) CanCreateKafkaUsers(arg0 any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanCreateKafkaUsers", reflect.TypeOf((*MockAuthorizationHooks)(nil).CanCreateKafkaUsers), arg0) } +// CanCreateRedpandaRoles mocks base method. +func (m *MockAuthorizationHooks) CanCreateRedpandaRoles(arg0 context.Context) (bool, *rest.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CanCreateRedpandaRoles", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(*rest.Error) + return ret0, ret1 +} + +// CanCreateRedpandaRoles indicates an expected call of CanCreateRedpandaRoles. +func (mr *MockAuthorizationHooksMockRecorder) CanCreateRedpandaRoles(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanCreateRedpandaRoles", reflect.TypeOf((*MockAuthorizationHooks)(nil).CanCreateRedpandaRoles), arg0) +} + // CanCreateSchemas mocks base method. func (m *MockAuthorizationHooks) CanCreateSchemas(arg0 context.Context) (bool, *rest.Error) { m.ctrl.T.Helper() @@ -208,6 +223,21 @@ func (mr *MockAuthorizationHooksMockRecorder) CanDeleteKafkaUsers(arg0 any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDeleteKafkaUsers", reflect.TypeOf((*MockAuthorizationHooks)(nil).CanDeleteKafkaUsers), arg0) } +// CanDeleteRedpandaRoles mocks base method. +func (m *MockAuthorizationHooks) CanDeleteRedpandaRoles(arg0 context.Context) (bool, *rest.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CanDeleteRedpandaRoles", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(*rest.Error) + return ret0, ret1 +} + +// CanDeleteRedpandaRoles indicates an expected call of CanDeleteRedpandaRoles. +func (mr *MockAuthorizationHooksMockRecorder) CanDeleteRedpandaRoles(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDeleteRedpandaRoles", reflect.TypeOf((*MockAuthorizationHooks)(nil).CanDeleteRedpandaRoles), arg0) +} + // CanDeleteSchemas mocks base method. func (m *MockAuthorizationHooks) CanDeleteSchemas(arg0 context.Context) (bool, *rest.Error) { m.ctrl.T.Helper() @@ -343,6 +373,21 @@ func (mr *MockAuthorizationHooksMockRecorder) CanListQuotas(arg0 any) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanListQuotas", reflect.TypeOf((*MockAuthorizationHooks)(nil).CanListQuotas), arg0) } +// CanListRedpandaRoles mocks base method. +func (m *MockAuthorizationHooks) CanListRedpandaRoles(arg0 context.Context) (bool, *rest.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CanListRedpandaRoles", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(*rest.Error) + return ret0, ret1 +} + +// CanListRedpandaRoles indicates an expected call of CanListRedpandaRoles. +func (mr *MockAuthorizationHooksMockRecorder) CanListRedpandaRoles(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanListRedpandaRoles", reflect.TypeOf((*MockAuthorizationHooks)(nil).CanListRedpandaRoles), arg0) +} + // CanManageSchemaRegistry mocks base method. func (m *MockAuthorizationHooks) CanManageSchemaRegistry(arg0 context.Context) (bool, *rest.Error) { m.ctrl.T.Helper() diff --git a/backend/pkg/api/routes.go b/backend/pkg/api/routes.go index 183586fb3..923e52851 100644 --- a/backend/pkg/api/routes.go +++ b/backend/pkg/api/routes.go @@ -90,10 +90,28 @@ func (api *API) setupConnectWithGRPCGateway(r chi.Router) { runtime.WithUnescapingMode(runtime.UnescapingModeAllExceptReserved), ) + // Create OSS Connect handlers only after calling hook. We need the hook output's final list of interceptors. + userSvc := apiusersvc.NewService(api.Cfg, api.Logger.Named("user_service"), api.RedpandaSvc, api.ConsoleSvc, api.Hooks.Authorization.IsProtectedKafkaUser) + aclSvc := apiaclsvc.NewService(api.Cfg, api.Logger.Named("kafka_service"), api.ConsoleSvc) + kafkaConnectSvc := apikafkaconnectsvc.NewService(api.Cfg, api.Logger.Named("kafka_connect_service"), api.ConnectSvc) + topicSvc := topicsvc.NewService(api.Cfg, api.Logger.Named("topic_service"), api.ConsoleSvc) + transformSvc := transformsvc.NewService(api.Cfg, api.Logger.Named("transform_service"), api.RedpandaSvc, v) + consoleSvc := consolesvc.NewService(api.Logger.Named("console_service"), api.ConsoleSvc, api.Hooks.Authorization) + securitySvc := consolev1alpha1connect.UnimplementedSecurityServiceHandler{} + // Call Hook hookOutput := api.Hooks.Route.ConfigConnectRPC(ConfigConnectRPCRequest{ BaseInterceptors: baseInterceptors, GRPCGatewayMux: gwMux, + Services: map[string]any{ + dataplanev1alpha1connect.UserServiceName: userSvc, + dataplanev1alpha1connect.ACLServiceName: aclSvc, + dataplanev1alpha1connect.KafkaConnectServiceName: kafkaConnectSvc, + dataplanev1alpha1connect.TopicServiceName: topicSvc, + dataplanev1alpha1connect.TransformServiceName: transformSvc, + consolev1alpha1connect.ConsoleServiceName: consoleSvc, + consolev1alpha1connect.SecurityServiceName: securitySvc, + }, }) // Use HTTP Middlewares that are configured by the Hook @@ -102,23 +120,30 @@ func (api *API) setupConnectWithGRPCGateway(r chi.Router) { } r.Mount("/v1alpha1", gwMux) // Dataplane API - // Create OSS Connect handlers only after calling hook. We need the hook output's final list of interceptors. - userSvc := apiusersvc.NewService(api.Cfg, api.Logger.Named("user_service"), api.RedpandaSvc, api.ConsoleSvc, api.Hooks.Authorization.IsProtectedKafkaUser) - aclSvc := apiaclsvc.NewService(api.Cfg, api.Logger.Named("kafka_service"), api.ConsoleSvc) - consoleSvc := consolesvc.NewService(api.Logger.Named("console_service"), api.ConsoleSvc, api.Hooks.Authorization) - kafkaConnectSvc := apikafkaconnectsvc.NewService(api.Cfg, api.Logger.Named("kafka_connect_service"), api.ConnectSvc) - topicSvc := topicsvc.NewService(api.Cfg, api.Logger.Named("topic_service"), api.ConsoleSvc) - transformSvc := transformsvc.NewService(api.Cfg, api.Logger.Named("transform_service"), api.RedpandaSvc, v) - // Wasm Transforms r.Put("/v1alpha1/transforms", transformSvc.HandleDeployTransform()) - userSvcPath, userSvcHandler := dataplanev1alpha1connect.NewUserServiceHandler(userSvc, connect.WithInterceptors(hookOutput.Interceptors...)) - aclSvcPath, aclSvcHandler := dataplanev1alpha1connect.NewACLServiceHandler(aclSvc, connect.WithInterceptors(hookOutput.Interceptors...)) - kafkaConnectPath, kafkaConnectHandler := dataplanev1alpha1connect.NewKafkaConnectServiceHandler(kafkaConnectSvc, connect.WithInterceptors(hookOutput.Interceptors...)) - consoleServicePath, consoleServiceHandler := consolev1alpha1connect.NewConsoleServiceHandler(consoleSvc, connect.WithInterceptors(hookOutput.Interceptors...)) - topicSvcPath, topicSvcHandler := dataplanev1alpha1connect.NewTopicServiceHandler(topicSvc, connect.WithInterceptors(hookOutput.Interceptors...)) - transformSvcPath, transformSvcHandler := dataplanev1alpha1connect.NewTransformServiceHandler(transformSvc, connect.WithInterceptors(hookOutput.Interceptors...)) + userSvcPath, userSvcHandler := dataplanev1alpha1connect.NewUserServiceHandler( + hookOutput.Services[dataplanev1alpha1connect.UserServiceName].(dataplanev1alpha1connect.UserServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) + aclSvcPath, aclSvcHandler := dataplanev1alpha1connect.NewACLServiceHandler( + hookOutput.Services[dataplanev1alpha1connect.ACLServiceName].(dataplanev1alpha1connect.ACLServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) + kafkaConnectPath, kafkaConnectHandler := dataplanev1alpha1connect.NewKafkaConnectServiceHandler( + hookOutput.Services[dataplanev1alpha1connect.KafkaConnectServiceName].(dataplanev1alpha1connect.KafkaConnectServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) + topicSvcPath, topicSvcHandler := dataplanev1alpha1connect.NewTopicServiceHandler( + hookOutput.Services[dataplanev1alpha1connect.TopicServiceName].(dataplanev1alpha1connect.TopicServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) + transformSvcPath, transformSvcHandler := dataplanev1alpha1connect.NewTransformServiceHandler( + hookOutput.Services[dataplanev1alpha1connect.TransformServiceName].(dataplanev1alpha1connect.TransformServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) + consoleServicePath, consoleServiceHandler := consolev1alpha1connect.NewConsoleServiceHandler( + hookOutput.Services[consolev1alpha1connect.ConsoleServiceName].(consolev1alpha1connect.ConsoleServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) + securityServicePath, securityServiceHandler := consolev1alpha1connect.NewSecurityServiceHandler( + hookOutput.Services[consolev1alpha1connect.SecurityServiceName].(consolev1alpha1connect.SecurityServiceHandler), + connect.WithInterceptors(hookOutput.Interceptors...)) ossServices := []ConnectService{ { @@ -151,6 +176,11 @@ func (api *API) setupConnectWithGRPCGateway(r chi.Router) { MountPath: transformSvcPath, Handler: transformSvcHandler, }, + { + ServiceName: consolev1alpha1connect.SecurityServiceName, + MountPath: securityServicePath, + Handler: securityServiceHandler, + }, } // Order matters. OSS services first, so Enterprise handlers override OSS. diff --git a/backend/pkg/console/endpoint_compatibility.go b/backend/pkg/console/endpoint_compatibility.go index 685027215..3b650a259 100644 --- a/backend/pkg/console/endpoint_compatibility.go +++ b/backend/pkg/console/endpoint_compatibility.go @@ -13,8 +13,11 @@ import ( "context" "fmt" + "github.com/coreos/go-semver/semver" "github.com/twmb/franz-go/pkg/kmsg" "github.com/twmb/franz-go/pkg/kversion" + + "github.com/redpanda-data/console/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect" ) // EndpointCompatibility describes what Console endpoints can be offered to the frontend, @@ -46,10 +49,11 @@ func (s *Service) GetEndpointCompatibility(ctx context.Context) (EndpointCompati // Required kafka requests per API endpoint type endpoint struct { - URL string - Method string - Requests []kmsg.Request - HasRedpandaAPI bool + URL string + Method string + Requests []kmsg.Request + HasRedpandaAPI bool + MinRedpandaVersion string } endpointRequirements := []endpoint{ { @@ -115,12 +119,25 @@ func (s *Service) GetEndpointCompatibility(ctx context.Context) (EndpointCompati Requests: []kmsg.Request{&kmsg.AlterUserSCRAMCredentialsRequest{}}, HasRedpandaAPI: true, }, + { + URL: consolev1alpha1connect.SecurityServiceName, + Method: "POST", + HasRedpandaAPI: true, + MinRedpandaVersion: "24.1.1-rc5", // no v prefix + }, } endpoints := make([]EndpointCompatibilityEndpoint, 0, len(endpointRequirements)) for _, endpointReq := range endpointRequirements { endpointSupported := true + if len(endpointReq.Requests) == 0 { + // not a Kafka API endpoint + // it requires Redpanda Admin API only + // default it to false + endpointSupported = false + } + for _, req := range endpointReq.Requests { _, isSupported := versions.LookupMaxKeyVersion(req.Key()) if !isSupported { @@ -134,6 +151,14 @@ func (s *Service) GetEndpointCompatibility(ctx context.Context) (EndpointCompati // this feature anyways. if endpointReq.HasRedpandaAPI && s.redpandaSvc != nil { endpointSupported = true + + if endpointReq.MinRedpandaVersion != "" { + minV, _ := semver.NewVersion(endpointReq.MinRedpandaVersion) + rpV, _ := semver.NewVersion(s.redpandaSvc.ClusterVersion()) + if minV != nil && rpV != nil { + endpointSupported = rpV.Compare(*minV) >= 0 + } + } } endpoints = append(endpoints, EndpointCompatibilityEndpoint{ diff --git a/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect/security.connect.go b/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect/security.connect.go new file mode 100644 index 000000000..1294b8cb9 --- /dev/null +++ b/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect/security.connect.go @@ -0,0 +1,284 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/console/v1alpha1/security.proto + +package consolev1alpha1connect + +import ( + context "context" + errors "errors" + http "net/http" + strings "strings" + + connect "connectrpc.com/connect" + + v1alpha1 "github.com/redpanda-data/console/backend/pkg/protogen/redpanda/api/console/v1alpha1" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // SecurityServiceName is the fully-qualified name of the SecurityService service. + SecurityServiceName = "redpanda.api.console.v1alpha1.SecurityService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // SecurityServiceListRolesProcedure is the fully-qualified name of the SecurityService's ListRoles + // RPC. + SecurityServiceListRolesProcedure = "/redpanda.api.console.v1alpha1.SecurityService/ListRoles" + // SecurityServiceCreateRoleProcedure is the fully-qualified name of the SecurityService's + // CreateRole RPC. + SecurityServiceCreateRoleProcedure = "/redpanda.api.console.v1alpha1.SecurityService/CreateRole" + // SecurityServiceGetRoleProcedure is the fully-qualified name of the SecurityService's GetRole RPC. + SecurityServiceGetRoleProcedure = "/redpanda.api.console.v1alpha1.SecurityService/GetRole" + // SecurityServiceDeleteRoleProcedure is the fully-qualified name of the SecurityService's + // DeleteRole RPC. + SecurityServiceDeleteRoleProcedure = "/redpanda.api.console.v1alpha1.SecurityService/DeleteRole" + // SecurityServiceListRoleMembersProcedure is the fully-qualified name of the SecurityService's + // ListRoleMembers RPC. + SecurityServiceListRoleMembersProcedure = "/redpanda.api.console.v1alpha1.SecurityService/ListRoleMembers" + // SecurityServiceUpdateRoleMembershipProcedure is the fully-qualified name of the SecurityService's + // UpdateRoleMembership RPC. + SecurityServiceUpdateRoleMembershipProcedure = "/redpanda.api.console.v1alpha1.SecurityService/UpdateRoleMembership" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + securityServiceServiceDescriptor = v1alpha1.File_redpanda_api_console_v1alpha1_security_proto.Services().ByName("SecurityService") + securityServiceListRolesMethodDescriptor = securityServiceServiceDescriptor.Methods().ByName("ListRoles") + securityServiceCreateRoleMethodDescriptor = securityServiceServiceDescriptor.Methods().ByName("CreateRole") + securityServiceGetRoleMethodDescriptor = securityServiceServiceDescriptor.Methods().ByName("GetRole") + securityServiceDeleteRoleMethodDescriptor = securityServiceServiceDescriptor.Methods().ByName("DeleteRole") + securityServiceListRoleMembersMethodDescriptor = securityServiceServiceDescriptor.Methods().ByName("ListRoleMembers") + securityServiceUpdateRoleMembershipMethodDescriptor = securityServiceServiceDescriptor.Methods().ByName("UpdateRoleMembership") +) + +// SecurityServiceClient is a client for the redpanda.api.console.v1alpha1.SecurityService service. +type SecurityServiceClient interface { + // ListRoles lists all the roles based on optional filter. + ListRoles(context.Context, *connect.Request[v1alpha1.ListRolesRequest]) (*connect.Response[v1alpha1.ListRolesResponse], error) + CreateRole(context.Context, *connect.Request[v1alpha1.CreateRoleRequest]) (*connect.Response[v1alpha1.CreateRoleResponse], error) + // GetRole retrieves the specific role. + GetRole(context.Context, *connect.Request[v1alpha1.GetRoleRequest]) (*connect.Response[v1alpha1.GetRoleResponse], error) + // DeleteRole deletes the role from the system. + DeleteRole(context.Context, *connect.Request[v1alpha1.DeleteRoleRequest]) (*connect.Response[v1alpha1.DeleteRoleResponse], error) + // ListRoleMembership lists all the members assigned to a role based on optional filter. + ListRoleMembers(context.Context, *connect.Request[v1alpha1.ListRoleMembersRequest]) (*connect.Response[v1alpha1.ListRoleMembersResponse], error) + // UpdateRoleMembership updates role membership. + // Partially update role membership, adding or removing from a role + // ONLY those members listed in the “add” or “remove” fields, respectively. + // Adding a member that is already assigned to the role (or removing one that is not) is a no-op, + // and the rest of the members will be added and removed and reported. + UpdateRoleMembership(context.Context, *connect.Request[v1alpha1.UpdateRoleMembershipRequest]) (*connect.Response[v1alpha1.UpdateRoleMembershipResponse], error) +} + +// NewSecurityServiceClient constructs a client for the +// redpanda.api.console.v1alpha1.SecurityService service. By default, it uses the Connect protocol +// with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed requests. To +// use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or connect.WithGRPCWeb() +// options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewSecurityServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SecurityServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &securityServiceClient{ + listRoles: connect.NewClient[v1alpha1.ListRolesRequest, v1alpha1.ListRolesResponse]( + httpClient, + baseURL+SecurityServiceListRolesProcedure, + connect.WithSchema(securityServiceListRolesMethodDescriptor), + connect.WithClientOptions(opts...), + ), + createRole: connect.NewClient[v1alpha1.CreateRoleRequest, v1alpha1.CreateRoleResponse]( + httpClient, + baseURL+SecurityServiceCreateRoleProcedure, + connect.WithSchema(securityServiceCreateRoleMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getRole: connect.NewClient[v1alpha1.GetRoleRequest, v1alpha1.GetRoleResponse]( + httpClient, + baseURL+SecurityServiceGetRoleProcedure, + connect.WithSchema(securityServiceGetRoleMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteRole: connect.NewClient[v1alpha1.DeleteRoleRequest, v1alpha1.DeleteRoleResponse]( + httpClient, + baseURL+SecurityServiceDeleteRoleProcedure, + connect.WithSchema(securityServiceDeleteRoleMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listRoleMembers: connect.NewClient[v1alpha1.ListRoleMembersRequest, v1alpha1.ListRoleMembersResponse]( + httpClient, + baseURL+SecurityServiceListRoleMembersProcedure, + connect.WithSchema(securityServiceListRoleMembersMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateRoleMembership: connect.NewClient[v1alpha1.UpdateRoleMembershipRequest, v1alpha1.UpdateRoleMembershipResponse]( + httpClient, + baseURL+SecurityServiceUpdateRoleMembershipProcedure, + connect.WithSchema(securityServiceUpdateRoleMembershipMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// securityServiceClient implements SecurityServiceClient. +type securityServiceClient struct { + listRoles *connect.Client[v1alpha1.ListRolesRequest, v1alpha1.ListRolesResponse] + createRole *connect.Client[v1alpha1.CreateRoleRequest, v1alpha1.CreateRoleResponse] + getRole *connect.Client[v1alpha1.GetRoleRequest, v1alpha1.GetRoleResponse] + deleteRole *connect.Client[v1alpha1.DeleteRoleRequest, v1alpha1.DeleteRoleResponse] + listRoleMembers *connect.Client[v1alpha1.ListRoleMembersRequest, v1alpha1.ListRoleMembersResponse] + updateRoleMembership *connect.Client[v1alpha1.UpdateRoleMembershipRequest, v1alpha1.UpdateRoleMembershipResponse] +} + +// ListRoles calls redpanda.api.console.v1alpha1.SecurityService.ListRoles. +func (c *securityServiceClient) ListRoles(ctx context.Context, req *connect.Request[v1alpha1.ListRolesRequest]) (*connect.Response[v1alpha1.ListRolesResponse], error) { + return c.listRoles.CallUnary(ctx, req) +} + +// CreateRole calls redpanda.api.console.v1alpha1.SecurityService.CreateRole. +func (c *securityServiceClient) CreateRole(ctx context.Context, req *connect.Request[v1alpha1.CreateRoleRequest]) (*connect.Response[v1alpha1.CreateRoleResponse], error) { + return c.createRole.CallUnary(ctx, req) +} + +// GetRole calls redpanda.api.console.v1alpha1.SecurityService.GetRole. +func (c *securityServiceClient) GetRole(ctx context.Context, req *connect.Request[v1alpha1.GetRoleRequest]) (*connect.Response[v1alpha1.GetRoleResponse], error) { + return c.getRole.CallUnary(ctx, req) +} + +// DeleteRole calls redpanda.api.console.v1alpha1.SecurityService.DeleteRole. +func (c *securityServiceClient) DeleteRole(ctx context.Context, req *connect.Request[v1alpha1.DeleteRoleRequest]) (*connect.Response[v1alpha1.DeleteRoleResponse], error) { + return c.deleteRole.CallUnary(ctx, req) +} + +// ListRoleMembers calls redpanda.api.console.v1alpha1.SecurityService.ListRoleMembers. +func (c *securityServiceClient) ListRoleMembers(ctx context.Context, req *connect.Request[v1alpha1.ListRoleMembersRequest]) (*connect.Response[v1alpha1.ListRoleMembersResponse], error) { + return c.listRoleMembers.CallUnary(ctx, req) +} + +// UpdateRoleMembership calls redpanda.api.console.v1alpha1.SecurityService.UpdateRoleMembership. +func (c *securityServiceClient) UpdateRoleMembership(ctx context.Context, req *connect.Request[v1alpha1.UpdateRoleMembershipRequest]) (*connect.Response[v1alpha1.UpdateRoleMembershipResponse], error) { + return c.updateRoleMembership.CallUnary(ctx, req) +} + +// SecurityServiceHandler is an implementation of the redpanda.api.console.v1alpha1.SecurityService +// service. +type SecurityServiceHandler interface { + // ListRoles lists all the roles based on optional filter. + ListRoles(context.Context, *connect.Request[v1alpha1.ListRolesRequest]) (*connect.Response[v1alpha1.ListRolesResponse], error) + CreateRole(context.Context, *connect.Request[v1alpha1.CreateRoleRequest]) (*connect.Response[v1alpha1.CreateRoleResponse], error) + // GetRole retrieves the specific role. + GetRole(context.Context, *connect.Request[v1alpha1.GetRoleRequest]) (*connect.Response[v1alpha1.GetRoleResponse], error) + // DeleteRole deletes the role from the system. + DeleteRole(context.Context, *connect.Request[v1alpha1.DeleteRoleRequest]) (*connect.Response[v1alpha1.DeleteRoleResponse], error) + // ListRoleMembership lists all the members assigned to a role based on optional filter. + ListRoleMembers(context.Context, *connect.Request[v1alpha1.ListRoleMembersRequest]) (*connect.Response[v1alpha1.ListRoleMembersResponse], error) + // UpdateRoleMembership updates role membership. + // Partially update role membership, adding or removing from a role + // ONLY those members listed in the “add” or “remove” fields, respectively. + // Adding a member that is already assigned to the role (or removing one that is not) is a no-op, + // and the rest of the members will be added and removed and reported. + UpdateRoleMembership(context.Context, *connect.Request[v1alpha1.UpdateRoleMembershipRequest]) (*connect.Response[v1alpha1.UpdateRoleMembershipResponse], error) +} + +// NewSecurityServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewSecurityServiceHandler(svc SecurityServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + securityServiceListRolesHandler := connect.NewUnaryHandler( + SecurityServiceListRolesProcedure, + svc.ListRoles, + connect.WithSchema(securityServiceListRolesMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + securityServiceCreateRoleHandler := connect.NewUnaryHandler( + SecurityServiceCreateRoleProcedure, + svc.CreateRole, + connect.WithSchema(securityServiceCreateRoleMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + securityServiceGetRoleHandler := connect.NewUnaryHandler( + SecurityServiceGetRoleProcedure, + svc.GetRole, + connect.WithSchema(securityServiceGetRoleMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + securityServiceDeleteRoleHandler := connect.NewUnaryHandler( + SecurityServiceDeleteRoleProcedure, + svc.DeleteRole, + connect.WithSchema(securityServiceDeleteRoleMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + securityServiceListRoleMembersHandler := connect.NewUnaryHandler( + SecurityServiceListRoleMembersProcedure, + svc.ListRoleMembers, + connect.WithSchema(securityServiceListRoleMembersMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + securityServiceUpdateRoleMembershipHandler := connect.NewUnaryHandler( + SecurityServiceUpdateRoleMembershipProcedure, + svc.UpdateRoleMembership, + connect.WithSchema(securityServiceUpdateRoleMembershipMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.console.v1alpha1.SecurityService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case SecurityServiceListRolesProcedure: + securityServiceListRolesHandler.ServeHTTP(w, r) + case SecurityServiceCreateRoleProcedure: + securityServiceCreateRoleHandler.ServeHTTP(w, r) + case SecurityServiceGetRoleProcedure: + securityServiceGetRoleHandler.ServeHTTP(w, r) + case SecurityServiceDeleteRoleProcedure: + securityServiceDeleteRoleHandler.ServeHTTP(w, r) + case SecurityServiceListRoleMembersProcedure: + securityServiceListRoleMembersHandler.ServeHTTP(w, r) + case SecurityServiceUpdateRoleMembershipProcedure: + securityServiceUpdateRoleMembershipHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedSecurityServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedSecurityServiceHandler struct{} + +func (UnimplementedSecurityServiceHandler) ListRoles(context.Context, *connect.Request[v1alpha1.ListRolesRequest]) (*connect.Response[v1alpha1.ListRolesResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.console.v1alpha1.SecurityService.ListRoles is not implemented")) +} + +func (UnimplementedSecurityServiceHandler) CreateRole(context.Context, *connect.Request[v1alpha1.CreateRoleRequest]) (*connect.Response[v1alpha1.CreateRoleResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.console.v1alpha1.SecurityService.CreateRole is not implemented")) +} + +func (UnimplementedSecurityServiceHandler) GetRole(context.Context, *connect.Request[v1alpha1.GetRoleRequest]) (*connect.Response[v1alpha1.GetRoleResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.console.v1alpha1.SecurityService.GetRole is not implemented")) +} + +func (UnimplementedSecurityServiceHandler) DeleteRole(context.Context, *connect.Request[v1alpha1.DeleteRoleRequest]) (*connect.Response[v1alpha1.DeleteRoleResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.console.v1alpha1.SecurityService.DeleteRole is not implemented")) +} + +func (UnimplementedSecurityServiceHandler) ListRoleMembers(context.Context, *connect.Request[v1alpha1.ListRoleMembersRequest]) (*connect.Response[v1alpha1.ListRoleMembersResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.console.v1alpha1.SecurityService.ListRoleMembers is not implemented")) +} + +func (UnimplementedSecurityServiceHandler) UpdateRoleMembership(context.Context, *connect.Request[v1alpha1.UpdateRoleMembershipRequest]) (*connect.Response[v1alpha1.UpdateRoleMembershipResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.console.v1alpha1.SecurityService.UpdateRoleMembership is not implemented")) +} diff --git a/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect/security.connect.gw.go b/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect/security.connect.gw.go new file mode 100644 index 000000000..6ff0b23c1 --- /dev/null +++ b/backend/pkg/protogen/redpanda/api/console/v1alpha1/consolev1alpha1connect/security.connect.gw.go @@ -0,0 +1,71 @@ +// Code generated by protoc-gen-connect-gateway. DO NOT EDIT. +// +// Source: redpanda/api/console/v1alpha1/security.proto + +package consolev1alpha1connect + +import ( + context "context" + fmt "fmt" + + runtime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + connect_gateway "go.vallahaye.net/connect-gateway" + + v1alpha1 "github.com/redpanda-data/console/backend/pkg/protogen/redpanda/api/console/v1alpha1" +) + +// SecurityServiceGatewayServer implements the gRPC server API for the SecurityService service. +type SecurityServiceGatewayServer struct { + v1alpha1.UnimplementedSecurityServiceServer + listRoles connect_gateway.UnaryHandler[v1alpha1.ListRolesRequest, v1alpha1.ListRolesResponse] + createRole connect_gateway.UnaryHandler[v1alpha1.CreateRoleRequest, v1alpha1.CreateRoleResponse] + getRole connect_gateway.UnaryHandler[v1alpha1.GetRoleRequest, v1alpha1.GetRoleResponse] + deleteRole connect_gateway.UnaryHandler[v1alpha1.DeleteRoleRequest, v1alpha1.DeleteRoleResponse] + listRoleMembers connect_gateway.UnaryHandler[v1alpha1.ListRoleMembersRequest, v1alpha1.ListRoleMembersResponse] + updateRoleMembership connect_gateway.UnaryHandler[v1alpha1.UpdateRoleMembershipRequest, v1alpha1.UpdateRoleMembershipResponse] +} + +// NewSecurityServiceGatewayServer constructs a Connect-Gateway gRPC server for the SecurityService +// service. +func NewSecurityServiceGatewayServer(svc SecurityServiceHandler, opts ...connect_gateway.HandlerOption) *SecurityServiceGatewayServer { + return &SecurityServiceGatewayServer{ + listRoles: connect_gateway.NewUnaryHandler(SecurityServiceListRolesProcedure, svc.ListRoles, opts...), + createRole: connect_gateway.NewUnaryHandler(SecurityServiceCreateRoleProcedure, svc.CreateRole, opts...), + getRole: connect_gateway.NewUnaryHandler(SecurityServiceGetRoleProcedure, svc.GetRole, opts...), + deleteRole: connect_gateway.NewUnaryHandler(SecurityServiceDeleteRoleProcedure, svc.DeleteRole, opts...), + listRoleMembers: connect_gateway.NewUnaryHandler(SecurityServiceListRoleMembersProcedure, svc.ListRoleMembers, opts...), + updateRoleMembership: connect_gateway.NewUnaryHandler(SecurityServiceUpdateRoleMembershipProcedure, svc.UpdateRoleMembership, opts...), + } +} + +func (s *SecurityServiceGatewayServer) ListRoles(ctx context.Context, req *v1alpha1.ListRolesRequest) (*v1alpha1.ListRolesResponse, error) { + return s.listRoles(ctx, req) +} + +func (s *SecurityServiceGatewayServer) CreateRole(ctx context.Context, req *v1alpha1.CreateRoleRequest) (*v1alpha1.CreateRoleResponse, error) { + return s.createRole(ctx, req) +} + +func (s *SecurityServiceGatewayServer) GetRole(ctx context.Context, req *v1alpha1.GetRoleRequest) (*v1alpha1.GetRoleResponse, error) { + return s.getRole(ctx, req) +} + +func (s *SecurityServiceGatewayServer) DeleteRole(ctx context.Context, req *v1alpha1.DeleteRoleRequest) (*v1alpha1.DeleteRoleResponse, error) { + return s.deleteRole(ctx, req) +} + +func (s *SecurityServiceGatewayServer) ListRoleMembers(ctx context.Context, req *v1alpha1.ListRoleMembersRequest) (*v1alpha1.ListRoleMembersResponse, error) { + return s.listRoleMembers(ctx, req) +} + +func (s *SecurityServiceGatewayServer) UpdateRoleMembership(ctx context.Context, req *v1alpha1.UpdateRoleMembershipRequest) (*v1alpha1.UpdateRoleMembershipResponse, error) { + return s.updateRoleMembership(ctx, req) +} + +// RegisterSecurityServiceHandlerGatewayServer registers the Connect handlers for the +// SecurityService "svc" to "mux". +func RegisterSecurityServiceHandlerGatewayServer(mux *runtime.ServeMux, svc SecurityServiceHandler, opts ...connect_gateway.HandlerOption) { + if err := v1alpha1.RegisterSecurityServiceHandlerServer(context.TODO(), mux, NewSecurityServiceGatewayServer(svc, opts...)); err != nil { + panic(fmt.Errorf("connect-gateway: %w", err)) + } +} diff --git a/backend/pkg/protogen/redpanda/api/console/v1alpha1/security.pb.go b/backend/pkg/protogen/redpanda/api/console/v1alpha1/security.pb.go new file mode 100644 index 000000000..74cb572fb --- /dev/null +++ b/backend/pkg/protogen/redpanda/api/console/v1alpha1/security.pb.go @@ -0,0 +1,1449 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: redpanda/api/console/v1alpha1/security.proto + +package consolev1alpha1 + +import ( + reflect "reflect" + sync "sync" + + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Role defines a role in the system. +type Role struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the role. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Role) Reset() { + *x = Role{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Role) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Role) ProtoMessage() {} + +func (x *Role) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Role.ProtoReflect.Descriptor instead. +func (*Role) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{0} +} + +func (x *Role) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// ListRolesRequest is the request for ListRoles. +type ListRolesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Optional filter. + Filter *ListRolesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // Page size. + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListRolesRequest) Reset() { + *x = ListRolesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRolesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRolesRequest) ProtoMessage() {} + +func (x *ListRolesRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRolesRequest.ProtoReflect.Descriptor instead. +func (*ListRolesRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{1} +} + +func (x *ListRolesRequest) GetFilter() *ListRolesRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListRolesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRolesRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +// ListRolesResponse is the response for ListRoles. +type ListRolesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The roles in the system. + Roles []*Role `protobuf:"bytes,1,rep,name=roles,proto3" json:"roles,omitempty"` + // Token to retrieve the next page. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListRolesResponse) Reset() { + *x = ListRolesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRolesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRolesResponse) ProtoMessage() {} + +func (x *ListRolesResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRolesResponse.ProtoReflect.Descriptor instead. +func (*ListRolesResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{2} +} + +func (x *ListRolesResponse) GetRoles() []*Role { + if x != nil { + return x.Roles + } + return nil +} + +func (x *ListRolesResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// CreateRoleRequest is the request for CreateRole. +type CreateRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role to create. + Role *Role `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` +} + +func (x *CreateRoleRequest) Reset() { + *x = CreateRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRoleRequest) ProtoMessage() {} + +func (x *CreateRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRoleRequest.ProtoReflect.Descriptor instead. +func (*CreateRoleRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateRoleRequest) GetRole() *Role { + if x != nil { + return x.Role + } + return nil +} + +// CreateRoleResponse is the response for CreateRole. +type CreateRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role. + Role *Role `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` +} + +func (x *CreateRoleResponse) Reset() { + *x = CreateRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRoleResponse) ProtoMessage() {} + +func (x *CreateRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRoleResponse.ProtoReflect.Descriptor instead. +func (*CreateRoleResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateRoleResponse) GetRole() *Role { + if x != nil { + return x.Role + } + return nil +} + +// CreateRoleRequest is the request for CreateRole. +type GetRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role name. + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` +} + +func (x *GetRoleRequest) Reset() { + *x = GetRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleRequest) ProtoMessage() {} + +func (x *GetRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleRequest.ProtoReflect.Descriptor instead. +func (*GetRoleRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{5} +} + +func (x *GetRoleRequest) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +// GetRoleResponse is the response to GetRole. +type GetRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The Role. + Role *Role `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + // Members assigned to the role. + Members []*RoleMembership `protobuf:"bytes,2,rep,name=members,proto3" json:"members,omitempty"` +} + +func (x *GetRoleResponse) Reset() { + *x = GetRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleResponse) ProtoMessage() {} + +func (x *GetRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleResponse.ProtoReflect.Descriptor instead. +func (*GetRoleResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{6} +} + +func (x *GetRoleResponse) GetRole() *Role { + if x != nil { + return x.Role + } + return nil +} + +func (x *GetRoleResponse) GetMembers() []*RoleMembership { + if x != nil { + return x.Members + } + return nil +} + +// DeleteRoleRequest is the request for DeleteRole. +type DeleteRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role name. + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + // Whether to delete the ACLs bound to the role. + DeleteAcls bool `protobuf:"varint,2,opt,name=delete_acls,json=deleteAcls,proto3" json:"delete_acls,omitempty"` +} + +func (x *DeleteRoleRequest) Reset() { + *x = DeleteRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleRequest) ProtoMessage() {} + +func (x *DeleteRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleRequest.ProtoReflect.Descriptor instead. +func (*DeleteRoleRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteRoleRequest) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *DeleteRoleRequest) GetDeleteAcls() bool { + if x != nil { + return x.DeleteAcls + } + return false +} + +// DeleteRoleResponse is the response for DeleteRole. +type DeleteRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteRoleResponse) Reset() { + *x = DeleteRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleResponse) ProtoMessage() {} + +func (x *DeleteRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleResponse.ProtoReflect.Descriptor instead. +func (*DeleteRoleResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{8} +} + +// List role members for a role. That is user principals assigned to that role. +type ListRoleMembersRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role name. + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + // Optional filter. + Filter *ListRoleMembersRequest_Filter `protobuf:"bytes,2,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // Page size. + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,4,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListRoleMembersRequest) Reset() { + *x = ListRoleMembersRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRoleMembersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleMembersRequest) ProtoMessage() {} + +func (x *ListRoleMembersRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleMembersRequest.ProtoReflect.Descriptor instead. +func (*ListRoleMembersRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{9} +} + +func (x *ListRoleMembersRequest) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *ListRoleMembersRequest) GetFilter() *ListRoleMembersRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListRoleMembersRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRoleMembersRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +// ListRoleMembersResponse is the response for ListRoleMembers. +type ListRoleMembersResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role name. + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + // Members assigned to the role. + Members []*RoleMembership `protobuf:"bytes,2,rep,name=members,proto3" json:"members,omitempty"` + // Token to retrieve the next page. + NextPageToken string `protobuf:"bytes,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListRoleMembersResponse) Reset() { + *x = ListRoleMembersResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRoleMembersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleMembersResponse) ProtoMessage() {} + +func (x *ListRoleMembersResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleMembersResponse.ProtoReflect.Descriptor instead. +func (*ListRoleMembersResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{10} +} + +func (x *ListRoleMembersResponse) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *ListRoleMembersResponse) GetMembers() []*RoleMembership { + if x != nil { + return x.Members + } + return nil +} + +func (x *ListRoleMembersResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// RoleMembership is the role membership. +type RoleMembership struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the principal assigned to the role. + Principal string `protobuf:"bytes,1,opt,name=principal,proto3" json:"principal,omitempty"` +} + +func (x *RoleMembership) Reset() { + *x = RoleMembership{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoleMembership) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoleMembership) ProtoMessage() {} + +func (x *RoleMembership) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoleMembership.ProtoReflect.Descriptor instead. +func (*RoleMembership) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{11} +} + +func (x *RoleMembership) GetPrincipal() string { + if x != nil { + return x.Principal + } + return "" +} + +// UpdateRoleMembershipRequest is the request to UpdateRoleMembership. +type UpdateRoleMembershipRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role name. + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + // Create the role if it doesn't already exist. + // If the role is created in this way, the “add” list will be respected, but the “remove” list will be ignored. + Create bool `protobuf:"varint,2,opt,name=create,proto3" json:"create,omitempty"` + // Members to assign to the role. + Add []*RoleMembership `protobuf:"bytes,3,rep,name=add,proto3" json:"add,omitempty"` + // Members to remove from the role. + Remove []*RoleMembership `protobuf:"bytes,4,rep,name=remove,proto3" json:"remove,omitempty"` +} + +func (x *UpdateRoleMembershipRequest) Reset() { + *x = UpdateRoleMembershipRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRoleMembershipRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoleMembershipRequest) ProtoMessage() {} + +func (x *UpdateRoleMembershipRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoleMembershipRequest.ProtoReflect.Descriptor instead. +func (*UpdateRoleMembershipRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{12} +} + +func (x *UpdateRoleMembershipRequest) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *UpdateRoleMembershipRequest) GetCreate() bool { + if x != nil { + return x.Create + } + return false +} + +func (x *UpdateRoleMembershipRequest) GetAdd() []*RoleMembership { + if x != nil { + return x.Add + } + return nil +} + +func (x *UpdateRoleMembershipRequest) GetRemove() []*RoleMembership { + if x != nil { + return x.Remove + } + return nil +} + +// UpdateRoleMembershipResponse is the response for UpdateRoleMembership. +type UpdateRoleMembershipResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The role name. + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + // Members assigned to the role. + Added []*RoleMembership `protobuf:"bytes,2,rep,name=added,proto3" json:"added,omitempty"` + // Members removed from the role. + Removed []*RoleMembership `protobuf:"bytes,3,rep,name=removed,proto3" json:"removed,omitempty"` +} + +func (x *UpdateRoleMembershipResponse) Reset() { + *x = UpdateRoleMembershipResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRoleMembershipResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoleMembershipResponse) ProtoMessage() {} + +func (x *UpdateRoleMembershipResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoleMembershipResponse.ProtoReflect.Descriptor instead. +func (*UpdateRoleMembershipResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{13} +} + +func (x *UpdateRoleMembershipResponse) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *UpdateRoleMembershipResponse) GetAdded() []*RoleMembership { + if x != nil { + return x.Added + } + return nil +} + +func (x *UpdateRoleMembershipResponse) GetRemoved() []*RoleMembership { + if x != nil { + return x.Removed + } + return nil +} + +// Filter options. +type ListRolesRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Filter results only roles named with the prefix. + NamePrefix string `protobuf:"bytes,1,opt,name=name_prefix,json=namePrefix,proto3" json:"name_prefix,omitempty"` + // Filter results to only roles with names which contain the string. + NameContains string `protobuf:"bytes,2,opt,name=name_contains,json=nameContains,proto3" json:"name_contains,omitempty"` + // Return only roles assigned to this principal. + Principal string `protobuf:"bytes,3,opt,name=principal,proto3" json:"principal,omitempty"` +} + +func (x *ListRolesRequest_Filter) Reset() { + *x = ListRolesRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRolesRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRolesRequest_Filter) ProtoMessage() {} + +func (x *ListRolesRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRolesRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListRolesRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *ListRolesRequest_Filter) GetNamePrefix() string { + if x != nil { + return x.NamePrefix + } + return "" +} + +func (x *ListRolesRequest_Filter) GetNameContains() string { + if x != nil { + return x.NameContains + } + return "" +} + +func (x *ListRolesRequest_Filter) GetPrincipal() string { + if x != nil { + return x.Principal + } + return "" +} + +// Filter options. +type ListRoleMembersRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Filter results to only members with names which contain the string. + NameContains string `protobuf:"bytes,1,opt,name=name_contains,json=nameContains,proto3" json:"name_contains,omitempty"` +} + +func (x *ListRoleMembersRequest_Filter) Reset() { + *x = ListRoleMembersRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRoleMembersRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleMembersRequest_Filter) ProtoMessage() {} + +func (x *ListRoleMembersRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_console_v1alpha1_security_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleMembersRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListRoleMembersRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP(), []int{9, 0} +} + +func (x *ListRoleMembersRequest_Filter) GetNameContains() string { + if x != nil { + return x.NameContains + } + return "" +} + +var File_redpanda_api_console_v1alpha1_security_proto protoreflect.FileDescriptor + +var file_redpanda_api_console_v1alpha1_security_proto_rawDesc = []byte{ + 0x0a, 0x2c, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, + 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, + 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1b, 0x62, + 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3b, 0x0a, 0x04, 0x52, 0x6f, + 0x6c, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x1f, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, + 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, + 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xf8, 0x02, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x53, 0x0a, 0x06, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x88, 0x01, + 0x01, 0x12, 0x30, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x42, 0x13, 0xba, 0x48, 0x10, 0x1a, 0x0e, 0x18, 0xe8, 0x07, 0x28, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x1a, 0xb2, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, + 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x1c, 0xba, 0x48, 0x19, 0x72, 0x17, 0x18, 0x80, 0x01, 0x32, 0x12, 0x5e, 0x28, + 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2a, 0x29, 0x24, + 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x41, 0x0a, 0x0d, + 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x1c, 0xba, 0x48, 0x19, 0x72, 0x17, 0x18, 0x80, 0x01, 0x32, 0x12, 0x5e, + 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2a, 0x29, + 0x24, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x12, + 0x26, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x18, 0x80, 0x01, 0x52, 0x09, 0x70, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x22, 0x76, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x6c, + 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x4c, 0x0a, 0x11, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x37, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, + 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x4d, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, + 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6c, + 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x4e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x6f, 0x6c, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, 0xba, 0x48, + 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x72, + 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x04, 0x72, + 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, + 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x22, 0x72, 0x0a, + 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, + 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, + 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x61, 0x63, 0x6c, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x6c, + 0x73, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc6, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, + 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, + 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x59, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x3c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x48, 0x00, + 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x30, 0x0a, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x13, + 0xba, 0x48, 0x10, 0x1a, 0x0e, 0x18, 0xe8, 0x07, 0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x01, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x37, 0x0a, 0x06, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x2d, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, + 0x48, 0x05, 0x72, 0x03, 0x18, 0x80, 0x01, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x22, 0xa7, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x07, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x2e, 0x0a, 0x0e, 0x52, 0x6f, + 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x1c, 0x0a, 0x09, + 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x22, 0xfb, 0x01, 0x0a, 0x1b, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x6f, + 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, 0xba, + 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, + 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, + 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x12, 0x3f, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, + 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x03, 0x61, 0x64, + 0x64, 0x12, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x6f, 0x6c, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, 0xba, 0x48, + 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x08, 0x72, + 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x07, + 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x6f, + 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x07, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x32, 0xf2, 0x05, 0x0a, 0x0f, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x70, 0x0a, 0x09, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x2f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x0a, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, + 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x6a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, + 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x0a, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x30, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x82, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x91, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, + 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, + 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xae, 0x02, 0x0a, 0x21, 0x63, + 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x42, 0x0d, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x63, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2f, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x1d, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1d, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x29, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_console_v1alpha1_security_proto_rawDescOnce sync.Once + file_redpanda_api_console_v1alpha1_security_proto_rawDescData = file_redpanda_api_console_v1alpha1_security_proto_rawDesc +) + +func file_redpanda_api_console_v1alpha1_security_proto_rawDescGZIP() []byte { + file_redpanda_api_console_v1alpha1_security_proto_rawDescOnce.Do(func() { + file_redpanda_api_console_v1alpha1_security_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_console_v1alpha1_security_proto_rawDescData) + }) + return file_redpanda_api_console_v1alpha1_security_proto_rawDescData +} + +var file_redpanda_api_console_v1alpha1_security_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_redpanda_api_console_v1alpha1_security_proto_goTypes = []interface{}{ + (*Role)(nil), // 0: redpanda.api.console.v1alpha1.Role + (*ListRolesRequest)(nil), // 1: redpanda.api.console.v1alpha1.ListRolesRequest + (*ListRolesResponse)(nil), // 2: redpanda.api.console.v1alpha1.ListRolesResponse + (*CreateRoleRequest)(nil), // 3: redpanda.api.console.v1alpha1.CreateRoleRequest + (*CreateRoleResponse)(nil), // 4: redpanda.api.console.v1alpha1.CreateRoleResponse + (*GetRoleRequest)(nil), // 5: redpanda.api.console.v1alpha1.GetRoleRequest + (*GetRoleResponse)(nil), // 6: redpanda.api.console.v1alpha1.GetRoleResponse + (*DeleteRoleRequest)(nil), // 7: redpanda.api.console.v1alpha1.DeleteRoleRequest + (*DeleteRoleResponse)(nil), // 8: redpanda.api.console.v1alpha1.DeleteRoleResponse + (*ListRoleMembersRequest)(nil), // 9: redpanda.api.console.v1alpha1.ListRoleMembersRequest + (*ListRoleMembersResponse)(nil), // 10: redpanda.api.console.v1alpha1.ListRoleMembersResponse + (*RoleMembership)(nil), // 11: redpanda.api.console.v1alpha1.RoleMembership + (*UpdateRoleMembershipRequest)(nil), // 12: redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest + (*UpdateRoleMembershipResponse)(nil), // 13: redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse + (*ListRolesRequest_Filter)(nil), // 14: redpanda.api.console.v1alpha1.ListRolesRequest.Filter + (*ListRoleMembersRequest_Filter)(nil), // 15: redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter +} +var file_redpanda_api_console_v1alpha1_security_proto_depIdxs = []int32{ + 14, // 0: redpanda.api.console.v1alpha1.ListRolesRequest.filter:type_name -> redpanda.api.console.v1alpha1.ListRolesRequest.Filter + 0, // 1: redpanda.api.console.v1alpha1.ListRolesResponse.roles:type_name -> redpanda.api.console.v1alpha1.Role + 0, // 2: redpanda.api.console.v1alpha1.CreateRoleRequest.role:type_name -> redpanda.api.console.v1alpha1.Role + 0, // 3: redpanda.api.console.v1alpha1.CreateRoleResponse.role:type_name -> redpanda.api.console.v1alpha1.Role + 0, // 4: redpanda.api.console.v1alpha1.GetRoleResponse.role:type_name -> redpanda.api.console.v1alpha1.Role + 11, // 5: redpanda.api.console.v1alpha1.GetRoleResponse.members:type_name -> redpanda.api.console.v1alpha1.RoleMembership + 15, // 6: redpanda.api.console.v1alpha1.ListRoleMembersRequest.filter:type_name -> redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter + 11, // 7: redpanda.api.console.v1alpha1.ListRoleMembersResponse.members:type_name -> redpanda.api.console.v1alpha1.RoleMembership + 11, // 8: redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest.add:type_name -> redpanda.api.console.v1alpha1.RoleMembership + 11, // 9: redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest.remove:type_name -> redpanda.api.console.v1alpha1.RoleMembership + 11, // 10: redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse.added:type_name -> redpanda.api.console.v1alpha1.RoleMembership + 11, // 11: redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse.removed:type_name -> redpanda.api.console.v1alpha1.RoleMembership + 1, // 12: redpanda.api.console.v1alpha1.SecurityService.ListRoles:input_type -> redpanda.api.console.v1alpha1.ListRolesRequest + 3, // 13: redpanda.api.console.v1alpha1.SecurityService.CreateRole:input_type -> redpanda.api.console.v1alpha1.CreateRoleRequest + 5, // 14: redpanda.api.console.v1alpha1.SecurityService.GetRole:input_type -> redpanda.api.console.v1alpha1.GetRoleRequest + 7, // 15: redpanda.api.console.v1alpha1.SecurityService.DeleteRole:input_type -> redpanda.api.console.v1alpha1.DeleteRoleRequest + 9, // 16: redpanda.api.console.v1alpha1.SecurityService.ListRoleMembers:input_type -> redpanda.api.console.v1alpha1.ListRoleMembersRequest + 12, // 17: redpanda.api.console.v1alpha1.SecurityService.UpdateRoleMembership:input_type -> redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest + 2, // 18: redpanda.api.console.v1alpha1.SecurityService.ListRoles:output_type -> redpanda.api.console.v1alpha1.ListRolesResponse + 4, // 19: redpanda.api.console.v1alpha1.SecurityService.CreateRole:output_type -> redpanda.api.console.v1alpha1.CreateRoleResponse + 6, // 20: redpanda.api.console.v1alpha1.SecurityService.GetRole:output_type -> redpanda.api.console.v1alpha1.GetRoleResponse + 8, // 21: redpanda.api.console.v1alpha1.SecurityService.DeleteRole:output_type -> redpanda.api.console.v1alpha1.DeleteRoleResponse + 10, // 22: redpanda.api.console.v1alpha1.SecurityService.ListRoleMembers:output_type -> redpanda.api.console.v1alpha1.ListRoleMembersResponse + 13, // 23: redpanda.api.console.v1alpha1.SecurityService.UpdateRoleMembership:output_type -> redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse + 18, // [18:24] is the sub-list for method output_type + 12, // [12:18] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_redpanda_api_console_v1alpha1_security_proto_init() } +func file_redpanda_api_console_v1alpha1_security_proto_init() { + if File_redpanda_api_console_v1alpha1_security_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Role); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRolesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRolesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRoleMembersRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRoleMembersResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoleMembership); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRoleMembershipRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRoleMembershipResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRolesRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRoleMembersRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[1].OneofWrappers = []interface{}{} + file_redpanda_api_console_v1alpha1_security_proto_msgTypes[9].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_console_v1alpha1_security_proto_rawDesc, + NumEnums: 0, + NumMessages: 16, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_console_v1alpha1_security_proto_goTypes, + DependencyIndexes: file_redpanda_api_console_v1alpha1_security_proto_depIdxs, + MessageInfos: file_redpanda_api_console_v1alpha1_security_proto_msgTypes, + }.Build() + File_redpanda_api_console_v1alpha1_security_proto = out.File + file_redpanda_api_console_v1alpha1_security_proto_rawDesc = nil + file_redpanda_api_console_v1alpha1_security_proto_goTypes = nil + file_redpanda_api_console_v1alpha1_security_proto_depIdxs = nil +} diff --git a/backend/pkg/protogen/redpanda/api/console/v1alpha1/security.pb.gw.go b/backend/pkg/protogen/redpanda/api/console/v1alpha1/security.pb.gw.go new file mode 100644 index 000000000..1ae36df72 --- /dev/null +++ b/backend/pkg/protogen/redpanda/api/console/v1alpha1/security.pb.gw.go @@ -0,0 +1,596 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: redpanda/api/console/v1alpha1/security.proto + +/* +Package consolev1alpha1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package consolev1alpha1 + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_SecurityService_ListRoles_0(ctx context.Context, marshaler runtime.Marshaler, client SecurityServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListRolesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListRoles(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SecurityService_ListRoles_0(ctx context.Context, marshaler runtime.Marshaler, server SecurityServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListRolesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListRoles(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SecurityService_CreateRole_0(ctx context.Context, marshaler runtime.Marshaler, client SecurityServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateRoleRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CreateRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SecurityService_CreateRole_0(ctx context.Context, marshaler runtime.Marshaler, server SecurityServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateRoleRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CreateRole(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SecurityService_GetRole_0(ctx context.Context, marshaler runtime.Marshaler, client SecurityServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRoleRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SecurityService_GetRole_0(ctx context.Context, marshaler runtime.Marshaler, server SecurityServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRoleRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetRole(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SecurityService_DeleteRole_0(ctx context.Context, marshaler runtime.Marshaler, client SecurityServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteRoleRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DeleteRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SecurityService_DeleteRole_0(ctx context.Context, marshaler runtime.Marshaler, server SecurityServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteRoleRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DeleteRole(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SecurityService_ListRoleMembers_0(ctx context.Context, marshaler runtime.Marshaler, client SecurityServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListRoleMembersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListRoleMembers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SecurityService_ListRoleMembers_0(ctx context.Context, marshaler runtime.Marshaler, server SecurityServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListRoleMembersRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListRoleMembers(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SecurityService_UpdateRoleMembership_0(ctx context.Context, marshaler runtime.Marshaler, client SecurityServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateRoleMembershipRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateRoleMembership(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SecurityService_UpdateRoleMembership_0(ctx context.Context, marshaler runtime.Marshaler, server SecurityServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateRoleMembershipRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateRoleMembership(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterSecurityServiceHandlerServer registers the http handlers for service SecurityService to "mux". +// UnaryRPC :call SecurityServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterSecurityServiceHandlerFromEndpoint instead. +func RegisterSecurityServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server SecurityServiceServer) error { + + mux.Handle("POST", pattern_SecurityService_ListRoles_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/ListRoles", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/ListRoles")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SecurityService_ListRoles_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_ListRoles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_CreateRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/CreateRole", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/CreateRole")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SecurityService_CreateRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_CreateRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_GetRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/GetRole", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/GetRole")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SecurityService_GetRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_GetRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_DeleteRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/DeleteRole", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/DeleteRole")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SecurityService_DeleteRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_DeleteRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_ListRoleMembers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/ListRoleMembers", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/ListRoleMembers")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SecurityService_ListRoleMembers_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_ListRoleMembers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_UpdateRoleMembership_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/UpdateRoleMembership", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/UpdateRoleMembership")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SecurityService_UpdateRoleMembership_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_UpdateRoleMembership_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterSecurityServiceHandlerFromEndpoint is same as RegisterSecurityServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterSecurityServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.DialContext(ctx, endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterSecurityServiceHandler(ctx, mux, conn) +} + +// RegisterSecurityServiceHandler registers the http handlers for service SecurityService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterSecurityServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterSecurityServiceHandlerClient(ctx, mux, NewSecurityServiceClient(conn)) +} + +// RegisterSecurityServiceHandlerClient registers the http handlers for service SecurityService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "SecurityServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "SecurityServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "SecurityServiceClient" to call the correct interceptors. +func RegisterSecurityServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client SecurityServiceClient) error { + + mux.Handle("POST", pattern_SecurityService_ListRoles_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/ListRoles", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/ListRoles")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SecurityService_ListRoles_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_ListRoles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_CreateRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/CreateRole", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/CreateRole")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SecurityService_CreateRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_CreateRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_GetRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/GetRole", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/GetRole")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SecurityService_GetRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_GetRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_DeleteRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/DeleteRole", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/DeleteRole")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SecurityService_DeleteRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_DeleteRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_ListRoleMembers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/ListRoleMembers", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/ListRoleMembers")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SecurityService_ListRoleMembers_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_ListRoleMembers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SecurityService_UpdateRoleMembership_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/redpanda.api.console.v1alpha1.SecurityService/UpdateRoleMembership", runtime.WithHTTPPathPattern("/redpanda.api.console.v1alpha1.SecurityService/UpdateRoleMembership")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SecurityService_UpdateRoleMembership_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SecurityService_UpdateRoleMembership_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_SecurityService_ListRoles_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"redpanda.api.console.v1alpha1.SecurityService", "ListRoles"}, "")) + + pattern_SecurityService_CreateRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"redpanda.api.console.v1alpha1.SecurityService", "CreateRole"}, "")) + + pattern_SecurityService_GetRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"redpanda.api.console.v1alpha1.SecurityService", "GetRole"}, "")) + + pattern_SecurityService_DeleteRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"redpanda.api.console.v1alpha1.SecurityService", "DeleteRole"}, "")) + + pattern_SecurityService_ListRoleMembers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"redpanda.api.console.v1alpha1.SecurityService", "ListRoleMembers"}, "")) + + pattern_SecurityService_UpdateRoleMembership_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"redpanda.api.console.v1alpha1.SecurityService", "UpdateRoleMembership"}, "")) +) + +var ( + forward_SecurityService_ListRoles_0 = runtime.ForwardResponseMessage + + forward_SecurityService_CreateRole_0 = runtime.ForwardResponseMessage + + forward_SecurityService_GetRole_0 = runtime.ForwardResponseMessage + + forward_SecurityService_DeleteRole_0 = runtime.ForwardResponseMessage + + forward_SecurityService_ListRoleMembers_0 = runtime.ForwardResponseMessage + + forward_SecurityService_UpdateRoleMembership_0 = runtime.ForwardResponseMessage +) diff --git a/backend/pkg/protogen/redpanda/api/console/v1alpha1/security_grpc.pb.go b/backend/pkg/protogen/redpanda/api/console/v1alpha1/security_grpc.pb.go new file mode 100644 index 000000000..3e4d4f121 --- /dev/null +++ b/backend/pkg/protogen/redpanda/api/console/v1alpha1/security_grpc.pb.go @@ -0,0 +1,313 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc (unknown) +// source: redpanda/api/console/v1alpha1/security.proto + +package consolev1alpha1 + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + SecurityService_ListRoles_FullMethodName = "/redpanda.api.console.v1alpha1.SecurityService/ListRoles" + SecurityService_CreateRole_FullMethodName = "/redpanda.api.console.v1alpha1.SecurityService/CreateRole" + SecurityService_GetRole_FullMethodName = "/redpanda.api.console.v1alpha1.SecurityService/GetRole" + SecurityService_DeleteRole_FullMethodName = "/redpanda.api.console.v1alpha1.SecurityService/DeleteRole" + SecurityService_ListRoleMembers_FullMethodName = "/redpanda.api.console.v1alpha1.SecurityService/ListRoleMembers" + SecurityService_UpdateRoleMembership_FullMethodName = "/redpanda.api.console.v1alpha1.SecurityService/UpdateRoleMembership" +) + +// SecurityServiceClient is the client API for SecurityService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type SecurityServiceClient interface { + // ListRoles lists all the roles based on optional filter. + ListRoles(ctx context.Context, in *ListRolesRequest, opts ...grpc.CallOption) (*ListRolesResponse, error) + CreateRole(ctx context.Context, in *CreateRoleRequest, opts ...grpc.CallOption) (*CreateRoleResponse, error) + // GetRole retrieves the specific role. + GetRole(ctx context.Context, in *GetRoleRequest, opts ...grpc.CallOption) (*GetRoleResponse, error) + // DeleteRole deletes the role from the system. + DeleteRole(ctx context.Context, in *DeleteRoleRequest, opts ...grpc.CallOption) (*DeleteRoleResponse, error) + // ListRoleMembership lists all the members assigned to a role based on optional filter. + ListRoleMembers(ctx context.Context, in *ListRoleMembersRequest, opts ...grpc.CallOption) (*ListRoleMembersResponse, error) + // UpdateRoleMembership updates role membership. + // Partially update role membership, adding or removing from a role + // ONLY those members listed in the “add” or “remove” fields, respectively. + // Adding a member that is already assigned to the role (or removing one that is not) is a no-op, + // and the rest of the members will be added and removed and reported. + UpdateRoleMembership(ctx context.Context, in *UpdateRoleMembershipRequest, opts ...grpc.CallOption) (*UpdateRoleMembershipResponse, error) +} + +type securityServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewSecurityServiceClient(cc grpc.ClientConnInterface) SecurityServiceClient { + return &securityServiceClient{cc} +} + +func (c *securityServiceClient) ListRoles(ctx context.Context, in *ListRolesRequest, opts ...grpc.CallOption) (*ListRolesResponse, error) { + out := new(ListRolesResponse) + err := c.cc.Invoke(ctx, SecurityService_ListRoles_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *securityServiceClient) CreateRole(ctx context.Context, in *CreateRoleRequest, opts ...grpc.CallOption) (*CreateRoleResponse, error) { + out := new(CreateRoleResponse) + err := c.cc.Invoke(ctx, SecurityService_CreateRole_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *securityServiceClient) GetRole(ctx context.Context, in *GetRoleRequest, opts ...grpc.CallOption) (*GetRoleResponse, error) { + out := new(GetRoleResponse) + err := c.cc.Invoke(ctx, SecurityService_GetRole_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *securityServiceClient) DeleteRole(ctx context.Context, in *DeleteRoleRequest, opts ...grpc.CallOption) (*DeleteRoleResponse, error) { + out := new(DeleteRoleResponse) + err := c.cc.Invoke(ctx, SecurityService_DeleteRole_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *securityServiceClient) ListRoleMembers(ctx context.Context, in *ListRoleMembersRequest, opts ...grpc.CallOption) (*ListRoleMembersResponse, error) { + out := new(ListRoleMembersResponse) + err := c.cc.Invoke(ctx, SecurityService_ListRoleMembers_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *securityServiceClient) UpdateRoleMembership(ctx context.Context, in *UpdateRoleMembershipRequest, opts ...grpc.CallOption) (*UpdateRoleMembershipResponse, error) { + out := new(UpdateRoleMembershipResponse) + err := c.cc.Invoke(ctx, SecurityService_UpdateRoleMembership_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SecurityServiceServer is the server API for SecurityService service. +// All implementations must embed UnimplementedSecurityServiceServer +// for forward compatibility +type SecurityServiceServer interface { + // ListRoles lists all the roles based on optional filter. + ListRoles(context.Context, *ListRolesRequest) (*ListRolesResponse, error) + CreateRole(context.Context, *CreateRoleRequest) (*CreateRoleResponse, error) + // GetRole retrieves the specific role. + GetRole(context.Context, *GetRoleRequest) (*GetRoleResponse, error) + // DeleteRole deletes the role from the system. + DeleteRole(context.Context, *DeleteRoleRequest) (*DeleteRoleResponse, error) + // ListRoleMembership lists all the members assigned to a role based on optional filter. + ListRoleMembers(context.Context, *ListRoleMembersRequest) (*ListRoleMembersResponse, error) + // UpdateRoleMembership updates role membership. + // Partially update role membership, adding or removing from a role + // ONLY those members listed in the “add” or “remove” fields, respectively. + // Adding a member that is already assigned to the role (or removing one that is not) is a no-op, + // and the rest of the members will be added and removed and reported. + UpdateRoleMembership(context.Context, *UpdateRoleMembershipRequest) (*UpdateRoleMembershipResponse, error) + mustEmbedUnimplementedSecurityServiceServer() +} + +// UnimplementedSecurityServiceServer must be embedded to have forward compatible implementations. +type UnimplementedSecurityServiceServer struct { +} + +func (UnimplementedSecurityServiceServer) ListRoles(context.Context, *ListRolesRequest) (*ListRolesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRoles not implemented") +} +func (UnimplementedSecurityServiceServer) CreateRole(context.Context, *CreateRoleRequest) (*CreateRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateRole not implemented") +} +func (UnimplementedSecurityServiceServer) GetRole(context.Context, *GetRoleRequest) (*GetRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRole not implemented") +} +func (UnimplementedSecurityServiceServer) DeleteRole(context.Context, *DeleteRoleRequest) (*DeleteRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteRole not implemented") +} +func (UnimplementedSecurityServiceServer) ListRoleMembers(context.Context, *ListRoleMembersRequest) (*ListRoleMembersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRoleMembers not implemented") +} +func (UnimplementedSecurityServiceServer) UpdateRoleMembership(context.Context, *UpdateRoleMembershipRequest) (*UpdateRoleMembershipResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateRoleMembership not implemented") +} +func (UnimplementedSecurityServiceServer) mustEmbedUnimplementedSecurityServiceServer() {} + +// UnsafeSecurityServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SecurityServiceServer will +// result in compilation errors. +type UnsafeSecurityServiceServer interface { + mustEmbedUnimplementedSecurityServiceServer() +} + +func RegisterSecurityServiceServer(s grpc.ServiceRegistrar, srv SecurityServiceServer) { + s.RegisterService(&SecurityService_ServiceDesc, srv) +} + +func _SecurityService_ListRoles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRolesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecurityServiceServer).ListRoles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecurityService_ListRoles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecurityServiceServer).ListRoles(ctx, req.(*ListRolesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SecurityService_CreateRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecurityServiceServer).CreateRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecurityService_CreateRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecurityServiceServer).CreateRole(ctx, req.(*CreateRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SecurityService_GetRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecurityServiceServer).GetRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecurityService_GetRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecurityServiceServer).GetRole(ctx, req.(*GetRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SecurityService_DeleteRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecurityServiceServer).DeleteRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecurityService_DeleteRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecurityServiceServer).DeleteRole(ctx, req.(*DeleteRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SecurityService_ListRoleMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoleMembersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecurityServiceServer).ListRoleMembers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecurityService_ListRoleMembers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecurityServiceServer).ListRoleMembers(ctx, req.(*ListRoleMembersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SecurityService_UpdateRoleMembership_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRoleMembershipRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecurityServiceServer).UpdateRoleMembership(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecurityService_UpdateRoleMembership_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecurityServiceServer).UpdateRoleMembership(ctx, req.(*UpdateRoleMembershipRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// SecurityService_ServiceDesc is the grpc.ServiceDesc for SecurityService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SecurityService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "redpanda.api.console.v1alpha1.SecurityService", + HandlerType: (*SecurityServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListRoles", + Handler: _SecurityService_ListRoles_Handler, + }, + { + MethodName: "CreateRole", + Handler: _SecurityService_CreateRole_Handler, + }, + { + MethodName: "GetRole", + Handler: _SecurityService_GetRole_Handler, + }, + { + MethodName: "DeleteRole", + Handler: _SecurityService_DeleteRole_Handler, + }, + { + MethodName: "ListRoleMembers", + Handler: _SecurityService_ListRoleMembers_Handler, + }, + { + MethodName: "UpdateRoleMembership", + Handler: _SecurityService_UpdateRoleMembership_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "redpanda/api/console/v1alpha1/security.proto", +} diff --git a/backend/pkg/redpanda/service.go b/backend/pkg/redpanda/service.go index f75c11889..8caf0c2d9 100644 --- a/backend/pkg/redpanda/service.go +++ b/backend/pkg/redpanda/service.go @@ -25,8 +25,9 @@ import ( // Service is the abstraction for communicating with a Redpanda cluster via the admin api. type Service struct { - adminClient *adminapi.AdminAPI - logger *zap.Logger + adminClient *adminapi.AdminAPI + logger *zap.Logger + clusterVersion string } // NewService creates a new redpanda.Service. It creates a Redpanda admin client based @@ -81,8 +82,9 @@ func NewService(cfg config.Redpanda, logger *zap.Logger) (*Service, error) { zap.String("cluster_version", clusterVersion)) return &Service{ - adminClient: adminClient, - logger: logger, + adminClient: adminClient, + logger: logger, + clusterVersion: clusterVersion, }, nil } @@ -249,3 +251,10 @@ func (s *Service) GetRole(ctx context.Context, roleName string) (adminapi.RoleDe func (s *Service) UpdateRoleMembership(ctx context.Context, roleName string, add, remove []adminapi.RoleMember, createRole bool) (adminapi.PatchRoleResponse, error) { return s.adminClient.UpdateRoleMembership(ctx, roleName, add, remove, createRole) } + +// ClusterVersion returns the Redpanda cluster version. +func (s *Service) ClusterVersion() string { + trimmed := strings.ReplaceAll(s.clusterVersion, "Redpanda", "") + trimmed = strings.ReplaceAll(trimmed, " ", "") + return strings.TrimLeft(trimmed, "v") +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6c14adf59..274ab2d76 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,7 +18,7 @@ "@monaco-editor/react": "^4.6", "@playwright/test": "^1.42.0", "@primer/octicons-react": "^17", - "@redpanda-data/ui": "^3.39.0", + "@redpanda-data/ui": "^3.45", "@textea/json-viewer": "^1.24.4", "array-move": "^4", "framer-motion": "^7", @@ -4795,9 +4795,9 @@ } }, "node_modules/@redpanda-data/ui": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/@redpanda-data/ui/-/ui-3.39.0.tgz", - "integrity": "sha512-oEjobSA9RHd+qLc/BFiFiBNNO5uGnxlyOYuwd8GBYM5b/jPSQVU9qUsnmA+vjNjtkG5rr2s+p065m8GAvj4ztQ==", + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/@redpanda-data/ui/-/ui-3.45.0.tgz", + "integrity": "sha512-IRdpmGnf+viyqwuQDJIVra0Cfdmq73pRfpFka2rsKiNYm5NZgEk0c+oRZLrAwwmmzQxbpYzU6vTFljCo4+U1oA==", "dependencies": { "@chakra-ui/icons": "^2.1.1", "@chakra-ui/react": "^2.8.1", @@ -26993,9 +26993,9 @@ "requires": {} }, "@redpanda-data/ui": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/@redpanda-data/ui/-/ui-3.39.0.tgz", - "integrity": "sha512-oEjobSA9RHd+qLc/BFiFiBNNO5uGnxlyOYuwd8GBYM5b/jPSQVU9qUsnmA+vjNjtkG5rr2s+p065m8GAvj4ztQ==", + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/@redpanda-data/ui/-/ui-3.45.0.tgz", + "integrity": "sha512-IRdpmGnf+viyqwuQDJIVra0Cfdmq73pRfpFka2rsKiNYm5NZgEk0c+oRZLrAwwmmzQxbpYzU6vTFljCo4+U1oA==", "requires": { "@chakra-ui/icons": "^2.1.1", "@chakra-ui/react": "^2.8.1", diff --git a/frontend/package.json b/frontend/package.json index aa3d0bb68..7d121385e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "@monaco-editor/react": "^4.6", "@playwright/test": "^1.42.0", "@primer/octicons-react": "^17", - "@redpanda-data/ui": "^3.39.0", + "@redpanda-data/ui": "^3.45", "@textea/json-viewer": "^1.24.4", "array-move": "^4", "framer-motion": "^7", diff --git a/frontend/src/components/pages/acls/Acl.List.tsx b/frontend/src/components/pages/acls/Acl.List.tsx index c4c71c0d5..0dbcbe65d 100644 --- a/frontend/src/components/pages/acls/Acl.List.tsx +++ b/frontend/src/components/pages/acls/Acl.List.tsx @@ -11,23 +11,30 @@ import { observer } from 'mobx-react'; import { PageComponent, PageInitHelper } from '../Page'; -import { api } from '../../../state/backendApi'; +import { api, rolesApi } from '../../../state/backendApi'; import { uiSettings } from '../../../state/ui'; import { AclRequestDefault } from '../../../state/restInterfaces'; -import { comparer, computed, makeObservable, observable } from 'mobx'; +import { makeObservable, observable } from 'mobx'; import { appGlobal } from '../../../state/appGlobal'; import { Code, DefaultSkeleton } from '../../../utils/tsxUtils'; import { clone, toJson } from '../../../utils/jsonUtils'; import { QuestionIcon } from '@primer/octicons-react'; -import { TrashIcon } from '@heroicons/react/outline'; -import { AclFlat, AclPrincipalGroup, collectClusterAcls, collectConsumerGroupAcls, collectTopicAcls, collectTransactionalIdAcls, createEmptyClusterAcl, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl } from './Models'; +import { TrashIcon, PencilIcon } from '@heroicons/react/outline'; +import { AclPrincipalGroup, createEmptyClusterAcl, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, principalGroupsView } from './Models'; import { AclPrincipalGroupEditor } from './PrincipalGroupEditor'; import Section from '../../misc/Section'; import PageContent from '../../misc/PageContent'; import { Features } from '../../../state/supportedFeatures'; -import { Alert, AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertIcon, Badge, Button, createStandaloneToast, DataTable, Flex, Icon, Menu, MenuButton, MenuItem, MenuList, redpandaTheme, redpandaToastOptions, Result, SearchField, Text, Tooltip } from '@redpanda-data/ui'; -import React, { FC, useRef } from 'react'; -import { openCreateUserModal } from './CreateServiceAccountModal'; +import { Alert, AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertIcon, Badge, Box, Button, createStandaloneToast, DataTable, Flex, Icon, Menu, MenuButton, MenuItem, MenuList, redpandaTheme, redpandaToastOptions, Result, SearchField, Tabs, Text, Tooltip } from '@redpanda-data/ui'; +import { FC, useRef, useState } from 'react'; +import { TabsItemProps } from '@redpanda-data/ui/dist/components/Tabs/Tabs'; +import { Link as ReactRouterLink } from 'react-router-dom' +import { Link as ChakraLink, Tag } from '@chakra-ui/react' +import { DeleteRoleConfirmModal } from './DeleteRoleConfirmModal'; +import { DeleteUserConfirmModal } from './DeleteUserConfirmModal'; + +import { UserPermissionAssignments } from './UserPermissionAssignments'; + // TODO - once AclList is migrated to FC, we could should move this code to use useToast() const { ToastContainer, toast } = createStandaloneToast({ @@ -39,10 +46,11 @@ const { ToastContainer, toast } = createStandaloneToast({ } }) +export type AclListTab = 'users' | 'roles' | 'acls'; + @observer -class AclList extends PageComponent { +class AclList extends PageComponent<{ tab: AclListTab }> { editorType: 'create' | 'edit' = 'create'; - @observable aclFailed: { err: unknown } | null = null; @observable edittingPrincipalGroup?: AclPrincipalGroup; constructor(p: any) { @@ -51,8 +59,8 @@ class AclList extends PageComponent { } initPage(p: PageInitHelper): void { - p.title = 'Kafka Access Control'; - p.addBreadcrumb('Kafka Access Control', '/acls'); + p.title = 'Access control'; + p.addBreadcrumb('Access control', '/security'); this.refreshData(true); appGlobal.onRefresh = () => this.refreshData(true); @@ -63,8 +71,11 @@ class AclList extends PageComponent { await Promise.allSettled([ api.refreshAcls(AclRequestDefault, force), - api.refreshServiceAccounts(true) + api.refreshServiceAccounts(true), + rolesApi.refreshRoles() ]); + + await rolesApi.refreshRoleMembers(); // must be after refreshRoles is completed, otherwise the function couldn't know the names of the roles to refresh } render() { @@ -87,325 +98,479 @@ class AclList extends PageComponent { : null; - let groups = this.principalGroups - try { - const quickSearchRegExp = new RegExp(uiSettings.aclList.configTable.quickSearch, 'i') - groups = groups.filter( - aclGroup => aclGroup.principalName.match(quickSearchRegExp) - ); - } catch (e) { - console.warn('Invalid expression') + const tabs = [ + { key: 'principals' as AclListTab, name: 'Principals', component: }, + { key: 'roles' as AclListTab, name: 'Roles', component: , isDisabled: Features.rolesApi ? false : 'Not supported in this cluster' }, + { key: 'acls' as AclListTab, name: 'ACLs', component: }, + ] as TabsItemProps[]; + + // todo: maybe there is a better way to sync the tab control to the path + const activeTab = tabs.findIndex(x => x.key == this.props.tab); + if (activeTab == -1) { + // No tab selected, default to users + appGlobal.history.push('/security/users'); } return <> - { - this.aclFailed = null - }}/> + + {warning} + {noAclAuthorizer} + + = 0 ? activeTab : 0} + items={tabs} + onChange={(_, key) => { + appGlobal.history.push(`/security/${key}`); + }} + /> + + + } +} + +export default AclList; - {this.edittingPrincipalGroup && - { - this.edittingPrincipalGroup = undefined; - this.refreshData(true); - }} - />} - -
- - - {warning} - {noAclAuthorizer} - - - data={groups} - pagination - sorting - columns={[ - { - size: Infinity, - header: 'Principal', - accessorKey: 'principal', - cell: ({row: {original: record}}) => { - const userExists = api.serviceAccounts?.users.includes(record.principalName); - const isComplete = api.serviceAccounts?.isComplete === true; - const showWarning = isComplete && !userExists && !record.principalName.includes('*'); - const principalType = record.principalType == 'User' && record.principalName.endsWith('*') - ? 'User Group' - : record.principalType; - return ( - + + + + data={users} + pagination + sorting + emptyText="No principals yet" + emptyAction={ + + } + columns={[ + { + id: 'name', + size: Infinity, + header: 'Principal', + cell: (ctx) => { + const entry = ctx.row.original; + return <> + + {entry.name} + {entry.type == 'SERVICE_ACCOUNT' && Redpanda user} + + + } + }, + { + id: 'assignedRoles', + header: 'Assigned roles', + cell: (ctx) => { + const entry = ctx.row.original; + return + } + }, + { + size: 60, + id: 'menu', + header: '', + cell: (ctx) => { + const entry = ctx.row.original; + return ( + + {Features.rolesApi && + - ); - }, - }, - { - header: 'Host', - accessorKey: 'host', - cell: ({row: {original: {host}}}) => (!host || host == '*') ? Any : host - }, - { - size: 60, - id: 'menu', - header: '', - cell: ({row: {original: record}}) => { - const userExists = api.serviceAccounts?.users.includes(record.principalName); - const hasAcls = record.sourceEntries.length > 0; - - const onDelete = async (user: boolean, acls: boolean) => { - if (acls) { - try { - await api.deleteACLs({ - resourceType: 'Any', - resourceName: undefined, - resourcePatternType: 'Any', - principal: record.principalType + ':' + record.principalName, - host: record.host, - operation: 'Any', - permissionType: 'Any', - }); - toast({ - status: 'success', - description: Deleted ACLs for {record.principalName} - }); - } catch (err: unknown) { - console.error('failed to delete acls', { error: err }); - - this.aclFailed = { - err, - } - } - } - - if (user) { - try { - await api.deleteServiceAccount(record.principalName); - toast({ - status: 'success', - description: Deleted user {record.principalName} - }); - } catch (err: unknown) { - console.error('failed to delete acls', { error: err }); - - this.aclFailed = { - err, + } + {entry.type == 'SERVICE_ACCOUNT' && + { + await api.deleteServiceAccount(entry.name); + + // Remove user from all its roles + const promises = []; + for (const [roleName, members] of rolesApi.roleMembers) { + if (members.any(m => m.name == entry.name)) { // is this user part of this role? + // then remove it + promises.push(rolesApi.updateRoleMembership(roleName, [], [entry.name])); } } + + await Promise.allSettled(promises); + await rolesApi.refreshRoleMembers(); + await api.refreshServiceAccounts(true); + }} + buttonEl={ + } - - await this.refreshData(true); + userName={entry.name} + /> } + + ); + } + }, + ]} + /> +
+ +}); + +const RolesTab = observer(() => { + + const roles = (rolesApi.roles ?? []) + .filter(u => { + const filter = uiSettings.aclList.rolesTab.quickSearch; + if (!filter) return true; + try { + const quickSearchRegExp = new RegExp(filter, 'i'); + return u.match(quickSearchRegExp); + } catch { return false; } + }); + const _isLoading = rolesApi.roles == null; + const rolesWithMembers = roles.map(r => { + const members = rolesApi.roleMembers.get(r) ?? []; + return { name: r, members }; + }); - return - - - - - { - void onDelete(true, true); - e.stopPropagation() - }} - > - Delete (User and ACLs) - - { - void onDelete(true, false); - e.stopPropagation() - }} - > - Delete (User only) - - { - void onDelete(false, true); - e.stopPropagation() - }} - > - Delete (ACLs only) - - - - } - }, - ]} - /> - - - - } - - @computed({ equals: comparer.structural }) get flatAcls() { - const acls = api.ACLs; - if (!acls || !acls.aclResources || acls.aclResources.length == 0) - return []; - - const flattened: AclFlat[] = []; - for (const res of acls.aclResources) { - for (const rule of res.acls) { - - const flattenedEntry: AclFlat = { - resourceType: res.resourceType, - resourceName: res.resourceName, - resourcePatternType: res.resourcePatternType, - - principal: rule.principal, - host: rule.host, - operation: rule.operation, - permissionType: rule.permissionType - }; + return + + Roles are groups of ACLs abstracted under a single name. Roles can be assigned to users. + - flattened.push(flattenedEntry); - } - } + (uiSettings.aclList.rolesTab.quickSearch = x)} + placeholderText="Filter by name" + /> - return flattened; +
+ + + { + const entry = ctx.row.original; + return <> + + {entry.name} + + + } + }, + { + id: 'assignedPrincipals', + header: 'Assigned principals', + cell: (ctx) => { + return <>{ctx.row.original.members.length} + } + }, + { + size: 60, + id: 'menu', + header: '', + cell: (ctx) => { + const entry = ctx.row.original; + return ( + + + { + await rolesApi.deleteRole(entry.name, true); + await rolesApi.refreshRoles(); + await rolesApi.refreshRoleMembers(); + }} + buttonEl={ + + } + roleName={entry.name} + /> + + ); + } + }, + ]} + /> +
+
+}); + +const AclsTab = observer((p: { + principalGroups: AclPrincipalGroup[] +}) => { + + const [aclFailed, setAclFailed] = useState<{ err: unknown } | null>(null); + const [editorType, setEditorType] = useState<'create' | 'edit'>('create'); + const [edittingPrincipalGroup, setEdittingPrincipalGroup] = useState(null); + + let groups = p.principalGroups.filter(g => g.principalType == 'User'); + try { + const quickSearchRegExp = new RegExp(uiSettings.aclList.configTable.quickSearch, 'i') + groups = groups.filter( + aclGroup => aclGroup.principalName.match(quickSearchRegExp) + ); + } catch (e) { + console.warn('Invalid expression') } - @computed({ equals: comparer.structural }) get principalGroups(): AclPrincipalGroup[] { - const flat = this.flatAcls; + return + + Access-control lists (ACLs) are the primary mechanism used by Redpanda to manage user permissions. + ACLs are assigned to principals, which then access resources within Redpanda. + - const g = flat.groupInto(f => { - const groupingKey = (f.principal ?? 'Any') + ' ' + (f.host ?? 'Any'); - return groupingKey; - }); + (uiSettings.aclList.configTable.quickSearch = x)} + placeholderText="Filter by name" + /> - const result: AclPrincipalGroup[] = []; - - for (const { items } of g) { - const { principal, host } = items[0]; - - let principalType: string; - let principalName: string; - if (principal.includes(':')) { - const split = principal.split(':', 2); - principalType = split[0]; - principalName = split[1]; - } else { - principalType = 'User'; - principalName = principal; - } - - const principalGroup: AclPrincipalGroup = { - principalType, - principalName, - host, - - topicAcls: collectTopicAcls(items), - consumerGroupAcls: collectConsumerGroupAcls(items), - clusterAcls: collectClusterAcls(items), - transactionalIdAcls: collectTransactionalIdAcls(items), - - sourceEntries: items, - }; - result.push(principalGroup); - } +
+ {edittingPrincipalGroup && + { + setEdittingPrincipalGroup(null); + api.refreshAcls(AclRequestDefault, true); + api.refreshServiceAccounts(true); + }} + />} - // Add service accounts that exist but have no associated acl rules - const serviceAccounts = api.serviceAccounts?.users; - if (serviceAccounts) { - for (const acc of serviceAccounts) { - if (!result.any(g => g.principalName == acc)) { - // Doesn't have a group yet, create one - result.push({ - principalType: 'User', + setAclFailed(null)} /> + + + + + data={groups} + pagination + sorting + columns={[ + { + size: Infinity, + header: 'Principal', + accessorKey: 'principal', + cell: ({ row: { original: record } }) => { + const userExists = api.serviceAccounts?.users.includes(record.principalName); + const isComplete = api.serviceAccounts?.isComplete === true; + const showWarning = isComplete && !userExists && !record.principalName.includes('*'); + const principalType = record.principalType == 'User' && record.principalName.endsWith('*') + ? 'User Group' + : record.principalType; + return ( + + ); + }, + }, + { + header: 'Host', + accessorKey: 'host', + cell: ({ row: { original: { host } } }) => (!host || host == '*') ? Any : host + }, + { + size: 60, + id: 'menu', + header: '', + cell: ({ row: { original: record } }) => { + const userExists = api.serviceAccounts?.users.includes(record.principalName); + const hasAcls = record.sourceEntries.length > 0; + + const onDelete = async (user: boolean, acls: boolean) => { + if (acls) { + try { + await api.deleteACLs({ + resourceType: 'Any', + resourceName: undefined, + resourcePatternType: 'Any', + principal: record.principalType + ':' + record.principalName, + host: record.host, + operation: 'Any', + permissionType: 'Any', + }); + toast({ + status: 'success', + description: Deleted ACLs for {record.principalName} + }); + } catch (err: unknown) { + console.error('failed to delete acls', { error: err }); + setAclFailed({ err }); + } + } - return result; - } + if (user) { + try { + await api.deleteServiceAccount(record.principalName); + toast({ + status: 'success', + description: Deleted user {record.principalName} + }); + } catch (err: unknown) { + console.error('failed to delete acls', { error: err }); + setAclFailed({ err }); + } + } - SearchControls = observer(() => { - return ( -
- (uiSettings.aclList.configTable.quickSearch = x)} - placeholderText="Enter search term/regex" - /> + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, true), + api.refreshServiceAccounts(true) + ]); + } + + + return + + + + + { + void onDelete(true, true); + e.stopPropagation() + }} + > + Delete (User and ACLs) + + { + void onDelete(true, false); + e.stopPropagation() + }} + > + Delete (User only) + + { + void onDelete(false, true); + e.stopPropagation() + }} + > + Delete (ACLs only) + + + + } + }, + ]} + /> +
- +
- - - - - - - ); - }); -} -export default AclList; +}); + -// function isRowMatch(entry: AclPrincipalGroup, regex: RegExp): boolean { -// if (regex.test(entry.host)) return true; -// if (regex.test(entry.principalName)) return true; -// -// for (const e of entry.sourceEntries) { -// if (regex.test(e.operation)) return true; -// if (regex.test(e.resourceType)) return true; -// if (regex.test(e.resourceName)) return true; -// } -// -// return false; -// } - -const AlertDeleteFailed: FC<{ aclFailed: { err: unknown } | null, onClose: () => void }> = ({aclFailed, onClose}) => { +const AlertDeleteFailed: FC<{ aclFailed: { err: unknown } | null, onClose: () => void }> = ({ aclFailed, onClose }) => { const ref = useRef(null) return ( @@ -435,7 +600,7 @@ const PermissionDenied = <> status={403} userMessage={ You are not allowed to view this page. -
+
Contact the administrator if you think this is an error.
} diff --git a/frontend/src/components/pages/acls/CreateServiceAccountEditor.tsx b/frontend/src/components/pages/acls/CreateServiceAccountEditor.tsx deleted file mode 100644 index 3afc4e02a..000000000 --- a/frontend/src/components/pages/acls/CreateServiceAccountEditor.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { ReloadOutlined } from '@ant-design/icons'; -import { Button, Input, Flex, Checkbox, FormField, CopyButton } from '@redpanda-data/ui'; -import { observer } from 'mobx-react'; -import { useState } from 'react'; -import { CreateUserRequest } from '../../../state/restInterfaces'; -import { Tooltip, PasswordInput } from '@redpanda-data/ui'; -import { SingleSelect } from '../../misc/Select'; - -export const CreateServiceAccountEditor = observer((p: { state: CreateUserRequest }) => { - const state = p.state; - const [allowSpecialChars, setAllowSpecialChars] = useState(false); - - return ( -
-
- - - state.username = v.target.value} - width="100%" - autoFocus - spellCheck={false} - placeholder="Username" - autoComplete="off" - /> - - - - - - - (state.password = e.target.value)} - isInvalid={state.password.length <= 3 || state.password.length > 64} - /> - - - - - - - - - {setAllowSpecialChars(e.target.checked); state.password = generatePassword(30, e.target.checked)}}>Generate with special characters - - - - - - options={[{ - value: 'SCRAM-SHA-256', - label: 'SCRAM-SHA-256', - }, { - value: 'SCRAM-SHA-512', - label: 'SCRAM-SHA-512', - }]} value={state.mechanism} onChange={e => { - state.mechanism = e; - }}/> - - -
-
- ); -}); - -export function generatePassword(length: number, allowSpecialChars: boolean): string { - if (length <= 0) return ''; - - const lowercase = 'abcdefghijklmnopqrstuvwxyz' - const uppercase = lowercase.toUpperCase(); - const numbers = '0123456789'; - const special = '.,&_+|[]/-()'; - - let alphabet = lowercase + uppercase + numbers; - if (allowSpecialChars) { - alphabet += special; - } - - const randomValues = new Uint32Array(length); - crypto.getRandomValues(randomValues); - - let result = ''; - for (const n of randomValues) { - const index = n % alphabet.length; - const sym = alphabet[index]; - - result += sym; - } - - return result; -} diff --git a/frontend/src/components/pages/acls/CreateServiceAccountModal.tsx b/frontend/src/components/pages/acls/CreateServiceAccountModal.tsx deleted file mode 100644 index 43f7c173d..000000000 --- a/frontend/src/components/pages/acls/CreateServiceAccountModal.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import { - Box, - Button, - Checkbox, - CopyButton, - Flex, - FormField, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - PasswordInput, - Tooltip, - Text, - createStandaloneToast, - redpandaTheme, - redpandaToastOptions, - Grid, - Alert, - AlertIcon, -} from '@redpanda-data/ui'; -import { generatePassword } from './CreateServiceAccountEditor'; -import { AclRequestDefault, CreateUserRequest } from '../../../state/restInterfaces'; -import { openModal } from '../../../utils/ModalContainer'; -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { ReloadOutlined } from '@ant-design/icons'; -import { SingleSelect } from '../../misc/Select'; -import { api } from '../../../state/backendApi'; -import { CheckCircleIcon } from '@chakra-ui/icons'; - -const { ToastContainer, toast } = createStandaloneToast({ - theme: redpandaTheme, - defaultOptions: { - ...redpandaToastOptions.defaultOptions, - isClosable: false, - duration: 2000, - }, -}); - -export type CreateUserModalState = CreateUserRequest & { - generateWithSpecialChars: boolean; - step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION'; - isCreating: boolean; - isValidUsername: boolean; - isValidPassword: boolean; -}; - -const CreateUserRootModal = observer( - (p: { - state: CreateUserModalState; - onCreateUser: (state: CreateUserModalState) => Promise; - closeModal: () => void; - }) => { - return ( - - {p.state.step === 'CREATE_USER' ? : } - - ); - } -); - -const CreateUserModal = observer( - (p: { - state: CreateUserModalState; - onCreateUser: (state: CreateUserModalState) => Promise; - closeModal: () => void; - }) => { - const isValidUsername = /^[a-zA-Z0-9._@-]+$/.test(p.state.username); - const isValidPassword = p.state.password && p.state.password.length >= 4 && p.state.password.length <= 64; - - return ( - <> - - - - Create user - - - - - (p.state.username = v.target.value)} - width="100%" - autoFocus - spellCheck={false} - placeholder="Username" - autoComplete="off" - /> - - - - - - (p.state.password = e.target.value)} - isInvalid={!isValidPassword} - /> - - - - - - - - - { - p.state.generateWithSpecialChars = e.target.checked; - p.state.password = generatePassword(30, e.target.checked); - }} - > - Generate with special characters - - - - - - - options={[ - { - value: 'SCRAM-SHA-256', - label: 'SCRAM-SHA-256', - }, - { - value: 'SCRAM-SHA-512', - label: 'SCRAM-SHA-512', - }, - ]} - value={p.state.mechanism} - onChange={(e) => { - p.state.mechanism = e; - }} - /> - - - - - - - - - - - ); - } -); - -const CreateUserConfirmationModal = observer((p: { state: CreateUserModalState; closeModal: () => void }) => { - return ( - <> - - - - - - - User created - - - - - - - Username - - - - - {p.state.username} - - - - - - - - - - Password - - - - - - - - - - - - - Mechanism - - - - {p.state.mechanism} - - - - - - - Password will be inaccessible after this modal is closed. - - - - - - - - - ); -}); - -export function openCreateUserModal() { - const state = observable({ - username: '', - password: generatePassword(30, false), - mechanism: 'SCRAM-SHA-256', - generateWithSpecialChars: false, - step: 'CREATE_USER', - isCreating: false, - isValidUsername: false, - isValidPassword: false, - } as CreateUserModalState); - - const onCreateUser = async (state: CreateUserModalState): Promise => { - try { - state.isCreating = true; - await api.createServiceAccount({ - username: state.username, - password: state.password, - mechanism: state.mechanism, - }); - - // Refresh user list - if (api.userData != null && !api.userData.canListAcls) return false; - await Promise.allSettled([api.refreshAcls(AclRequestDefault, true), api.refreshServiceAccounts(true)]); - state.step = 'CREATE_USER_CONFIRMATION'; - } catch (err) { - toast({ - status: 'error', - duration: null, - isClosable: true, - title: 'Failed to create user', - description: String(err), - }); - } finally { - state.isCreating = false; - } - return true; - }; - - openModal(CreateUserRootModal, { - state: state, - onCreateUser: onCreateUser, - }); -} diff --git a/frontend/src/components/pages/acls/DeleteRoleConfirmModal.tsx b/frontend/src/components/pages/acls/DeleteRoleConfirmModal.tsx new file mode 100644 index 000000000..c4ed3625e --- /dev/null +++ b/frontend/src/components/pages/acls/DeleteRoleConfirmModal.tsx @@ -0,0 +1,28 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { FC } from 'react'; +import { Text, ConfirmItemDeleteModal } from '@redpanda-data/ui'; + + +export const DeleteRoleConfirmModal: FC<{ + roleName: string; + numberOfPrincipals: number; + onConfirm: () => void; + buttonEl: React.ReactElement +}> = ({ roleName, numberOfPrincipals, onConfirm, buttonEl }) => { + return ( + + This role is assigned to {numberOfPrincipals} {numberOfPrincipals === 1 ? 'principal' : 'principals'}. Deleting it will remove it from these principals and take those permissions away. The ACLs will all be deleted. + To restore the permissions, the role will need to be recreated and reassigned to these principals. To confirm, type the role name in the confirmation box below. + + ) +} diff --git a/frontend/src/components/pages/acls/DeleteUserConfirmModal.tsx b/frontend/src/components/pages/acls/DeleteUserConfirmModal.tsx new file mode 100644 index 000000000..bdca71ed0 --- /dev/null +++ b/frontend/src/components/pages/acls/DeleteUserConfirmModal.tsx @@ -0,0 +1,26 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { FC } from 'react'; +import { Text, ConfirmItemDeleteModal } from '@redpanda-data/ui'; + + +export const DeleteUserConfirmModal: FC<{ + userName: string; + onConfirm: () => void; + buttonEl: React.ReactElement +}> = ({ userName, onConfirm, buttonEl }) => { + return ( + + This user has roles and ACLs assigned to it. Those roles and ACLs will not be deleted, but the user will need to be recreated and reassigned to them to be used again. To confirm, type the user name in the box below. + + ) +} diff --git a/frontend/src/components/pages/acls/Models.ts b/frontend/src/components/pages/acls/Models.ts index b09f5608f..31b2f7c65 100644 --- a/frontend/src/components/pages/acls/Models.ts +++ b/frontend/src/components/pages/acls/Models.ts @@ -9,8 +9,11 @@ * by the Apache License, Version 2.0 */ +import { comparer, observable } from 'mobx'; +import { api } from '../../../state/backendApi'; import { AclStrOperation, AclStrPermission, AclStrResourcePatternType, AclStrResourceType } from '../../../state/restInterfaces'; +export type PrincipalType = 'User' | 'RedpandaRole'; export type AclFlat = { // AclResource resourceType: AclStrResourceType; @@ -25,8 +28,9 @@ export type AclFlat = { } export type AclPrincipalGroup = { - principalType: string; - principalName: string; + principalType: PrincipalType; + // This can only ever be a literal, or match anything (star in that case). No prefix or postfix matching + principalName: string | '*'; host: string; @@ -146,7 +150,7 @@ export function createEmptyTransactionalIdAcl(): TransactionalIdACLs { } -export function collectTopicAcls(acls: AclFlat[]): TopicACLs[] { +function collectTopicAcls(acls: AclFlat[]): TopicACLs[] { const topics = acls .filter(x => x.resourceType == 'Topic') .groupInto(x => `${x.resourcePatternType}: ${x.resourceName}`); @@ -209,7 +213,7 @@ export function collectTopicAcls(acls: AclFlat[]): TopicACLs[] { return topicAcls; }; -export function collectConsumerGroupAcls(acls: AclFlat[]): ConsumerGroupACLs[] { +function collectConsumerGroupAcls(acls: AclFlat[]): ConsumerGroupACLs[] { const consumerGroups = acls .filter(x => x.resourceType == 'Group') .groupInto(x => `${x.resourcePatternType}: ${x.resourceName}`); @@ -262,7 +266,7 @@ export function collectConsumerGroupAcls(acls: AclFlat[]): ConsumerGroupACLs[] { return consumerGroupAcls; }; -export function collectClusterAcls(acls: AclFlat[]): ClusterACLs { +function collectClusterAcls(acls: AclFlat[]): ClusterACLs { const flatClusterAcls = acls.filter(x => x.resourceType == 'Cluster'); const clusterOperations = [ @@ -306,7 +310,7 @@ export function collectClusterAcls(acls: AclFlat[]): ClusterACLs { return clusterAcls; }; -export function collectTransactionalIdAcls(acls: AclFlat[]): TransactionalIdACLs[] { +function collectTransactionalIdAcls(acls: AclFlat[]): TransactionalIdACLs[] { const transactionalIds = acls .filter(x => x.resourceType == 'TransactionalID') .groupInto(x => `${x.resourcePatternType}: ${x.resourceName}`); @@ -358,6 +362,106 @@ export function collectTransactionalIdAcls(acls: AclFlat[]): TransactionalIdACLs }; +export const principalGroupsView = observable({ + + get flatAcls() { + const acls = api.ACLs; + if (!acls || !acls.aclResources || acls.aclResources.length == 0) + return []; + + const flattened: AclFlat[] = []; + for (const res of acls.aclResources) { + for (const rule of res.acls) { + + const flattenedEntry: AclFlat = { + resourceType: res.resourceType, + resourceName: res.resourceName, + resourcePatternType: res.resourcePatternType, + + principal: rule.principal, + host: rule.host, + operation: rule.operation, + permissionType: rule.permissionType + }; + + flattened.push(flattenedEntry); + } + } + + return observable(flattened); + }, + + get principalGroups(): AclPrincipalGroup[] { + const flat = this.flatAcls; + + const g = flat.groupInto(f => { + const groupingKey = (f.principal ?? 'Any') + ' ' + (f.host ?? 'Any'); + return groupingKey; + }); + + const result: AclPrincipalGroup[] = []; + + for (const { items } of g) { + const { principal, host } = items[0]; + + let principalType: PrincipalType; + let principalName: string; + if (principal.includes(':')) { + const split = principal.split(':', 2); + principalType = split[0] as PrincipalType; + principalName = split[1]; + } else { + principalType = 'User'; + principalName = principal; + } + + const principalGroup: AclPrincipalGroup = { + principalType, + principalName, + host, + + topicAcls: collectTopicAcls(items), + consumerGroupAcls: collectConsumerGroupAcls(items), + clusterAcls: collectClusterAcls(items), + transactionalIdAcls: collectTransactionalIdAcls(items), + + sourceEntries: items, + }; + result.push(principalGroup); + } + + // Add service accounts that exist but have no associated acl rules + const serviceAccounts = api.serviceAccounts?.users; + if (serviceAccounts) { + for (const acc of serviceAccounts) { + if (!result.any(g => g.principalName == acc)) { + // Doesn't have a group yet, create one + result.push({ + principalType: 'User', + host: '', + principalName: acc, + topicAcls: [createEmptyTopicAcl()], + consumerGroupAcls: [createEmptyConsumerGroupAcl()], + transactionalIdAcls: [createEmptyTransactionalIdAcl()], + clusterAcls: createEmptyClusterAcl(), + sourceEntries: [], + }); + } + } + } + + return observable(result); + } +}, undefined, { + equals: comparer.structural +}); + + +/* + Sooner or later you want to go back from an 'AclPrincipalGroup' to flat ACLs. + Why? Because you'll need to call the remove/create acl apis and those only work with flat acls. + Use this method to convert your principal group back to a list of flat acls. +*/ export function unpackPrincipalGroup(group: AclPrincipalGroup): AclFlat[] { const flat: AclFlat[] = []; diff --git a/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx b/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx index 945542b80..739eeb6a4 100644 --- a/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx +++ b/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx @@ -16,7 +16,7 @@ import { AclOperation, AclStrOperation, AclStrResourceType } from '../../../stat import { AnimatePresence, animProps_radioOptionGroup, MotionDiv } from '../../../utils/animationProps'; import { Code, Label, LabelTooltip } from '../../../utils/tsxUtils'; import { HiOutlineTrash } from 'react-icons/hi'; -import { AclPrincipalGroup, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, ResourceACLs, unpackPrincipalGroup } from './Models'; +import { AclPrincipalGroup, createEmptyClusterAcl, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, PrincipalType, ResourceACLs, unpackPrincipalGroup } from './Models'; import { Operation } from './Operation'; import { Box, Button, Flex, Grid, HStack, Icon, Input, InputGroup, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text, useToast, VStack } from '@redpanda-data/ui'; import { SingleSelect } from '../../misc/Select'; @@ -105,6 +105,9 @@ export const AclPrincipalGroupEditor = observer((p: { p.onClose(); } + if (!group.clusterAcls) + group.clusterAcls = createEmptyClusterAcl(); + return ( {}}> @@ -135,22 +138,17 @@ export const AclPrincipalGroupEditor = observer((p: { } > - - + + + isDisabled value={group.principalType} options={[ { label: 'User', value: 'User', - }, - // { - // label: 'Group', - // value: 'Group' - // } + } ]} - onChange={(value) => { - group.principalType = value - }} + onChange={(value) => group.principalType = value} /> @@ -241,7 +239,7 @@ export const AclPrincipalGroupEditor = observer((p: { Cluster - + @@ -255,12 +253,16 @@ export const AclPrincipalGroupEditor = observer((p: { ) }); -const ResourceACLsEditor = observer((p: { +export const ResourceACLsEditor = observer((p: { resource: ResourceACLs, resourceType: AclStrResourceType, onDelete?: () => void }) => { const res = p.resource; + if (!res) { + // Happens for clusterAcls? + return null; + } const isCluster = !('selector' in res); const isAllSet = res.all == 'Allow' || res.all == 'Deny'; @@ -272,59 +274,74 @@ const ResourceACLsEditor = observer((p: { return ( - {isCluster ? ( - Applies to whole cluster - ) : ( - - )} + + {isCluster ? ( + Applies to whole cluster + ) : ( + + )} - + {p.onDelete && ( - - - + )} ); diff --git a/frontend/src/components/pages/acls/RoleCreate.tsx b/frontend/src/components/pages/acls/RoleCreate.tsx new file mode 100644 index 000000000..235be5377 --- /dev/null +++ b/frontend/src/components/pages/acls/RoleCreate.tsx @@ -0,0 +1,59 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { PageComponent, PageInitHelper } from '../Page'; +import { api, rolesApi } from '../../../state/backendApi'; +import { AclRequestDefault } from '../../../state/restInterfaces'; +import { appGlobal } from '../../../state/appGlobal'; +import { DefaultSkeleton } from '../../../utils/tsxUtils'; +import PageContent from '../../misc/PageContent'; +import { RoleForm } from './RoleForm'; +import { Text } from '@redpanda-data/ui'; +import { observer } from 'mobx-react'; + +@observer +class RoleCreatePage extends PageComponent { + + initPage(p: PageInitHelper): void { + p.title = 'Create role'; + p.addBreadcrumb('Access control', '/security'); + p.addBreadcrumb('Roles', '/security/roles'); + p.addBreadcrumb('Create role', '/security/roles/create'); + + this.refreshData(true); + appGlobal.onRefresh = () => this.refreshData(true); + } + + async refreshData(force: boolean) { + if (api.userData != null && !api.userData.canListAcls) return; + + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, force), + api.refreshServiceAccounts(true), + rolesApi.refreshRoles() + ]); + } + + render() { + if (api.ACLs?.aclResources === undefined) return DefaultSkeleton; + if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton; + + return ( + + A role is a named collection of ACLs which may have users (security principals) assigned to it. You can assign any number of roles to a given user. + + + ); + } +} + +export default RoleCreatePage; + diff --git a/frontend/src/components/pages/acls/RoleDetails.tsx b/frontend/src/components/pages/acls/RoleDetails.tsx new file mode 100644 index 000000000..370e8944a --- /dev/null +++ b/frontend/src/components/pages/acls/RoleDetails.tsx @@ -0,0 +1,160 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { observer } from 'mobx-react'; +import { PageComponent, PageInitHelper } from '../Page'; +import { api, RolePrincipal, rolesApi } from '../../../state/backendApi'; +import { AclRequestDefault } from '../../../state/restInterfaces'; +import { makeObservable, observable } from 'mobx'; +import { appGlobal } from '../../../state/appGlobal'; +import { DefaultSkeleton } from '../../../utils/tsxUtils'; +import PageContent from '../../misc/PageContent'; +import { Button, DataTable, Flex, Heading, SearchField, Text } from '@redpanda-data/ui'; +import { principalGroupsView } from './Models'; +import { AclPrincipalGroupPermissionsTable } from './UserDetails'; + +import { Link as ReactRouterLink } from 'react-router-dom'; +import { Box, Link as ChakraLink } from '@chakra-ui/react'; +import { DeleteRoleConfirmModal } from './DeleteRoleConfirmModal'; + + +@observer +class RoleDetailsPage extends PageComponent<{ roleName: string }> { + + @observable isDeleting: boolean = false; + @observable principalSearch: string = ''; + + constructor(p: any) { + super(p); + makeObservable(this); + this.deleteRole = this.deleteRole.bind(this); + } + + initPage(p: PageInitHelper): void { + const roleName = decodeURIComponent(this.props.roleName); + + p.title = 'Role details'; + p.addBreadcrumb('Access control', '/security'); + p.addBreadcrumb('Roles', '/security/roles'); + p.addBreadcrumb(roleName, `/security/roles/${roleName}`); + + this.refreshData(true); + appGlobal.onRefresh = () => this.refreshData(true); + } + + async refreshData(force: boolean) { + if (api.userData != null && !api.userData.canListAcls) return; + + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, force), + api.refreshServiceAccounts(true), + ]); + + await rolesApi.refreshRoles(); + await rolesApi.refreshRoleMembers(); + } + + async deleteRole() { + this.isDeleting = true + try { + await rolesApi.deleteRole(this.props.roleName, true) + await rolesApi.refreshRoles(); + await rolesApi.refreshRoleMembers(); // need to refresh assignments as well, otherwise users will still be marked as having that role, even though it doesn't exist anymore + } finally { + this.isDeleting = false; + } + appGlobal.history.push('/security/roles/'); + } + + render() { + if (api.ACLs?.aclResources === undefined) return DefaultSkeleton; + if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton; + + const aclPrincipalGroup = principalGroupsView.principalGroups.find(({ + principalType, + principalName + }) => principalType === 'RedpandaRole' && principalName === this.props.roleName) + + let members = rolesApi.roleMembers.get(this.props.roleName) ?? [] + try { + const quickSearchRegExp = new RegExp(this.principalSearch, 'i') + members = members.filter(({name}) => name.match(quickSearchRegExp)) + } catch (e) { + console.warn('Invalid expression') + } + + const numberOfPrincipals = rolesApi.roleMembers.get(this.props.roleName)?.length ?? 0; + + return <> + + + + + Delete + + } + roleName={this.props.roleName} + /> + + + Permissions + {aclPrincipalGroup ? : 'This role has no permissions assigned.'} + + + + Principals + This role is assigned to {numberOfPrincipals} {numberOfPrincipals === 1 ? 'member' : 'members'} + + (this.principalSearch = x)} + placeholderText="Filter by name" + /> + + + data={members ?? []} + pagination + sorting + emptyText="No users found" + columns={[ + { + id: 'name', + size: Infinity, + header: 'User', + cell: (ctx) => { + const entry = ctx.row.original; + return <> + + {entry.name} + + + } + } + ]} + /> + + + + } + +} + +export default RoleDetailsPage; + diff --git a/frontend/src/components/pages/acls/RoleEditPage.tsx b/frontend/src/components/pages/acls/RoleEditPage.tsx new file mode 100644 index 000000000..d09f97e1c --- /dev/null +++ b/frontend/src/components/pages/acls/RoleEditPage.tsx @@ -0,0 +1,90 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { observer } from 'mobx-react'; +import { PageComponent, PageInitHelper } from '../Page'; +import { api, rolesApi } from '../../../state/backendApi'; +import { AclRequestDefault } from '../../../state/restInterfaces'; +import { makeObservable, observable } from 'mobx'; +import { appGlobal } from '../../../state/appGlobal'; +import { DefaultSkeleton } from '../../../utils/tsxUtils'; +import PageContent from '../../misc/PageContent'; +import { RoleForm } from './RoleForm'; +import { principalGroupsView } from './Models'; + + +@observer +class RoleEditPage extends PageComponent<{ roleName: string }> { + + @observable allDataLoaded = false; + + constructor(p: any) { + super(p); + makeObservable(this); + } + + initPage(p: PageInitHelper): void { + const roleName = decodeURIComponent(this.props.roleName); + + p.title = 'Edit role'; + p.addBreadcrumb('Access control', '/security'); + p.addBreadcrumb('Roles', '/security/roles'); + p.addBreadcrumb(roleName, `/security/roles/${roleName}`); + + this.refreshData(true); + appGlobal.onRefresh = () => this.refreshData(true); + } + + async refreshData(force: boolean) { + if (api.userData != null && !api.userData.canListAcls) return; + + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, force), + api.refreshServiceAccounts(true), + ]); + + await rolesApi.refreshRoles(); + await rolesApi.refreshRoleMembers(); + + this.allDataLoaded = true + } + + render() { + // if (api.ACLs?.aclResources === undefined) return DefaultSkeleton; + // if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton; + if (!this.allDataLoaded) return DefaultSkeleton; + + const aclPrincipalGroup = principalGroupsView.principalGroups.find(({ + principalType, + principalName + }) => principalType === 'RedpandaRole' && principalName === this.props.roleName); + + const principals = rolesApi.roleMembers.get(this.props.roleName) + + return <> + + + + + } + +} + +export default RoleEditPage; + diff --git a/frontend/src/components/pages/acls/RoleForm.tsx b/frontend/src/components/pages/acls/RoleForm.tsx new file mode 100644 index 000000000..25f5b7e14 --- /dev/null +++ b/frontend/src/components/pages/acls/RoleForm.tsx @@ -0,0 +1,287 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { Box, Button, Flex, FormField, Heading, HStack, Input, isSingleValue, Select, Tag, TagCloseButton, TagLabel } from '@redpanda-data/ui'; +import React, { useEffect, useMemo, useState } from 'react'; +import { AclPrincipalGroup, ClusterACLs, ConsumerGroupACLs, createEmptyClusterAcl, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, TopicACLs, TransactionalIdACLs, unpackPrincipalGroup } from './Models'; +import { observer, useLocalObservable } from 'mobx-react'; +import { ResourceACLsEditor } from './PrincipalGroupEditor'; +import { api, RolePrincipal, rolesApi } from '../../../state/backendApi'; +import { AclStrOperation, AclStrResourceType } from '../../../state/restInterfaces'; +import { useHistory } from 'react-router-dom' +import { appGlobal } from '../../../state/appGlobal'; + +type CreateRoleFormState = { + roleName: string; + allowAllOperations: boolean; + host: string; + topicACLs: TopicACLs[]; + consumerGroupsACLs: ConsumerGroupACLs[]; + transactionalIDACLs: TransactionalIdACLs[]; + clusterACLs: ClusterACLs, + principals: RolePrincipal[] +} + +type RoleFormProps = { + initialData?: Partial; +} + +export const RoleForm = observer(({initialData}: RoleFormProps) => { + const history = useHistory() + const formState = useLocalObservable(() => ({ + roleName: '', + allowAllOperations: false, + host: '', + topicACLs: [createEmptyTopicAcl()], + consumerGroupsACLs: [createEmptyConsumerGroupAcl()], + transactionalIDACLs: [createEmptyTransactionalIdAcl()], + clusterACLs: createEmptyClusterAcl(), + principals: [], + ...initialData, + })) + + if (!formState.clusterACLs) + formState.clusterACLs = createEmptyClusterAcl(); + + const originalUsernames = useMemo(() => initialData?.principals?.map(({name}) => name) ?? [], [ + initialData?.principals + ]) + const currentUsernames = formState.principals.map(({ name }) => name) ?? [] + const editMode: boolean = Boolean(initialData?.roleName) + + const roleNameAlreadyExist = rolesApi.roles.includes(formState.roleName) && !editMode; + + return ( + +
{ + e.preventDefault() + + const usersToRemove = originalUsernames.filter(item => currentUsernames.indexOf(item) === -1) + + const principalType: AclStrResourceType = 'RedpandaRole' + + if(editMode) { + await api.deleteACLs({ + resourceType: 'Any', + resourceName: undefined, + principal: `${principalType}:${formState.roleName}`, + resourcePatternType: 'Any', + operation: 'Any', + permissionType: 'Any' + }) + } + + const aclPrincipalGroup: AclPrincipalGroup = { + principalType: 'RedpandaRole', + principalName: formState.roleName, + + host: formState.host, + + topicAcls: formState.topicACLs, + consumerGroupAcls: formState.consumerGroupsACLs, + transactionalIdAcls: formState.transactionalIDACLs, + clusterAcls: formState.clusterACLs, + sourceEntries: [] + } + + const newRole = await rolesApi.updateRoleMembership( + formState.roleName, + formState.principals.map(x => x.name), usersToRemove, true + ) + + unpackPrincipalGroup(aclPrincipalGroup).forEach((async x => { + await api.createACL({ + host: x.host, + principal: x.principal, + resourceType: x.resourceType, + resourceName: x.resourceName, + resourcePatternType: x.resourcePatternType as unknown as 'Literal' | 'Prefixed', + operation: x.operation as unknown as Exclude, + permissionType: x.permissionType as unknown as 'Allow' | 'Deny' + }) + })) + + void history.push(`/security/roles/${newRole.roleName}/details`); + }}> + + + + + (formState.roleName = v.target.value)} + width={300} + /> + + + + + + + + (formState.host = v.target.value)} + width={600} + /> + + + + Topics + {formState.topicACLs.map((topicACL, index) => + { + formState.topicACLs.splice(index, 1); + }}/> + )} + + + + + + + + + + Consumer Groups + {formState.consumerGroupsACLs.map((acl, index) => + { + formState.consumerGroupsACLs.splice(index, 1); + }}/> + )} + + + + + + + + Transactional IDs + {formState.transactionalIDACLs.map((acl, index) => + { + formState.transactionalIDACLs.splice(index, 1); + }}/> + )} + + + + + + + + Cluster + + + + + + + + + Principals + + + + + + + + {editMode ? + + : } + {editMode ? : + + } + + +
+ ); +}) + +const PrincipalSelector = observer((p: {state: RolePrincipal[]}) => { + const [searchValue, setSearchValue] = useState(''); + + useEffect(() => { + void api.refreshServiceAccounts(); + }, []); + + const state = p.state + + + return + + + placeholder="Find users" + inputValue={searchValue} + onInputChange={setSearchValue} + isMulti={false} + options={api.serviceAccounts?.users.map((u) => ({ + value: u, + })) ?? []} + onChange={(val) => { + if(val && isSingleValue(val) && val.value) { + state.push({name: val.value, principalType: 'User'}); + setSearchValue('') + } + }} + /> + + + + {state.map((principal, idx) => + + {principal.name} + state.remove(principal)} /> + + )} + + +}) diff --git a/frontend/src/components/pages/acls/UserCreate.tsx b/frontend/src/components/pages/acls/UserCreate.tsx new file mode 100644 index 000000000..2ca81bc27 --- /dev/null +++ b/frontend/src/components/pages/acls/UserCreate.tsx @@ -0,0 +1,448 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { observer } from 'mobx-react'; +import { PageComponent, PageInitHelper } from '../Page'; +import { api, rolesApi } from '../../../state/backendApi'; +import { AclRequestDefault, CreateUserRequest } from '../../../state/restInterfaces'; +import { makeObservable, observable } from 'mobx'; +import { appGlobal } from '../../../state/appGlobal'; +import { DefaultSkeleton } from '../../../utils/tsxUtils'; +import Section from '../../misc/Section'; +import PageContent from '../../misc/PageContent'; +import { Alert, AlertIcon, Box, Button, Checkbox, CopyButton, createStandaloneToast, Flex, FormField, Grid, Heading, Input, isSingleValue, PasswordInput, redpandaTheme, redpandaToastOptions, Result, Select, Tag, TagCloseButton, TagLabel, Text, Tooltip } from '@redpanda-data/ui'; +import { useEffect, useMemo, useState } from 'react'; +import { ReloadOutlined } from '@ant-design/icons'; +import { SingleSelect } from '../../misc/Select'; +import { Features } from '../../../state/supportedFeatures'; + +const { ToastContainer, toast } = createStandaloneToast({ + theme: redpandaTheme, + defaultOptions: { + ...redpandaToastOptions.defaultOptions, + isClosable: false, + duration: 2000, + }, +}); + +export type CreateUserModalState = CreateUserRequest & { + generateWithSpecialChars: boolean; + step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION'; + isCreating: boolean; + isValidUsername: boolean; + isValidPassword: boolean; + selectedRoles: string[]; +}; + +@observer +class UserCreatePage extends PageComponent<{}> { + + @observable username: string = ''; + @observable password: string = generatePassword(30, false); + @observable mechanism: 'SCRAM-SHA-256' | 'SCRAM-SHA-512' = 'SCRAM-SHA-256'; + + @observable isValidUsername: boolean = false; + @observable isValidPassword: boolean = false; + + @observable generateWithSpecialChars: boolean = false; + @observable step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION' = 'CREATE_USER'; + @observable isCreating: boolean = false; + + @observable selectedRoles: string[] = []; + + constructor(p: any) { + super(p); + makeObservable(this); + this.onCreateUser = this.onCreateUser.bind(this); + } + + initPage(p: PageInitHelper): void { + p.title = 'Create user'; + p.addBreadcrumb('Access control', '/security'); + p.addBreadcrumb('Create user', '/security/users/create'); + + this.refreshData(true); + appGlobal.onRefresh = () => this.refreshData(true); + } + + async refreshData(force: boolean) { + if (api.userData != null && !api.userData.canListAcls) return; + + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, force), + api.refreshServiceAccounts(true) + ]); + } + + render() { + if (api.userData != null && !api.userData.canListAcls) return PermissionDenied; + if (api.ACLs?.aclResources === undefined) return DefaultSkeleton; + if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton; + + this.isValidUsername = /^[a-zA-Z0-9._@-]+$/.test(this.username); + this.isValidPassword = Boolean(this.password) && this.password.length >= 4 && this.password.length <= 64; + + const onCancel = () => appGlobal.history.push('/security/users'); + + return <> + + + + + {this.step === 'CREATE_USER' + ? + : + } + + + + } + + async onCreateUser(): Promise { + try { + this.isCreating = true; + await api.createServiceAccount({ + username: this.username, + password: this.password, + mechanism: this.mechanism, + }); + + // Refresh user list + if (api.userData != null && !api.userData.canListAcls) return false; + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, true), + api.refreshServiceAccounts(true) + ]); + + // Add the user to the selected roles + const roleAddPromises = []; + for (const r of this.selectedRoles) { + roleAddPromises.push(rolesApi.updateRoleMembership(r, [this.username], [], false)); + } + await Promise.allSettled(roleAddPromises); + + this.step = 'CREATE_USER_CONFIRMATION'; + } catch (err) { + toast({ + status: 'error', + duration: null, + isClosable: true, + title: 'Failed to create user', + description: String(err), + }); + } finally { + this.isCreating = false; + } + return true; + }; +} + +export default UserCreatePage; + + +const CreateUserModal = observer((p: { + state: CreateUserModalState; + onCreateUser: () => Promise; + onCancel: () => void; +}) => { + const state = p.state; + + const isValidUsername = /^[a-zA-Z0-9._@-]+$/.test(state.username); + const users = api.serviceAccounts?.users ?? [] + const userAlreadyExists = users.includes(state.username) + const isValidPassword = state.password && state.password.length >= 4 && state.password.length <= 64; + + const errorText = useMemo(() => { + if(!isValidUsername) { + return 'The username contains invalid characters. Use only letters, numbers, dots, underscores, at symbols, and hyphens.' + } + + if(userAlreadyExists) { + return 'User already exist' + } + }, [isValidUsername, userAlreadyExists]) + + return ( + + + + (state.username = v.target.value)} + width="100%" + autoFocus + spellCheck={false} + placeholder="Username" + autoComplete="off" + /> + + + + + + (state.password = e.target.value)} + isInvalid={!isValidPassword} + /> + + + + + + + + + { + state.generateWithSpecialChars = e.target.checked; + state.password = generatePassword(30, e.target.checked); + }} + > + Generate with special characters + + + + + + + options={[ + { + value: 'SCRAM-SHA-256', + label: 'SCRAM-SHA-256', + }, + { + value: 'SCRAM-SHA-512', + label: 'SCRAM-SHA-512', + }, + ]} + value={state.mechanism} + onChange={(e) => { + state.mechanism = e; + }} + /> + + + {Features.rolesApi && <> + + + + + } + + + + + + + + + ); +} +); + +const CreateUserConfirmationModal = observer((p: { state: CreateUserModalState; closeModal: () => void }) => { + return ( + <> + + + {/* */} + User created successfully + + + + + + You will not be able to view this password again. Make sure that it is copied and saved. + + + + + Username + + + + + {p.state.username} + + + + + + + + + + Password + + + + + + + + + + + + + Mechanism + + + + {p.state.mechanism} + + + + + + + + + + ); +}); + + +export const RoleSelector = observer((p: { state: string[] }) => { + + // Make sure we have up to date role info + useEffect(() => { + rolesApi.refreshRoles(); + rolesApi.refreshRoleMembers(); + }, []); + const [searchValue, setSearchValue] = useState(''); + + const state = p.state; + + const availableRoles = (rolesApi.roles ?? []) + .filter(r => !state.includes(r)) + .map(r => ({ value: r })); + + return + + + isMulti={false} + options={availableRoles} + inputValue={searchValue} + onInputChange={setSearchValue} + placeholder="Find roles..." + + // TODO: Selecting an entry triggers onChange properly. + // But there is no way to prevent the component from showing no value as intended + // Seems to be a bug with the component. + // On 'undefined' it should handle selection on its own (this works properly) + // On 'null' the component should NOT show any selection after a selection has been made (does not work!) + // The override doesn't work either (isOptionSelected={()=>false}) + value={undefined} + + onChange={(val, meta) => { + console.log('onChange', { metaAction: meta.action, val }); + if (val && isSingleValue(val) && val.value) { + state.push(val.value); + setSearchValue(''); + } + }} + /> + + + + {state.map(role => + + {role} + state.remove(role)} /> + + )} + + +}); + + +const PermissionDenied = <> + +
+ + You are not allowed to view this page. +
+ Contact the administrator if you think this is an error. + + } + extra={ + + } + /> +
+
+ + + +export function generatePassword(length: number, allowSpecialChars: boolean): string { + if (length <= 0) return ''; + + const lowercase = 'abcdefghijklmnopqrstuvwxyz' + const uppercase = lowercase.toUpperCase(); + const numbers = '0123456789'; + const special = '.,&_+|[]/-()'; + + let alphabet = lowercase + uppercase + numbers; + if (allowSpecialChars) { + alphabet += special; + } + + const randomValues = new Uint32Array(length); + crypto.getRandomValues(randomValues); + + let result = ''; + for (const n of randomValues) { + const index = n % alphabet.length; + const sym = alphabet[index]; + + result += sym; + } + + return result; +} diff --git a/frontend/src/components/pages/acls/UserDetails.tsx b/frontend/src/components/pages/acls/UserDetails.tsx new file mode 100644 index 000000000..b72140267 --- /dev/null +++ b/frontend/src/components/pages/acls/UserDetails.tsx @@ -0,0 +1,342 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { observer } from 'mobx-react'; +import { PageComponent, PageInitHelper } from '../Page'; +import { api, rolesApi } from '../../../state/backendApi'; +import { AclRequestDefault } from '../../../state/restInterfaces'; +import { makeObservable, observable } from 'mobx'; +import { appGlobal } from '../../../state/appGlobal'; +import { DefaultSkeleton } from '../../../utils/tsxUtils'; +import PageContent from '../../misc/PageContent'; +import { Box, Button, DataTable, Flex, Heading, Text } from '@redpanda-data/ui'; + +import { Link as ReactRouterLink } from 'react-router-dom'; +import { Link as ChakraLink } from '@chakra-ui/react'; +import { AclPrincipalGroup, principalGroupsView } from './Models'; +import { DeleteUserConfirmModal } from './DeleteUserConfirmModal'; +import { UserPermissionAssignments } from './UserPermissionAssignments'; +import { Features } from '../../../state/supportedFeatures'; + +@observer +class UserDetailsPage extends PageComponent<{ userName: string; }> { + + @observable username: string = ''; + @observable mechanism: 'SCRAM-SHA-256' | 'SCRAM-SHA-512' = 'SCRAM-SHA-256'; + + @observable isValidUsername: boolean = false; + @observable isValidPassword: boolean = false; + + @observable generateWithSpecialChars: boolean = false; + @observable step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION' = 'CREATE_USER'; + @observable isCreating: boolean = false; + + @observable selectedRoles: string[] = []; + + constructor(p: any) { + super(p); + makeObservable(this); + } + + initPage(p: PageInitHelper): void { + p.title = 'Create user'; + p.addBreadcrumb('Access control', '/security'); + p.addBreadcrumb('Users', '/security/users'); + p.addBreadcrumb(this.props.userName, '/security/users/'); + + this.refreshData(true); + appGlobal.onRefresh = () => this.refreshData(true); + } + + async refreshData(force: boolean) { + if (api.userData != null && !api.userData.canListAcls) return; + + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, force), + api.refreshServiceAccounts(true), + rolesApi.refreshRoles() + ]); + + await rolesApi.refreshRoleMembers(); + } + + render() { + if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton; + const userName = this.props.userName; + + const isServiceAccount = api.serviceAccounts.users.includes(userName); + + return <> + + + + {/* todo: refactor delete user dialog into a "fire and forget" dialog and use it in the overview list (and here) */} + {isServiceAccount && + { + await api.deleteServiceAccount(userName); + + // Remove user from all its roles + const promises = []; + for (const [roleName, members] of rolesApi.roleMembers) { + if (members.any(m => m.name == userName)) { // is this user part of this role? + // then remove it + promises.push(rolesApi.updateRoleMembership(roleName, [], [userName])); + } + } + await Promise.allSettled(promises); + await api.refreshServiceAccounts(true); + await rolesApi.refreshRoleMembers(); + appGlobal.history.push('/security/users/'); + }} + buttonEl={ + + } + userName={userName} + /> + } + + + Permissions + Below are all of the permissions assigned to this SCRAM user. + + {Features.rolesApi && <> + Assignments + + + + } + + + + } + + +} + +export default UserDetailsPage; + + +const PermissionAssignemntsDetails = observer((p: { + userName: string; +}) => { + // Get all roles and ACLs matching this user + // For each "AclPrincipalGroup" show its name, then a table that shows the details + const roles: string[] = []; + for (const [roleName, members] of rolesApi.roleMembers) { + if (!members.any(m => m.name == p.userName)) + continue; // this role doesn't contain our user + roles.push(roleName); + } + + // Get all AclPrincipal groups, find the ones that apply + const groups = principalGroupsView.principalGroups.filter(g => { + if (g.principalType == 'User' && (g.principalName == p.userName || g.principalName === '*')) return true; + if (g.principalType == 'RedpandaRole' && roles.includes(g.principalName)) return true; + return false; + }); + + console.log('groups: ' + groups.map(g => g.principalName + ' (' + g.principalType + ')').join(', ')); + + return <> + {groups.map(r => + <> + { + r.principalType == 'RedpandaRole' + ? {r.principalName} + : User: {r.principalName} + } + + + )} + +}); + +export const AclPrincipalGroupPermissionsTable = observer((p: { group: AclPrincipalGroup }) => { + + const entries: { + type: string; + selector: string; + operations: { + allow: string[]; + deny: string[]; + }; + }[] = []; + + // Convert all entries of the group into a table row + const group = p.group; + for (const topicAcl of group.topicAcls) { + const allow: string[] = []; + const deny: string[] = []; + + if (topicAcl.all == 'Allow') + allow.push('All'); + else if (topicAcl.all == 'Deny') + deny.push('All'); + else { + for (const [permName, value] of Object.entries(topicAcl.permissions)) { + if (value == 'Allow') + allow.push(permName); + if (value == 'Deny') + deny.push(permName); + } + } + + if (allow.length == 0 && deny.length == 0) + continue; + + entries.push({ + type: 'Topic', + selector: topicAcl.selector, + operations: { allow, deny } + }) + } + + for (const groupAcl of group.consumerGroupAcls) { + const allow: string[] = []; + const deny: string[] = []; + + if (groupAcl.all == 'Allow') + allow.push('All'); + else if (groupAcl.all == 'Deny') + deny.push('All'); + else { + for (const [permName, value] of Object.entries(groupAcl.permissions)) { + if (value == 'Allow') + allow.push(permName); + if (value == 'Deny') + deny.push(permName); + } + } + + if (allow.length == 0 && deny.length == 0) + continue; + + entries.push({ + type: 'ConsumerGroup', + selector: groupAcl.selector, + operations: { allow, deny } + }) + } + + for (const transactId of group.transactionalIdAcls) { + const allow: string[] = []; + const deny: string[] = []; + + if (transactId.all == 'Allow') + allow.push('All'); + else if (transactId.all == 'Deny') + deny.push('All'); + else { + for (const [permName, value] of Object.entries(transactId.permissions)) { + if (value == 'Allow') + allow.push(permName); + if (value == 'Deny') + deny.push(permName); + } + } + + if (allow.length == 0 && deny.length == 0) + continue; + + entries.push({ + type: 'TransactionalID', + selector: transactId.selector, + operations: { allow, deny } + }) + } + + // Cluster + { + const clusterAcls = group.clusterAcls; + + const allow: string[] = []; + const deny: string[] = []; + + if (clusterAcls.all == 'Allow') + allow.push('All'); + else if (clusterAcls.all == 'Deny') + deny.push('All'); + else { + for (const [permName, value] of Object.entries(clusterAcls.permissions)) { + if (value == 'Allow') + allow.push(permName); + if (value == 'Deny') + deny.push(permName); + } + } + + // Cluster only appears once, so it won't be filtered automatically, + // we need to manually skip this entry if there isn't any content + if (allow.length + deny.length > 0) + entries.push({ + type: 'Cluster', + selector: '', + operations: { allow, deny } + }) + } + + if (entries.length == 0) + return <>No permissions assigned; + + return <> + { + + const allow = record.operations.allow; + const deny = record.operations.deny; + + return + + {allow.length > 0 + ? <> + Allow: + {allow.join(', ')} + + : {' '} + } + + + {deny.length > 0 + ? <> + Deny: + {deny.join(', ')} + + : {' '} + } + + + }, + } + ]} + /> + + +}) diff --git a/frontend/src/components/pages/acls/UserEdit.tsx b/frontend/src/components/pages/acls/UserEdit.tsx new file mode 100644 index 000000000..22cf4cae3 --- /dev/null +++ b/frontend/src/components/pages/acls/UserEdit.tsx @@ -0,0 +1,157 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { observer } from 'mobx-react'; +import { PageComponent, PageInitHelper } from '../Page'; +import { api, rolesApi } from '../../../state/backendApi'; +import { AclRequestDefault } from '../../../state/restInterfaces'; +import { makeObservable, observable } from 'mobx'; +import { appGlobal } from '../../../state/appGlobal'; +import { DefaultSkeleton } from '../../../utils/tsxUtils'; +import PageContent from '../../misc/PageContent'; +import { Box, Button, Flex, Heading, Input, createStandaloneToast, redpandaTheme, redpandaToastOptions } from '@redpanda-data/ui'; +import { RoleSelector } from './UserCreate'; +import { UpdateRoleMembershipResponse } from '../../../protogen/redpanda/api/console/v1alpha1/security_pb'; +import { Features } from '../../../state/supportedFeatures'; + +const { ToastContainer, toast } = createStandaloneToast({ + theme: redpandaTheme, + defaultOptions: { + ...redpandaToastOptions.defaultOptions, + isClosable: false, + duration: 2000, + }, +}); + +@observer +class UserEditPage extends PageComponent<{ userName: string; }> { + + @observable originalRoles: Set | undefined = undefined; + @observable selectedRoles: string[] | undefined = undefined; + @observable isSaving = false; + + constructor(p: any) { + super(p); + makeObservable(this); + } + + initPage(p: PageInitHelper): void { + p.title = 'Edit user ' + this.props.userName; + p.addBreadcrumb('Access control', '/security'); + p.addBreadcrumb('Users', '/security/users'); + p.addBreadcrumb(this.props.userName, `/security/users/${this.props.userName}/edit`); + + this.refreshData(true); + appGlobal.onRefresh = () => this.refreshData(true); + } + + async refreshData(force: boolean) { + if (api.userData != null && !api.userData.canListAcls) return; + + await Promise.allSettled([ + api.refreshAcls(AclRequestDefault, force), + api.refreshServiceAccounts(true), + rolesApi.refreshRoles() + ]); + + await rolesApi.refreshRoleMembers(); + } + + render() { + if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton; + + const userName = this.props.userName; + + // Load roles the user is assigned to, for this we need all roles + if (this.selectedRoles == undefined || this.originalRoles == undefined) { + this.originalRoles = new Set(); + for (const [name, members] of rolesApi.roleMembers) { + if (members.any(x => x.name == userName)) + this.originalRoles.add(name); + } + + this.selectedRoles = [...this.originalRoles.values()]; + } + + const onCancel = () => appGlobal.history.push(`/security/users/${userName}/details`); + + + // Check if there are any roles removed, added, or replaced + // only then will the save button be enabled + // First check if they have the same number of roles + const originalRoles = [...this.originalRoles.values()]; + + const addedRoles = this.selectedRoles.except(originalRoles); + const removedRoles = originalRoles.except(this.selectedRoles); + + const hasChanges = addedRoles.length > 0 || removedRoles.length > 0 + + const onSave = async () => { + if (Features.rolesApi) { + const promises: Promise[] = []; + + // Remove user from "removedRoles" + for (const r of removedRoles) + promises.push(rolesApi.updateRoleMembership(r, [], [userName], false)); + // Add to newly selected roles + for (const r of addedRoles) + promises.push(rolesApi.updateRoleMembership(r, [userName], [], false)); + + await Promise.allSettled(promises); + + // Update roles and memberships so that the change is reflected in the ui + await rolesApi.refreshRoles(); + await rolesApi.refreshRoleMembers(); + } + + toast({ + status: 'success', + title: `${addedRoles.length} roles added, ${removedRoles.length} removed from user ${userName}` + }); + appGlobal.history.push(`/security/users/${userName}/details`); + }; + + return <> + + + + + Create user + This is a Redpanda SASL/SCRAM user. For other types of users and authentication, see our documentation. + + Username + + + {Features.rolesApi && <> + Assignments + + + } + + + + + + + + + } + + +} + +export default UserEditPage; + + diff --git a/frontend/src/components/pages/acls/UserPermissionAssignments.tsx b/frontend/src/components/pages/acls/UserPermissionAssignments.tsx new file mode 100644 index 000000000..2ceee91d6 --- /dev/null +++ b/frontend/src/components/pages/acls/UserPermissionAssignments.tsx @@ -0,0 +1,56 @@ +/** + * Copyright 2022 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +import { observer } from 'mobx-react'; +import { rolesApi } from '../../../state/backendApi'; +import { Link as ReactRouterLink } from 'react-router-dom'; +import { Link as ChakraLink } from '@chakra-ui/react'; +import React from 'react'; +import { Box, Flex, Text } from '@redpanda-data/ui'; + +export const UserPermissionAssignments = observer(({ + userName, + showMaxItems = Infinity + }: { + userName: string; + showMaxItems?: number; +}) => { + // Get all roles, and ACL sets that apply to this user + const roles = []; + for (const [roleName, members] of rolesApi.roleMembers) { + if (!members.any(m => m.name == userName)) { + continue; // this role doesn't contain our user + } + roles.push(roleName); + } + + const elements: JSX.Element[] = []; + + const numberOfVisibleElements = Math.min(roles.length, showMaxItems); + const numberOfHiddenElements = showMaxItems === Infinity ? 0 : Math.max(0, roles.length - showMaxItems); + + for (let i = 0; i < numberOfVisibleElements; i++) { + const r = roles[i]; + elements.push( + + {r} + + ); + + if (i < numberOfVisibleElements - 1) + elements.push({', '}); + } + + return + {elements} + {numberOfHiddenElements !== 0 && {`+${numberOfHiddenElements} more`}} + ; +}); diff --git a/frontend/src/components/routes.tsx b/frontend/src/components/routes.tsx index ae7d276f7..e6b1daebe 100644 --- a/frontend/src/components/routes.tsx +++ b/frontend/src/components/routes.tsx @@ -23,7 +23,7 @@ import AdminPage from './pages/admin/AdminPage'; import { api } from '../state/backendApi'; import SchemaList from './pages/schemas/Schema.List'; import SchemaDetailsView from './pages/schemas/Schema.Details'; -import AclList from './pages/acls/Acl.List'; +import AclList, { AclListTab } from './pages/acls/Acl.List'; import { HomeIcon, CogIcon, CollectionIcon, CubeTransparentIcon, FilterIcon, ShieldCheckIcon, LinkIcon, ScaleIcon, BeakerIcon } from '@heroicons/react/outline'; import ReassignPartitions from './pages/reassign-partitions/ReassignPartitions'; import { Feature, FeatureEntry, isSupported, shouldHideIfNotSupported } from '../state/supportedFeatures'; @@ -41,6 +41,12 @@ import { BrokerDetails } from './pages/overview/Broker.Details'; import EditSchemaCompatibilityPage from './pages/schemas/EditCompatibility'; import { SchemaCreatePage, SchemaAddVersionPage } from './pages/schemas/Schema.Create'; import { TopicProducePage } from './pages/topics/Topic.Produce'; +import UserCreatePage from './pages/acls/UserCreate'; +import UserDetailsPage from './pages/acls/UserDetails'; +import UserEditPage from './pages/acls/UserEdit'; +import RoleCreatePage from './pages/acls/RoleCreate'; +import RoleDetailsPage from './pages/acls/RoleDetails'; +import RoleEditPage from './pages/acls/RoleEditPage'; // // Route Types @@ -247,9 +253,19 @@ export const APP_ROUTES: IRouteEntry[] = [ ), MakeRoute<{ groupId: string }>('/groups/:groupId/', GroupDetails, 'Consumer Groups'), - MakeRoute<{}>('/acls', AclList, 'Security', ShieldCheckIcon, true, + MakeRoute<{}>('/security', AclList, 'Security', ShieldCheckIcon, true, routeVisibility(true, [], ['canListAcls']) ), + MakeRoute<{ tab: AclListTab }>('/security/:tab', AclList, 'Security'), + + MakeRoute<{}>('/security/users/create', UserCreatePage, 'Security'), + MakeRoute<{ userName: string }>('/security/users/:userName/details', UserDetailsPage, 'Security'), + MakeRoute<{ userName: string }>('/security/users/:userName/edit', UserEditPage, 'Security'), + + MakeRoute<{}>('/security/roles/create', RoleCreatePage, 'Security'), + MakeRoute<{ roleName: string }>('/security/roles/:roleName/details', RoleDetailsPage, 'Security'), + MakeRoute<{ roleName: string }>('/security/roles/:roleName/edit', RoleEditPage, 'Security'), + MakeRoute<{}>('/quotas', QuotasList, 'Quotas', ScaleIcon, true, routeVisibility(true, [Feature.GetQuotas], ['canListQuotas']) diff --git a/frontend/src/config.ts b/frontend/src/config.ts index a5c41c387..638bffc0b 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -19,6 +19,7 @@ import { APP_ROUTES } from './components/routes'; import { Interceptor as ConnectRpcInterceptor, StreamRequest, UnaryRequest, createPromiseClient, PromiseClient } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-web'; import { ConsoleService } from './protogen/redpanda/api/console/v1alpha1/console_service_connect'; +import { SecurityService } from './protogen/redpanda/api/console/v1alpha1/security_connect'; declare const __webpack_public_path__: string; @@ -63,6 +64,7 @@ export interface Breadcrumb { interface Config { restBasePath: string; consoleClient?: PromiseClient; + securityClient?: PromiseClient; fetch: WindowOrWorkerGlobalScope['fetch']; assetsPath: string; jwt?: string; @@ -94,14 +96,16 @@ const setConfig = ({ fetch, urlOverride, jwt, isServerless, ...args }: SetConfig interceptors: [addBearerTokenInterceptor] }); - const grpcClient = createPromiseClient(ConsoleService, transport); + const consoleGrpcClient = createPromiseClient(ConsoleService, transport); + const securityGrpcClient = createPromiseClient(SecurityService, transport); Object.assign(config, { jwt, isServerless, restBasePath: getRestBasePath(urlOverride?.rest), fetch: fetch ?? window.fetch.bind(window), assetsPath: assetsUrl ?? getBasePath(), - consoleClient: grpcClient, + consoleClient: consoleGrpcClient, + securityClient: securityGrpcClient, ...args, }); return config; diff --git a/frontend/src/protogen/redpanda/api/console/v1alpha1/security_connect.ts b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_connect.ts new file mode 100644 index 000000000..63f6d96cf --- /dev/null +++ b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_connect.ts @@ -0,0 +1,85 @@ +// @generated by protoc-gen-connect-es v1.2.0 with parameter "target=ts,import_extension=" +// @generated from file redpanda/api/console/v1alpha1/security.proto (package redpanda.api.console.v1alpha1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import { CreateRoleRequest, CreateRoleResponse, DeleteRoleRequest, DeleteRoleResponse, GetRoleRequest, GetRoleResponse, ListRoleMembersRequest, ListRoleMembersResponse, ListRolesRequest, ListRolesResponse, UpdateRoleMembershipRequest, UpdateRoleMembershipResponse } from "./security_pb"; +import { MethodKind } from "@bufbuild/protobuf"; + +/** + * @generated from service redpanda.api.console.v1alpha1.SecurityService + */ +export const SecurityService = { + typeName: "redpanda.api.console.v1alpha1.SecurityService", + methods: { + /** + * ListRoles lists all the roles based on optional filter. + * + * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.ListRoles + */ + listRoles: { + name: "ListRoles", + I: ListRolesRequest, + O: ListRolesResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.CreateRole + */ + createRole: { + name: "CreateRole", + I: CreateRoleRequest, + O: CreateRoleResponse, + kind: MethodKind.Unary, + }, + /** + * GetRole retrieves the specific role. + * + * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.GetRole + */ + getRole: { + name: "GetRole", + I: GetRoleRequest, + O: GetRoleResponse, + kind: MethodKind.Unary, + }, + /** + * DeleteRole deletes the role from the system. + * + * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.DeleteRole + */ + deleteRole: { + name: "DeleteRole", + I: DeleteRoleRequest, + O: DeleteRoleResponse, + kind: MethodKind.Unary, + }, + /** + * ListRoleMembership lists all the members assigned to a role based on optional filter. + * + * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.ListRoleMembers + */ + listRoleMembers: { + name: "ListRoleMembers", + I: ListRoleMembersRequest, + O: ListRoleMembersResponse, + kind: MethodKind.Unary, + }, + /** + * UpdateRoleMembership updates role membership. + * Partially update role membership, adding or removing from a role + * ONLY those members listed in the “add” or “remove” fields, respectively. + * Adding a member that is already assigned to the role (or removing one that is not) is a no-op, + * and the rest of the members will be added and removed and reported. + * + * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.UpdateRoleMembership + */ + updateRoleMembership: { + name: "UpdateRoleMembership", + I: UpdateRoleMembershipRequest, + O: UpdateRoleMembershipResponse, + kind: MethodKind.Unary, + }, + } +} as const; + diff --git a/frontend/src/protogen/redpanda/api/console/v1alpha1/security_pb.ts b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_pb.ts new file mode 100644 index 000000000..fa3043e85 --- /dev/null +++ b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_pb.ts @@ -0,0 +1,795 @@ +// @generated by protoc-gen-es v1.6.0 with parameter "target=ts,import_extension=" +// @generated from file redpanda/api/console/v1alpha1/security.proto (package redpanda.api.console.v1alpha1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; + +/** + * Role defines a role in the system. + * + * @generated from message redpanda.api.console.v1alpha1.Role + */ +export class Role extends Message { + /** + * The name of the role. + * + * @generated from field: string name = 1; + */ + name = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.Role"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): Role { + return new Role().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): Role { + return new Role().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Role { + return new Role().fromJsonString(jsonString, options); + } + + static equals(a: Role | PlainMessage | undefined, b: Role | PlainMessage | undefined): boolean { + return proto3.util.equals(Role, a, b); + } +} + +/** + * ListRolesRequest is the request for ListRoles. + * + * @generated from message redpanda.api.console.v1alpha1.ListRolesRequest + */ +export class ListRolesRequest extends Message { + /** + * Optional filter. + * + * @generated from field: optional redpanda.api.console.v1alpha1.ListRolesRequest.Filter filter = 1; + */ + filter?: ListRolesRequest_Filter; + + /** + * Page size. + * + * @generated from field: int32 page_size = 2; + */ + pageSize = 0; + + /** + * Value of the next_page_token field returned by the previous response. + * If not provided, the system assumes the first page is requested. + * + * @generated from field: string page_token = 3; + */ + pageToken = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.ListRolesRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "filter", kind: "message", T: ListRolesRequest_Filter, opt: true }, + { no: 2, name: "page_size", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: "page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListRolesRequest { + return new ListRolesRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListRolesRequest { + return new ListRolesRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListRolesRequest { + return new ListRolesRequest().fromJsonString(jsonString, options); + } + + static equals(a: ListRolesRequest | PlainMessage | undefined, b: ListRolesRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(ListRolesRequest, a, b); + } +} + +/** + * Filter options. + * + * @generated from message redpanda.api.console.v1alpha1.ListRolesRequest.Filter + */ +export class ListRolesRequest_Filter extends Message { + /** + * Filter results only roles named with the prefix. + * + * @generated from field: string name_prefix = 1; + */ + namePrefix = ""; + + /** + * Filter results to only roles with names which contain the string. + * + * @generated from field: string name_contains = 2; + */ + nameContains = ""; + + /** + * Return only roles assigned to this principal. + * + * @generated from field: string principal = 3; + */ + principal = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.ListRolesRequest.Filter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "name_prefix", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "name_contains", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "principal", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListRolesRequest_Filter { + return new ListRolesRequest_Filter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListRolesRequest_Filter { + return new ListRolesRequest_Filter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListRolesRequest_Filter { + return new ListRolesRequest_Filter().fromJsonString(jsonString, options); + } + + static equals(a: ListRolesRequest_Filter | PlainMessage | undefined, b: ListRolesRequest_Filter | PlainMessage | undefined): boolean { + return proto3.util.equals(ListRolesRequest_Filter, a, b); + } +} + +/** + * ListRolesResponse is the response for ListRoles. + * + * @generated from message redpanda.api.console.v1alpha1.ListRolesResponse + */ +export class ListRolesResponse extends Message { + /** + * The roles in the system. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.Role roles = 1; + */ + roles: Role[] = []; + + /** + * Token to retrieve the next page. + * + * @generated from field: string next_page_token = 2; + */ + nextPageToken = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.ListRolesResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "roles", kind: "message", T: Role, repeated: true }, + { no: 2, name: "next_page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListRolesResponse { + return new ListRolesResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListRolesResponse { + return new ListRolesResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListRolesResponse { + return new ListRolesResponse().fromJsonString(jsonString, options); + } + + static equals(a: ListRolesResponse | PlainMessage | undefined, b: ListRolesResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(ListRolesResponse, a, b); + } +} + +/** + * CreateRoleRequest is the request for CreateRole. + * + * @generated from message redpanda.api.console.v1alpha1.CreateRoleRequest + */ +export class CreateRoleRequest extends Message { + /** + * The role to create. + * + * @generated from field: redpanda.api.console.v1alpha1.Role role = 1; + */ + role?: Role; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.CreateRoleRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role", kind: "message", T: Role }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): CreateRoleRequest { + return new CreateRoleRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): CreateRoleRequest { + return new CreateRoleRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): CreateRoleRequest { + return new CreateRoleRequest().fromJsonString(jsonString, options); + } + + static equals(a: CreateRoleRequest | PlainMessage | undefined, b: CreateRoleRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(CreateRoleRequest, a, b); + } +} + +/** + * CreateRoleResponse is the response for CreateRole. + * + * @generated from message redpanda.api.console.v1alpha1.CreateRoleResponse + */ +export class CreateRoleResponse extends Message { + /** + * The role. + * + * @generated from field: redpanda.api.console.v1alpha1.Role role = 1; + */ + role?: Role; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.CreateRoleResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role", kind: "message", T: Role }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): CreateRoleResponse { + return new CreateRoleResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): CreateRoleResponse { + return new CreateRoleResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): CreateRoleResponse { + return new CreateRoleResponse().fromJsonString(jsonString, options); + } + + static equals(a: CreateRoleResponse | PlainMessage | undefined, b: CreateRoleResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(CreateRoleResponse, a, b); + } +} + +/** + * CreateRoleRequest is the request for CreateRole. + * + * @generated from message redpanda.api.console.v1alpha1.GetRoleRequest + */ +export class GetRoleRequest extends Message { + /** + * The role name. + * + * @generated from field: string role_name = 1; + */ + roleName = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.GetRoleRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): GetRoleRequest { + return new GetRoleRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): GetRoleRequest { + return new GetRoleRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): GetRoleRequest { + return new GetRoleRequest().fromJsonString(jsonString, options); + } + + static equals(a: GetRoleRequest | PlainMessage | undefined, b: GetRoleRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(GetRoleRequest, a, b); + } +} + +/** + * GetRoleResponse is the response to GetRole. + * + * @generated from message redpanda.api.console.v1alpha1.GetRoleResponse + */ +export class GetRoleResponse extends Message { + /** + * The Role. + * + * @generated from field: redpanda.api.console.v1alpha1.Role role = 1; + */ + role?: Role; + + /** + * Members assigned to the role. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership members = 2; + */ + members: RoleMembership[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.GetRoleResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role", kind: "message", T: Role }, + { no: 2, name: "members", kind: "message", T: RoleMembership, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): GetRoleResponse { + return new GetRoleResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): GetRoleResponse { + return new GetRoleResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): GetRoleResponse { + return new GetRoleResponse().fromJsonString(jsonString, options); + } + + static equals(a: GetRoleResponse | PlainMessage | undefined, b: GetRoleResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(GetRoleResponse, a, b); + } +} + +/** + * DeleteRoleRequest is the request for DeleteRole. + * + * @generated from message redpanda.api.console.v1alpha1.DeleteRoleRequest + */ +export class DeleteRoleRequest extends Message { + /** + * The role name. + * + * @generated from field: string role_name = 1; + */ + roleName = ""; + + /** + * Whether to delete the ACLs bound to the role. + * + * @generated from field: bool delete_acls = 2; + */ + deleteAcls = false; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.DeleteRoleRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "delete_acls", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): DeleteRoleRequest { + return new DeleteRoleRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): DeleteRoleRequest { + return new DeleteRoleRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): DeleteRoleRequest { + return new DeleteRoleRequest().fromJsonString(jsonString, options); + } + + static equals(a: DeleteRoleRequest | PlainMessage | undefined, b: DeleteRoleRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(DeleteRoleRequest, a, b); + } +} + +/** + * DeleteRoleResponse is the response for DeleteRole. + * + * @generated from message redpanda.api.console.v1alpha1.DeleteRoleResponse + */ +export class DeleteRoleResponse extends Message { + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.DeleteRoleResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): DeleteRoleResponse { + return new DeleteRoleResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): DeleteRoleResponse { + return new DeleteRoleResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): DeleteRoleResponse { + return new DeleteRoleResponse().fromJsonString(jsonString, options); + } + + static equals(a: DeleteRoleResponse | PlainMessage | undefined, b: DeleteRoleResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(DeleteRoleResponse, a, b); + } +} + +/** + * List role members for a role. That is user principals assigned to that role. + * + * @generated from message redpanda.api.console.v1alpha1.ListRoleMembersRequest + */ +export class ListRoleMembersRequest extends Message { + /** + * The role name. + * + * @generated from field: string role_name = 1; + */ + roleName = ""; + + /** + * Optional filter. + * + * @generated from field: optional redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter filter = 2; + */ + filter?: ListRoleMembersRequest_Filter; + + /** + * Page size. + * + * @generated from field: int32 page_size = 3; + */ + pageSize = 0; + + /** + * Value of the next_page_token field returned by the previous response. + * If not provided, the system assumes the first page is requested. + * + * @generated from field: string page_token = 4; + */ + pageToken = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.ListRoleMembersRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "filter", kind: "message", T: ListRoleMembersRequest_Filter, opt: true }, + { no: 3, name: "page_size", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, + { no: 4, name: "page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListRoleMembersRequest { + return new ListRoleMembersRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListRoleMembersRequest { + return new ListRoleMembersRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListRoleMembersRequest { + return new ListRoleMembersRequest().fromJsonString(jsonString, options); + } + + static equals(a: ListRoleMembersRequest | PlainMessage | undefined, b: ListRoleMembersRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(ListRoleMembersRequest, a, b); + } +} + +/** + * Filter options. + * + * @generated from message redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter + */ +export class ListRoleMembersRequest_Filter extends Message { + /** + * Filter results to only members with names which contain the string. + * + * @generated from field: string name_contains = 1; + */ + nameContains = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "name_contains", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListRoleMembersRequest_Filter { + return new ListRoleMembersRequest_Filter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListRoleMembersRequest_Filter { + return new ListRoleMembersRequest_Filter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListRoleMembersRequest_Filter { + return new ListRoleMembersRequest_Filter().fromJsonString(jsonString, options); + } + + static equals(a: ListRoleMembersRequest_Filter | PlainMessage | undefined, b: ListRoleMembersRequest_Filter | PlainMessage | undefined): boolean { + return proto3.util.equals(ListRoleMembersRequest_Filter, a, b); + } +} + +/** + * ListRoleMembersResponse is the response for ListRoleMembers. + * + * @generated from message redpanda.api.console.v1alpha1.ListRoleMembersResponse + */ +export class ListRoleMembersResponse extends Message { + /** + * The role name. + * + * @generated from field: string role_name = 1; + */ + roleName = ""; + + /** + * Members assigned to the role. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership members = 2; + */ + members: RoleMembership[] = []; + + /** + * Token to retrieve the next page. + * + * @generated from field: string next_page_token = 3; + */ + nextPageToken = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.ListRoleMembersResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "members", kind: "message", T: RoleMembership, repeated: true }, + { no: 3, name: "next_page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListRoleMembersResponse { + return new ListRoleMembersResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListRoleMembersResponse { + return new ListRoleMembersResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListRoleMembersResponse { + return new ListRoleMembersResponse().fromJsonString(jsonString, options); + } + + static equals(a: ListRoleMembersResponse | PlainMessage | undefined, b: ListRoleMembersResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(ListRoleMembersResponse, a, b); + } +} + +/** + * RoleMembership is the role membership. + * + * @generated from message redpanda.api.console.v1alpha1.RoleMembership + */ +export class RoleMembership extends Message { + /** + * The name of the principal assigned to the role. + * + * @generated from field: string principal = 1; + */ + principal = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.RoleMembership"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "principal", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): RoleMembership { + return new RoleMembership().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): RoleMembership { + return new RoleMembership().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): RoleMembership { + return new RoleMembership().fromJsonString(jsonString, options); + } + + static equals(a: RoleMembership | PlainMessage | undefined, b: RoleMembership | PlainMessage | undefined): boolean { + return proto3.util.equals(RoleMembership, a, b); + } +} + +/** + * UpdateRoleMembershipRequest is the request to UpdateRoleMembership. + * + * @generated from message redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest + */ +export class UpdateRoleMembershipRequest extends Message { + /** + * The role name. + * + * @generated from field: string role_name = 1; + */ + roleName = ""; + + /** + * Create the role if it doesn't already exist. + * If the role is created in this way, the “add” list will be respected, but the “remove” list will be ignored. + * + * @generated from field: bool create = 2; + */ + create = false; + + /** + * Members to assign to the role. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership add = 3; + */ + add: RoleMembership[] = []; + + /** + * Members to remove from the role. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership remove = 4; + */ + remove: RoleMembership[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "create", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 3, name: "add", kind: "message", T: RoleMembership, repeated: true }, + { no: 4, name: "remove", kind: "message", T: RoleMembership, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): UpdateRoleMembershipRequest { + return new UpdateRoleMembershipRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): UpdateRoleMembershipRequest { + return new UpdateRoleMembershipRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): UpdateRoleMembershipRequest { + return new UpdateRoleMembershipRequest().fromJsonString(jsonString, options); + } + + static equals(a: UpdateRoleMembershipRequest | PlainMessage | undefined, b: UpdateRoleMembershipRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(UpdateRoleMembershipRequest, a, b); + } +} + +/** + * UpdateRoleMembershipResponse is the response for UpdateRoleMembership. + * + * @generated from message redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse + */ +export class UpdateRoleMembershipResponse extends Message { + /** + * The role name. + * + * @generated from field: string role_name = 1; + */ + roleName = ""; + + /** + * Members assigned to the role. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership added = 2; + */ + added: RoleMembership[] = []; + + /** + * Members removed from the role. + * + * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership removed = 3; + */ + removed: RoleMembership[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "added", kind: "message", T: RoleMembership, repeated: true }, + { no: 3, name: "removed", kind: "message", T: RoleMembership, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): UpdateRoleMembershipResponse { + return new UpdateRoleMembershipResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): UpdateRoleMembershipResponse { + return new UpdateRoleMembershipResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): UpdateRoleMembershipResponse { + return new UpdateRoleMembershipResponse().fromJsonString(jsonString, options); + } + + static equals(a: UpdateRoleMembershipResponse | PlainMessage | undefined, b: UpdateRoleMembershipResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(UpdateRoleMembershipResponse, a, b); + } +} + diff --git a/frontend/src/state/backendApi.ts b/frontend/src/state/backendApi.ts index 45d192e7b..210b741c4 100644 --- a/frontend/src/state/backendApi.ts +++ b/frontend/src/state/backendApi.ts @@ -21,47 +21,94 @@ import { ObjToKv } from '../utils/tsxUtils'; import { decodeBase64, TimeSince } from '../utils/utils'; import { appGlobal } from './appGlobal'; import { - GetAclsRequest, AclRequestDefault, GetAclOverviewResponse, AdminInfo, - AlterConfigOperation, AlterPartitionReassignmentsResponse, ApiError, - Broker, BrokerConfigResponse, ClusterAdditionalInfo, ClusterConnectors, - ClusterInfo, ClusterInfoResponse, ConfigEntry, ConfigResourceType, - ConnectorValidationResult, CreateTopicRequest, CreateTopicResponse, - DeleteConsumerGroupOffsetsRequest, DeleteConsumerGroupOffsetsResponse, - DeleteConsumerGroupOffsetsResponseTopic, DeleteConsumerGroupOffsetsTopic, - DeleteRecordsResponseData, EditConsumerGroupOffsetsRequest, - EditConsumerGroupOffsetsResponse, EditConsumerGroupOffsetsResponseTopic, - EditConsumerGroupOffsetsTopic, EndpointCompatibility, - EndpointCompatibilityResponse, GetAllPartitionsResponse, - GetConsumerGroupResponse, GetConsumerGroupsResponse, GetPartitionsResponse, - GetTopicConsumersResponse, GetTopicOffsetsByTimestampResponse, - GetTopicsResponse, GroupDescription, isApiError, KafkaConnectors, - PartialTopicConfigsResponse, Partition, PartitionReassignmentRequest, - PartitionReassignments, PartitionReassignmentsResponse, - PatchConfigsRequest, PatchConfigsResponse, ProduceRecordsResponse, - PublishRecordsRequest, QuotaResponse, ResourceConfig, - Topic, TopicConfigResponse, TopicConsumer, TopicDescription, - TopicDocumentation, TopicDocumentationResponse, TopicOffset, - TopicPermissions, UserData, WrappedApiError, CreateACLRequest, - DeleteACLsRequest, RedpandaLicense, AclResource, GetUsersResponse, CreateUserRequest, - PatchTopicConfigsRequest, CreateSecretResponse, ClusterOverview, BrokerWithConfigAndStorage, + AclRequestDefault, + AclResource, + AdminInfo, + AlterConfigOperation, + AlterPartitionReassignmentsResponse, + ApiError, + Broker, + BrokerConfigResponse, + BrokerWithConfigAndStorage, + ClusterAdditionalInfo, + ClusterConnectors, + ClusterInfo, + ClusterInfoResponse, + ClusterOverview, + CompressionType, + ConfigEntry, + ConfigResourceType, + ConnectorValidationResult, + CreateACLRequest, + CreateSecretResponse, + CreateTopicRequest, + CreateTopicResponse, + CreateUserRequest, + DeleteACLsRequest, + DeleteConsumerGroupOffsetsRequest, + DeleteConsumerGroupOffsetsResponse, + DeleteConsumerGroupOffsetsResponseTopic, + DeleteConsumerGroupOffsetsTopic, + DeleteRecordsResponseData, + EditConsumerGroupOffsetsRequest, + EditConsumerGroupOffsetsResponse, + EditConsumerGroupOffsetsResponseTopic, + EditConsumerGroupOffsetsTopic, + EndpointCompatibility, + EndpointCompatibilityResponse, + GetAclOverviewResponse, + GetAclsRequest, + GetAllPartitionsResponse, + GetConsumerGroupResponse, + GetConsumerGroupsResponse, + GetPartitionsResponse, + GetTopicConsumersResponse, + GetTopicOffsetsByTimestampResponse, + GetTopicsResponse, + GetUsersResponse, + GroupDescription, + isApiError, + KafkaConnectors, OverviewNewsEntry, + PartialTopicConfigsResponse, + Partition, + PartitionReassignmentRequest, + PartitionReassignments, + PartitionReassignmentsResponse, + PatchConfigsRequest, + PatchConfigsResponse, + PatchTopicConfigsRequest, Payload, - SchemaRegistrySubject, - SchemaRegistrySubjectDetails, - SchemaRegistryModeResponse, + ProduceRecordsResponse, + PublishRecordsRequest, + QuotaResponse, + RedpandaLicense, + ResourceConfig, + SchemaReferencedByEntry, + SchemaRegistryCompatibilityMode, SchemaRegistryConfigResponse, - SchemaRegistrySchemaTypesResponse, - SchemaRegistryCreateSchemaResponse, SchemaRegistryCreateSchema, - SchemaRegistryDeleteSubjectVersionResponse, + SchemaRegistryCreateSchemaResponse, SchemaRegistryDeleteSubjectResponse, - SchemaRegistryCompatibilityMode, + SchemaRegistryDeleteSubjectVersionResponse, + SchemaRegistryModeResponse, + SchemaRegistrySchemaTypesResponse, SchemaRegistrySetCompatibilityModeRequest, - SchemaReferencedByEntry, + SchemaRegistrySubject, + SchemaRegistrySubjectDetails, SchemaRegistryValidateSchemaResponse, SchemaVersion, + Topic, + TopicConfigResponse, + TopicConsumer, + TopicDescription, + TopicDocumentation, + TopicDocumentationResponse, TopicMessage, - CompressionType + TopicOffset, + TopicPermissions, + UserData, + WrappedApiError } from './restInterfaces'; import { uiState } from './uiState'; import { config as appConfig, isEmbedded } from '../config'; @@ -69,9 +116,10 @@ import { createStandaloneToast, redpandaTheme, redpandaToastOptions } from '@red import { proto3 } from '@bufbuild/protobuf'; import { ListMessagesRequest } from '../protogen/redpanda/api/console/v1alpha1/list_messages_pb'; -import { PayloadEncoding, CompressionType as ProtoCompressionType } from '../protogen/redpanda/api/console/v1alpha1/common_pb'; +import { CompressionType as ProtoCompressionType, PayloadEncoding } from '../protogen/redpanda/api/console/v1alpha1/common_pb'; import { PublishMessageRequest, PublishMessageResponse } from '../protogen/redpanda/api/console/v1alpha1/publish_messages_pb'; import { PartitionOffsetOrigin } from './ui'; +import { Features } from './supportedFeatures'; const REST_TIMEOUT_SEC = 25; export const REST_CACHE_DURATION_SEC = 20; @@ -1486,6 +1534,109 @@ const apiStore = { }; +export type RolePrincipal = { name: string, principalType: 'User' }; +export const rolesApi = observable({ + roles: [] as string[], + roleMembers: new Map(), // RoleName -> Principals + + async refreshRoles(): Promise { + const client = appConfig.securityClient; + if (!client) throw new Error('security client is not initialized'); + + const roles: string[] = []; + + if (Features.rolesApi) { + let nextPageToken = ''; + while (true) { + const res = await client.listRoles({ pageSize: 500, pageToken: nextPageToken }); + + const newRoles = res.roles.map(x => x.name); + roles.push(...newRoles); + + if (!res.nextPageToken || res.nextPageToken.length == 0) + break; + + nextPageToken = res.nextPageToken; + } + } + + this.roles = roles; + }, + + async refreshRoleMembers() { + const client = appConfig.securityClient; + if (!client) throw new Error('security client is not initialized'); + + const rolePromises = []; + + if (Features.rolesApi) { + for (const role of this.roles) { + rolePromises.push(client.getRole({ roleName: role })); + } + } + + await Promise.allSettled(rolePromises); + + this.roleMembers.clear(); + + for (const r of rolePromises) { + const res = await r; + if (res.role == null) continue; // how could this ever happen, maybe someone deleted the role right before we retreived the members? + const roleName = res.role.name; + + const members = res.members.map(x => { + + const principalParts = x.principal.split(':'); + if (principalParts.length != 2) { + console.error('failed to split principal of role', { roleName, principal: x.principal }); + return null; + } + const principalType = principalParts[0]; + const name = principalParts[1]; + + if (principalType != 'User') { + console.error('unexpected principal type in refreshRoleMembers', { roleName, principal: x.principal }); + } + + return { principalType, name } as RolePrincipal; + }).filterNull(); + + this.roleMembers.set(roleName, members); + } + }, + + async createRole(name: string) { + const client = appConfig.securityClient; + if (!client) throw new Error('security client is not initialized'); + + if (Features.rolesApi) { + await client.createRole({ role: { name } }); + } + }, + + async deleteRole(name: string, deleteAcls: boolean) { + const client = appConfig.securityClient; + if (!client) throw new Error('security client is not initialized'); + + if (Features.rolesApi) { + await client.deleteRole({ roleName: name, deleteAcls }); + } + }, + + async updateRoleMembership(roleName: string, addUsers: string[], removeUsers: string[], create = false) { + const client = appConfig.securityClient; + if (!client) throw new Error('security client is not initialized'); + + return await client.updateRoleMembership({ + roleName: roleName, + add: addUsers.map(u => ({ principal: 'User:' + u })), + remove: removeUsers.map(u => ({ principal: 'User:' + u })), + create, + }); + }, +}); + + export function createMessageSearch() { const messageSearch = { // Parameters last passed to 'startMessageSearch' diff --git a/frontend/src/state/restInterfaces.ts b/frontend/src/state/restInterfaces.ts index ae2fbf397..4ba9cab36 100644 --- a/frontend/src/state/restInterfaces.ts +++ b/frontend/src/state/restInterfaces.ts @@ -692,6 +692,7 @@ export type AclStrResourceType = | 'Cluster' | 'TransactionalID' | 'DelegationToken' + | 'RedpandaRole' ; export type AclStrResourcePatternType = diff --git a/frontend/src/state/supportedFeatures.ts b/frontend/src/state/supportedFeatures.ts index b3659bb01..bb20b4102 100644 --- a/frontend/src/state/supportedFeatures.ts +++ b/frontend/src/state/supportedFeatures.ts @@ -37,6 +37,7 @@ export class Feature { static readonly GetQuotas: FeatureEntry = { endpoint: '/api/quotas', method: 'GET' }; static readonly CreateUser: FeatureEntry = { endpoint: '/api/users', method: 'POST' }; static readonly DeleteUser: FeatureEntry = { endpoint: '/api/users', method: 'DELETE' }; + static readonly SecurityService: FeatureEntry = { endpoint: 'redpanda.api.console.v1alpha1.SecurityService', method: 'POST' }; } export function isSupported(f: FeatureEntry): boolean { @@ -48,6 +49,10 @@ export function isSupported(f: FeatureEntry): boolean { if (e.endpoint == f.endpoint) return e.isSupported; + // Special handling, this will be completely absent in the community version + if (f.endpoint.includes('.SecurityService')) + return false; + featureErrors.push(`Unable to check if feature "${f.method} ${f.endpoint}" is supported because the backend did not return any information about it.`); return false; } @@ -72,6 +77,7 @@ class SupportedFeatures { @computed get getQuotas(): boolean { return isSupported(Feature.GetQuotas); } @computed get createUser(): boolean { return isSupported(Feature.CreateUser); } @computed get deleteUser(): boolean { return isSupported(Feature.DeleteUser); } + @computed get rolesApi(): boolean { return isSupported(Feature.SecurityService); } } const features = new SupportedFeatures(); diff --git a/frontend/src/state/ui.ts b/frontend/src/state/ui.ts index eea131df6..b5961ba36 100644 --- a/frontend/src/state/ui.ts +++ b/frontend/src/state/ui.ts @@ -233,6 +233,15 @@ const defaultUiSettings = { }, aclList: { + usersTab: { + quickSearch: '', + pageSize: 20, + }, + rolesTab: { + quickSearch: '', + pageSize: 20, + }, + configTable: { quickSearch: '', pageSize: 20, diff --git a/proto/gen/openapi/openapi.json b/proto/gen/openapi/openapi.json index 727856edc..818b80b86 100644 --- a/proto/gen/openapi/openapi.json +++ b/proto/gen/openapi/openapi.json @@ -1 +1 @@ -{"components":{"schemas":{"ACL.Operation":{"description":"The operation that is allowed or denied (e.g. READ).","enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"},"BadRequest":{"description":"Describes violations in a client request. This error type focuses on the\nsyntactic aspects of the request.","properties":{"field_violations":{"description":"Describes all violations in a client request.","items":{"$ref":"#/components/schemas/FieldViolation"},"type":"array"}},"title":"BadRequest","type":"object"},"Config":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConfigAlterOperation":{"enum":["CONFIG_ALTER_OPERATION_SET","CONFIG_ALTER_OPERATION_DELETE","CONFIG_ALTER_OPERATION_APPEND","CONFIG_ALTER_OPERATION_SUBTRACT"],"type":"string"},"ConfigSource":{"enum":["CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG","CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG","CONFIG_SOURCE_STATIC_BROKER_CONFIG","CONFIG_SOURCE_DEFAULT_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG"],"type":"string"},"ConfigSynonym":{"properties":{"name":{"type":"string"},"source":{"$ref":"#/components/schemas/ConfigSource"},"value":{"nullable":true,"type":"string"}},"type":"object"},"ConfigType":{"enum":["CONFIG_TYPE_BOOLEAN","CONFIG_TYPE_STRING","CONFIG_TYPE_INT","CONFIG_TYPE_SHORT","CONFIG_TYPE_LONG","CONFIG_TYPE_DOUBLE","CONFIG_TYPE_LIST","CONFIG_TYPE_CLASS","CONFIG_TYPE_PASSWORD"],"type":"string"},"Configuration":{"properties":{"config_synonyms":{"description":"If no config value is set at the topic level, it will inherit the value\nset at the broker or cluster level. `name` is the corresponding config\nkey whose value is inherited. `source` indicates whether the inherited\nconfig is default, broker, etc.","items":{"$ref":"#/components/schemas/ConfigSynonym"},"type":"array"},"documentation":{"description":"Config documentation.","nullable":true,"type":"string"},"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"read_only":{"description":"Whether the config is read-only, or is dynamic and can be altered.","type":"boolean"},"sensitive":{"description":"Whether this is a sensitive config key and value.","type":"boolean"},"source":{"$ref":"#/components/schemas/ConfigSource"},"type":{"$ref":"#/components/schemas/ConfigType"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConnectCluster":{"properties":{"address":{"type":"string"},"info":{"$ref":"#/components/schemas/ConnectCluster.Info"},"name":{"type":"string"},"plugins":{"items":{"$ref":"#/components/schemas/ConnectorPlugin"},"type":"array"}},"type":"object"},"ConnectCluster.Info":{"properties":{"commit":{"type":"string"},"kafka_cluster_id":{"type":"string"},"version":{"type":"string"}},"type":"object"},"Connector":{"properties":{"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"ConnectorError":{"properties":{"content":{"type":"string"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ConnectorError.Type"}},"title":"ConnectorError is the error of a connector, this is holistic error\nabstraction, made parsing the error trace of connector or Task","type":"object"},"ConnectorError.Type":{"enum":["TYPE_ERROR","TYPE_WARNING"],"type":"string"},"ConnectorHolisticState":{"description":"- CONNECTOR_HOLISTIC_STATE_PAUSED: PAUSED: The connector/task has been administratively paused.\n - CONNECTOR_HOLISTIC_STATE_RESTARTING: RESTARTING: he connector/task is restarting.\n - CONNECTOR_HOLISTIC_STATE_DESTROYED: DESTROYED: Connector is in destroyed state, regardless of any tasks.\n - CONNECTOR_HOLISTIC_STATE_STOPPED: STOPPED: The connector/task has been stopped.\n - CONNECTOR_HOLISTIC_STATE_UNASSIGNED: The connector/task has not yet been assigned to a worker\nUNASSIGNED: Connector is in unassigned state.\n Or Connector is in running state, and there are unassigned tasks.\n - CONNECTOR_HOLISTIC_STATE_HEALTHY: HEALTHY: Connector is in running state, \u003e 0 tasks, all of them in running state.\n - CONNECTOR_HOLISTIC_STATE_UNHEALTHY: UNHEALTHY: Connector is failed state.\n\t\t\tOr Connector is in running state but has 0 tasks.\n\t\t\tOr Connector is in running state, has \u003e 0 tasks, and all tasks are in failed state.\n - CONNECTOR_HOLISTIC_STATE_DEGRADED: DEGRADED: Connector is in running state, has \u003e 0 tasks, but has at least one state in failed state, but not all tasks are failed.\n - CONNECTOR_HOLISTIC_STATE_UNKNOWN: UNKNOWN: The connector/task could no be determined","enum":["CONNECTOR_HOLISTIC_STATE_PAUSED","CONNECTOR_HOLISTIC_STATE_RESTARTING","CONNECTOR_HOLISTIC_STATE_DESTROYED","CONNECTOR_HOLISTIC_STATE_STOPPED","CONNECTOR_HOLISTIC_STATE_UNASSIGNED","CONNECTOR_HOLISTIC_STATE_HEALTHY","CONNECTOR_HOLISTIC_STATE_UNHEALTHY","CONNECTOR_HOLISTIC_STATE_DEGRADED","CONNECTOR_HOLISTIC_STATE_UNKNOWN"],"title":"The following states are possible for a connector or one of its tasks\nimplement the state interface described in the Kafka connect API @see\nhttps://docs.confluent.io/platform/current/connect/monitoring.html#connector-and-task-status\nthis includes holistic unified connector status that takes into account not\njust the connector instance state, but also state of all the tasks within the\nconnector","type":"string"},"ConnectorInfoStatus":{"properties":{"info":{"$ref":"#/components/schemas/ConnectorSpec"},"name":{"title":"name is the connector name","type":"string"},"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"ConnectorPlugin":{"properties":{"class":{"type":"string"},"type":{"type":"string"},"version":{"type":"string"}},"type":"object"},"ConnectorSpec":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskInfo"},"readOnly":true,"type":"array"},"type":{"readOnly":true,"type":"string"}},"required":["name","config"],"title":"ConectorInfo is the spec of the connector, as defined in the Kafka connect\nAPI, it can be used as input of the connector creation or output of the\nconnectors","type":"object"},"ConnectorStatus":{"properties":{"connector":{"$ref":"#/components/schemas/Connector"},"errors":{"items":{"$ref":"#/components/schemas/ConnectorError"},"title":"Errors is list of parsed connectors' and tasks' errors","type":"array"},"holistic_state":{"$ref":"#/components/schemas/ConnectorHolisticState"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskStatus"},"type":"array"},"type":{"type":"string"}},"type":"object"},"CreateACLRequest":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.\nFor requests with resource_type CLUSTER, this will default to \"kafka-cluster\".","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","principal","host","operation","permission_type"],"type":"object"},"CreateACLResponse":{"type":"object"},"CreateConnectSecretResponse":{"description":"CreateConnectSecretResponse is the response of CreateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"CreateConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"CreateTopicRequest.Topic":{"properties":{"configs":{"description":"An array of key-value config pairs for a topic.\nThese correspond to Kafka topic-level configs.","items":{"$ref":"#/components/schemas/Config"},"type":"array"},"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions to give the topic. If specifying\npartitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default partition count, set to null.","format":"int32","nullable":true,"type":"integer"},"replica_assignments":{"description":"Manually specify broker ID assignments for partition replicas. If manually assigning replicas, both `replication_factor` and\n`partition_count` must be -1.","items":{"$ref":"#/components/schemas/ReplicaAssignment"},"type":"array"},"replication_factor":{"description":"The number of replicas every partition must have.\nIf specifying partitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default replication factor, set to null.","format":"int32","nullable":true,"type":"integer"}},"type":"object"},"CreateTopicResponse":{"properties":{"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions created for the topic.\nThis field has a default value of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"},"replication_factor":{"description":"The number of replicas per topic partition.\nThis field has a default of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"}},"type":"object"},"CreateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"CreateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/CreateUserResponse.User"}},"type":"object"},"CreateUserResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"title":"Name of newly-created user","type":"string"}},"type":"object"},"DeleteACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","operation","permission_type"],"type":"object"},"DeleteACLsResponse":{"properties":{"matching_acls":{"items":{"$ref":"#/components/schemas/MatchingACL"},"type":"array"}},"type":"object"},"DeleteConnectSecretResponse":{"description":"DeleteConnectSecretResponse is the response of DeleteConnectSecret.","type":"object"},"DeleteTopicResponse":{"type":"object"},"DeleteTransformResponse":{"type":"object"},"DeleteUserResponse":{"type":"object"},"DeployTransformRequest":{"description":"Metadata required to deploy a new Wasm\ntransform in a Redpanda cluster.","properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"The input topic to apply the transform to.","example":"orders","type":"string"},"name":{"description":"Name of the transform.","example":"redact-payment-details-in-orders","type":"string"},"output_topic_names":{"description":"Output topic to write the transform results to.","example":"orders-redacted","items":{"type":"string"},"type":"array"}},"required":["name","input_topic_name","output_topic_names"],"type":"object"},"EnvironmentVariable":{"properties":{"key":{"description":"The key of your environment variable.","example":"LOG_LEVEL","type":"string"},"value":{"description":"The value of your environment variable.","example":"DEBUG","type":"string"}},"required":["key","value"],"type":"object"},"ErrorInfo":{"description":"Describes the cause of the error with structured details.\n\nExample of an error when contacting the \"pubsub.googleapis.com\" API when it\nis not enabled:\n\n { \"reason\": \"API_DISABLED\"\n \"domain\": \"googleapis.com\"\n \"metadata\": {\n \"resource\": \"projects/123\",\n \"service\": \"pubsub.googleapis.com\"\n }\n }\n\nThis response indicates that the pubsub.googleapis.com API is not enabled.\n\nExample of an error that is returned when attempting to create a Spanner\ninstance in a region that is out of stock:\n\n { \"reason\": \"STOCKOUT\"\n \"domain\": \"spanner.googleapis.com\",\n \"metadata\": {\n \"availableRegions\": \"us-central1,us-east2\"\n }\n }","properties":{"domain":{"description":"The logical grouping to which the \"reason\" belongs. The error domain\nis typically the registered service name of the tool or product that\ngenerates the error. Example: \"pubsub.googleapis.com\". If the error is\ngenerated by some common infrastructure, the error domain must be a\nglobally unique value that identifies the infrastructure. For Google API\ninfrastructure, the error domain is \"googleapis.com\".","type":"string"},"metadata":{"additionalProperties":{"type":"string"},"description":"Additional structured details about this error.\n\nKeys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in\nlength. When identifying the current value of an exceeded limit, the units\nshould be contained in the key, not the value. For example, rather than\n{\"instanceLimit\": \"100/request\"}, should be returned as,\n{\"instanceLimitPerRequest\": \"100\"}, if the client exceeds the number of\ninstances that can be created in a single (batch) request.","type":"object"},"reason":{"description":"The reason of the error. This is a constant value that identifies the\nproximate cause of the error. Error reasons are unique within a particular\ndomain of errors. This should be at most 63 characters and match a\nregular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents\nUPPER_SNAKE_CASE.","type":"string"}},"title":"ErrorInfo","type":"object"},"FieldViolation":{"description":"A message type used to describe a single bad request field.","properties":{"description":{"description":"A description of why the request element is bad.","type":"string"},"field":{"description":"A path that leads to a field in the request body. The value will be a\nsequence of dot-separated identifiers that identify a protocol buffer\nfield.\n\nConsider the following:\n\n message CreateContactRequest {\n message EmailAddress {\n enum Type {\n TYPE_UNSPECIFIED = 0;\n HOME = 1;\n WORK = 2;\n }\n\n optional string email = 1;\n repeated EmailType type = 2;\n }\n\n string full_name = 1;\n repeated EmailAddress email_addresses = 2;\n }\n\nIn this example, in proto `field` could take one of the following values:\n\n* `full_name` for a violation in the `full_name` value\n* `email_addresses[1].email` for a violation in the `email` field of the\n first `email_addresses` message\n* `email_addresses[3].type[2]` for a violation in the second `type`\n value in the third `email_addresses` message.\n\nIn JSON, the same values are represented as:\n\n* `fullName` for a violation in the `fullName` value\n* `emailAddresses[1].email` for a violation in the `email` field of the\n first `emailAddresses` message\n* `emailAddresses[3].type[2]` for a violation in the second `type`\n value in the third `emailAddresses` message.","type":"string"}},"type":"object"},"GetConnectClusterResponse":{"properties":{"cluster":{"$ref":"#/components/schemas/ConnectCluster"}},"type":"object"},"GetConnectSecretResponse":{"description":"GetConnectSecretResponse is the response of GetConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"GetConnectorConfigResponse":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"GetConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"GetConnectorStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"GetTopicConfigurationsResponse":{"properties":{"configurations":{"items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"GetTransformResponse":{"properties":{"transform":{"$ref":"#/components/schemas/TransformMetadata"}},"type":"object"},"Help":{"description":"Provides links to documentation or for performing an out of band action.\n\nFor example, if a quota check failed with an error indicating the calling\nproject hasn't enabled the accessed service, this can contain a URL pointing\ndirectly to the right place in the developer console to flip the bit.","properties":{"links":{"description":"URL(s) pointing to additional information on handling the current error.","items":{"$ref":"#/components/schemas/Link"},"type":"array"}},"title":"Help","type":"object"},"Link":{"description":"Describes a URL link.","properties":{"description":{"description":"Describes what the link offers.","type":"string"},"url":{"description":"The URL of the link.","type":"string"}},"type":"object"},"ListACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ListACLsResponse":{"properties":{"resources":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"type":"object"},"ListConnectClustersResponse":{"properties":{"clusters":{"items":{"$ref":"#/components/schemas/ConnectCluster"},"type":"array"}},"type":"object"},"ListConnectSecretsResponse":{"description":"ListConnectSecretsResponse is the response of ListConnectSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListConnectorTopicsResponse":{"properties":{"topics":{"items":{"type":"string"},"type":"array"}},"type":"object"},"ListConnectorsResponse":{"properties":{"connectors":{"items":{"$ref":"#/components/schemas/ConnectorInfoStatus"},"title":"connectors is the list of connectors the key is the connector name","type":"array"},"next_page_token":{"description":"Page Token to fetch the next page. The value can be used as page_token in the next call to this endpoint.","type":"string"}},"type":"object"},"ListSecretsFilter":{"description":"ListSecretsFilter are the filter options for listing secrets.","properties":{"labels[string]":{"additionalProperties":{"type":"string"},"description":"The secret labels to search for.","type":"object"},"name_contains":{"description":"Substring match on secret name. Case-sensitive.","type":"string"}},"type":"object"},"ListSecretsResponse":{"description":"ListSecretsResponse is the response of ListSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListTopicsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on topic name. Case-sensitive.","type":"string"}},"type":"object"},"ListTopicsResponse":{"properties":{"next_page_token":{"type":"string"},"topics":{"items":{"$ref":"#/components/schemas/ListTopicsResponse.Topic"},"type":"array"}},"type":"object"},"ListTopicsResponse.Topic":{"properties":{"internal":{"description":"Whether topic is internal only.","type":"boolean"},"name":{"description":"Topic name.","type":"string"},"partition_count":{"description":"Topic partition count.","format":"int32","type":"integer"},"replication_factor":{"description":"Topic replication factor.","format":"int32","type":"integer"}},"type":"object"},"ListTransformsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on transform name. Case-sensitive.","type":"string"}},"type":"object"},"ListTransformsResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"transforms":{"items":{"$ref":"#/components/schemas/TransformMetadata"},"type":"array"}},"type":"object"},"ListUsersRequest.Filter":{"properties":{"name":{"description":"Username.","type":"string"},"name_contains":{"description":"Substring match on username. Case-sensitive.","type":"string"}},"type":"object"},"ListUsersResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"users":{"items":{"$ref":"#/components/schemas/ListUsersResponse.User"},"type":"array"}},"type":"object"},"ListUsersResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"}},"type":"object"},"MatchingACL":{"properties":{"error":{"$ref":"#/components/schemas/Status"},"host":{"description":"The host address to for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"Options":{"properties":{"include_tasks":{"type":"boolean"},"only_failed":{"type":"boolean"}},"type":"object"},"PartitionStatus":{"enum":["PARTITION_STATUS_RUNNING","PARTITION_STATUS_INACTIVE","PARTITION_STATUS_ERRORED","PARTITION_STATUS_UNKNOWN"],"type":"string"},"PartitionTransformStatus":{"properties":{"broker_id":{"format":"int32","type":"integer"},"lag":{"format":"int32","type":"integer"},"partition_id":{"format":"int32","type":"integer"},"status":{"$ref":"#/components/schemas/PartitionStatus"}},"type":"object"},"PermissionType":{"description":"Whether the operation should be allowed or denied.","enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"},"Policy":{"properties":{"host":{"description":"The host address for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"}},"type":"object"},"QuotaFailure":{"description":"Describes how a quota check failed.\n\nFor example if a daily limit was exceeded for the calling project,\na service could respond with a QuotaFailure detail containing the project\nid and the description of the quota limit that was exceeded. If the\ncalling project hasn't enabled the service in the developer console, then\na service could respond with the project id and set `service_disabled`\nto true.\n\nAlso see RetryInfo and Help types for other details about handling a\nquota failure.","properties":{"violations":{"description":"Describes all quota violations.","items":{"$ref":"#/components/schemas/QuotaFailure.Violation"},"type":"array"}},"title":"QuotaFailure","type":"object"},"QuotaFailure.Violation":{"description":"A message type used to describe a single quota violation. For example, a\ndaily quota or a custom quota that was exceeded.","properties":{"description":{"description":"A description of how the quota check failed. Clients can use this\ndescription to find more about the quota configuration in the service's\npublic documentation, or find the relevant quota limit to adjust through\ndeveloper console.\n\nFor example: \"Service disabled\" or \"Daily Limit for read operations\nexceeded\".","type":"string"},"subject":{"description":"The subject on which the quota check failed.\nFor example, \"clientip:\u003cip address of client\u003e\" or \"project:\u003cGoogle\ndeveloper project id\u003e\".","type":"string"}},"type":"object"},"ReplicaAssignment":{"properties":{"partition_id":{"description":"A partition to create.","format":"int32","type":"integer"},"replica_ids":{"description":"The broker IDs the partition replicas are assigned to.","items":{"format":"int32","type":"integer"},"type":"array"}},"type":"object"},"Resource":{"properties":{"acls":{"items":{"$ref":"#/components/schemas/Policy"},"type":"array"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ResourcePatternType":{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"},"ResourceType":{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"},"SASLMechanism":{"description":"SASL mechanism to use for authentication.","enum":["SASL_MECHANISM_SCRAM_SHA_256","SASL_MECHANISM_SCRAM_SHA_512"],"type":"string"},"Secret":{"description":"Defines the secret resource.","properties":{"id":{"description":"Secret identifier.","readOnly":true,"type":"string"},"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"}},"type":"object"},"SetConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"SetTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after this update.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"Status":{"description":"The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors).","properties":{"code":{"description":"RPC status code, as described [here](https://github.com/googleapis/googleapis/blob/b4c238feaa1097c53798ed77035bbfeb7fc72e96/google/rpc/code.proto#L32).","enum":["OK","CANCELLED","UNKNOWN","INVALID_ARGUMENT","DEADLINE_EXCEEDED","NOT_FOUND","ALREADY_EXISTS","PERMISSION_DENIED","UNAUTHENTICATED","RESOURCE_EXHAUSTED","FAILED_PRECONDITION","ABORTED","OUT_OF_RANGE","UNIMPLEMENTED","INTERNAL","UNAVAILABLE","DATA_LOSS"],"format":"int32","type":"string"},"details":{"items":{"description":"Details of the error.","oneOf":[{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.BadRequest"],"type":"string"}}},{"$ref":"#/components/schemas/BadRequest"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.ErrorInfo"],"type":"string"}}},{"$ref":"#/components/schemas/ErrorInfo"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.QuotaFailure"],"type":"string"}}},{"$ref":"#/components/schemas/QuotaFailure"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.Help"],"type":"string"}}},{"$ref":"#/components/schemas/Help"}]}]},"type":"array"},"message":{"description":"Detailed error message. No compatibility guarantees are given for the text contained in this message.","type":"string"}},"type":"object"},"TaskInfo":{"properties":{"connector":{"type":"string"},"task":{"format":"int32","type":"integer"}},"type":"object"},"TaskStatus":{"properties":{"id":{"format":"int32","type":"integer"},"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"TransformMetadata":{"properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"Input topic to apply the transform to.","type":"string"},"name":{"description":"Name of transform.","type":"string"},"output_topic_names":{"description":"Output topics to write the transform results to.","items":{"type":"string"},"type":"array"},"statuses":{"items":{"$ref":"#/components/schemas/PartitionTransformStatus"},"type":"array"}},"type":"object"},"UpdateConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"operation":{"$ref":"#/components/schemas/ConfigAlterOperation"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"UpdateConnectSecretResponse":{"description":"UpdateConnectSecretResponse is the response of UpdateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"UpdateTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after applying this partial patch.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"UpdateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"UpdateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/UpdateUserResponse.User"}},"type":"object"},"UpdateUserResponse.User":{"description":"Updated user's name and SASL mechanism.","properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"type":"string"}},"type":"object"},"UpsertConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"v1alpha1.Topic":{"type":"object"}},"securitySchemes":{"auth0":{"description":"RedpandaCloud","flows":{"implicit":{"authorizationUrl":"https://prod-cloudv2.us.auth0.com/oauth/authorize","scopes":{},"x-client-id":"dQjapNIAHhF7EQqQToRla3yEII9sUSap"}},"type":"oauth2"}}},"info":{"description":"Welcome to Redpanda Cloud's Dataplane API documentation.","title":"Redpanda Cloud","version":"v1alpha1"},"openapi":"3.0.3","paths":{"/v1alpha1/acls":{"delete":{"description":"Delete all ACLs that match the filter criteria. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_DeleteACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","required":true,"schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","required":true,"schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","required":true,"schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","required":true,"schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete ACLs","tags":["ACLService"]},"get":{"description":"List all ACLs. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_ListACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List ACLs","tags":["ACLService"]},"post":{"description":"Create a new ACL.","operationId":"ACLService_CreateACL","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLRequest"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLResponse"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create ACL","tags":["ACLService"]}},"/v1alpha1/connect/clusters":{"get":{"description":"List connect clusters available for being consumed by the console's kafka-connect service.","operationId":"KafkaConnectService_ListConnectClusters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectClustersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connect clusters","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}":{"get":{"description":"Get information about an available Kafka Connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","operationId":"KafkaConnectService_GetConnectCluster","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectCluster"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Connect cluster not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connect cluster","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors":{"get":{"description":"List connectors managed by the Kafka Connect service.","operationId":"KafkaConnectService_ListConnectors","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connectors","tags":["KafkaConnectService"]},"post":{"description":"Create a connector with the specified configuration.","operationId":"KafkaConnectService_CreateConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"required":true,"x-originalParamName":"connector"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}":{"delete":{"description":"Delete a connector. This operation force stops all tasks and also deletes the connector configuration.","operationId":"KafkaConnectService_DeleteConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete connector","tags":["KafkaConnectService"]},"get":{"description":"Get information about a connector in a specific cluster.","operationId":"KafkaConnectService_GetConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/config":{"get":{"description":"Get the configuration for the connector.","operationId":"KafkaConnectService_GetConnectorConfig","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector configuration","tags":["KafkaConnectService"]},"put":{"description":"Create a new connector using the given configuration, or update the configuration for an existing connector. Returns information about the connector after the change has been made.","operationId":"KafkaConnectService_UpsertConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"required":["config"],"type":"object"}}},"required":true,"x-originalParamName":"config"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Updated"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Upsert connector configuration","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/pause":{"put":{"description":"Pause the connector and its tasks, which stops messages from processing until the connector is resumed. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_PauseConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Pause request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Pause connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/restart":{"post":{"description":"Restarts a connector, triggers a connector restart, you can specify the only_failed or/and the include_tasks options.","operationId":"KafkaConnectService_RestartConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Options"}}},"required":true,"x-originalParamName":"options"},"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Restart connector request success"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Restart connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/resume":{"put":{"description":"Resume a paused connector and its tasks, and resumes message processing. This call is asynchronous and may take some time to process. If the connector was not paused, this operation does not do anything.","operationId":"KafkaConnectService_ResumeConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Resume request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Resume connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/status":{"get":{"description":"Gets the current status of the connector, including the state for each of its tasks, error information, etc.","operationId":"KafkaConnectService_GetConnectorStatus","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorStatus"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector status","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/stop":{"put":{"description":"Stops a connector, but does not delete the connector. All tasks for the connector are shut down completely. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_StopConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Stop connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics":{"get":{"description":"Returns a list of connector topic names. If the connector is inactive, this call returns an empty list.","operationId":"KafkaConnectService_ListConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics/reset":{"put":{"description":"Resets the set of topic names that the connector is using.","operationId":"KafkaConnectService_ResetConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Reset connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets":{"get":{"description":"List Kafka Connect cluster secrets. Optional: filter based on secret name and labels.","operationId":"SecretService_ListConnectSecrets","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Substring match on secret name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"This is a request variable of the map type. The query format is \"map_name[key]=value\", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age[\"bob\"]=18","in":"query","name":"filter.labels[string]","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSecretsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Connect Cluster Secrets","tags":["SecretService"]},"post":{"description":"Create a Kafka Connect cluster secret.","operationId":"SecretService_CreateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"CreateConnectSecretRequest is the request of CreateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"name":{"description":"Name of connector.","type":"string"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["name","secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"Secret created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets/{id}":{"delete":{"description":"Delete a Kafka Connect cluster secret.","operationId":"SecretService_DeleteConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to delete.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Secret deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Connect Cluster Secret","tags":["SecretService"]},"get":{"description":"Get a specific Kafka Connect cluster secret.","operationId":"SecretService_GetConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"The ID of the secret to retrieve.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Connect Cluster Secret","tags":["SecretService"]},"put":{"description":"Update a Kafka Connect cluster secret.","operationId":"SecretService_UpdateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to update.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"UpdateConnectSecretRequest is the request of UpdateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/topics":{"get":{"description":"List topics, with partition count and replication factor. Optional: filter based on topic name.","operationId":"TopicService_ListTopics","parameters":[{"description":"Substring match on topic name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Topics","tags":["TopicService"]},"post":{"description":"Create a topic.","operationId":"TopicService_CreateTopic","parameters":[{"description":"If true, makes this request a dry run; everything is validated but\nno topics are actually created.","in":"query","name":"validate_only","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTopicRequest.Topic"}}},"description":"The topic to create.","required":true,"x-originalParamName":"topic"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1alpha1.Topic"}}},"description":"Topic created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Topic","tags":["TopicService"]}},"/v1alpha1/topics/{name}":{"delete":{"description":"Delete the Kafka topic with the requested name.","operationId":"TopicService_DeleteTopic","parameters":[{"description":"Topic name.","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Topic deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Requested topic does not exist"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Topic","tags":["TopicService"]}},"/v1alpha1/topics/{topic_name}/configurations":{"get":{"description":"Get key-value configs for a topic.","operationId":"TopicService_GetTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Topic Configurations","tags":["TopicService"]},"patch":{"description":"Update a subset of the topic configurations.","operationId":"TopicService_UpdateTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UpdateConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Topic Configuration","tags":["TopicService"]},"put":{"description":"Update the entire set of key-value configurations for a topic. Config entries that are not provided in the request are removed and will fall back to their default values.","operationId":"TopicService_SetTopicConfigurations","parameters":[{"description":"Name of topic.","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SetConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Set Topic Configurations","tags":["TopicService"]}},"/v1alpha1/transforms":{"get":{"description":"Retrieve a list of Wasm transforms. Optional: filter based on transform name.","operationId":"TransformService_ListTransforms","parameters":[{"description":"Substring match on transform name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","transforms":[{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic11","output-topic12"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]},{"environment_variables":[],"input_topic_name":"topic2","name":"transform2","output_topic_names":["output-topic21","output-topic22"],"statuses":[{"broker_id":2,"lag":2,"partition_id":2,"status":"PARTITION_STATUS_RUNNING"}]}]},"schema":{"$ref":"#/components/schemas/ListTransformsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Transforms","tags":["TransformService"]},"put":{"description":"Initiate deployment of a new Wasm transform. This endpoint uses multipart/form-data encoding. Following deployment, a brief period is required before the Wasm transform becomes operational. Monitor the partition statuses to check whether the transform is active. This usually takes around 3s, but no longer than 10s.","operationId":"TransformService_DeployTransform","requestBody":{"content":{"multipart/form-data":{"schema":{"example":"{\"name\":\"redact-orders\",\"input_topic_name\":\"orders\",\"output_topic_names\":[\"orders-redacted\"],\"environment_variables\":[{\"key\":\"LOGGER_LEVEL\",\"value\":\"DEBUG\"}]}","properties":{"metadata":{"$ref":"#/components/schemas/DeployTransformRequest"},"wasm_binary":{"description":"Binary file containing the compiled WASM transform. The maximum size for this file is 10MiB.","format":"binary","type":"string"}},"type":"object"}}},"description":"Transform metadata as well as the WASM binary","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransformMetadata"}}},"description":"Created"}},"summary":"Deploy Transform","tags":["TransformService"]}},"/v1alpha1/transforms/{name}":{"delete":{"description":"Delete a Wasm transform with the requested name.","operationId":"TransformService_DeleteTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"Transform deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Transform","tags":["TransformService"]},"get":{"description":"Get a specific Wasm transform.","operationId":"TransformService_GetTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"transform":{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic1","output-topic2"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]}},"schema":{"$ref":"#/components/schemas/GetTransformResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Transform","tags":["TransformService"]}},"/v1alpha1/users":{"get":{"description":"List users. Optional: filter based on username.","operationId":"UserService_ListUsers","parameters":[{"description":"Username.","in":"query","name":"filter.name","schema":{"type":"string"}},{"description":"Substring match on username. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","users":[{"name":"payment-service"},{"name":"jane"}]},"schema":{"$ref":"#/components/schemas/ListUsersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Users","tags":["UserService"]},"post":{"description":"Create a new user.","operationId":"UserService_CreateUser","requestBody":{"content":{"application/json":{"example":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service","password":"secure-password"},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"required":true,"x-originalParamName":"user"},"responses":{"201":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"description":"User created"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create User","tags":["UserService"]}},"/v1alpha1/users/{name}":{"delete":{"description":"Delete the specified user","operationId":"UserService_DeleteUser","parameters":[{"description":"Username","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"User deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"example":{"code":"NOT_FOUND","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_RESOURCE_NOT_FOUND"}],"message":"user not found"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete User","tags":["UserService"]}},"/v1alpha1/users/{user.name}":{"put":{"description":"Update a user's credentials.","operationId":"UserService_UpdateUser","parameters":[{"description":"Username.","in":"path","name":"user.name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","password":"new-password"}},"schema":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"password":{"description":"Password.","type":"string"}},"type":"object"}}},"required":true,"x-originalParamName":"user"},"responses":{"200":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/UpdateUserResponse.User"}}},"description":"OK"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update User","tags":["UserService"]}}},"security":[{"auth0":[]}],"servers":[{"description":"Dataplane API","url":"{dataplane_api_url}","variables":{"dataplane_api_url":{"default":"https://api-a4cb21.ck09ma3c4vs12cng3cig.fmc.prd.cloud.redpanda.com","description":"Dataplane API.\u003cbr\u003e\n\t\t\t\t\tThe Dataplane API allows management of Topics,ACLs,Service accounts. It is exposed by each individual cluster.\n\t\t\t\t\tRetrieve the Dataplane API URL of a cluster by using the dataplane_api.url field returned by the Get Cluster endpoint.\u003cbr\u003e\u003cbr\u003e\n\t\t\t\t\tExample (Dedicated): https://api-a4cb21.ck09mi9c4vs17hng9gig.fmc.prd.cloud.redpanda.com\u003cbr\u003e\n\t\t\t\t\tExample (BYOC): https://api-a4cb21.ck09mi9c4vs17hng9gig.byoc.prd.cloud.redpanda.com"}}}],"tags":[{"name":"ACLService"},{"name":"TransformService"},{"name":"KafkaConnectService"},{"name":"SecretService"},{"name":"TopicService"},{"name":"UserService"}]} \ No newline at end of file +{"components":{"schemas":{"ACL.Operation":{"description":"The operation that is allowed or denied (e.g. READ).","enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"},"BadRequest":{"description":"Describes violations in a client request. This error type focuses on the\nsyntactic aspects of the request.","properties":{"field_violations":{"description":"Describes all violations in a client request.","items":{"$ref":"#/components/schemas/FieldViolation"},"type":"array"}},"title":"BadRequest","type":"object"},"Config":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConfigAlterOperation":{"enum":["CONFIG_ALTER_OPERATION_SET","CONFIG_ALTER_OPERATION_DELETE","CONFIG_ALTER_OPERATION_APPEND","CONFIG_ALTER_OPERATION_SUBTRACT"],"type":"string"},"ConfigSource":{"enum":["CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG","CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG","CONFIG_SOURCE_STATIC_BROKER_CONFIG","CONFIG_SOURCE_DEFAULT_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG"],"type":"string"},"ConfigSynonym":{"properties":{"name":{"type":"string"},"source":{"$ref":"#/components/schemas/ConfigSource"},"value":{"nullable":true,"type":"string"}},"type":"object"},"ConfigType":{"enum":["CONFIG_TYPE_BOOLEAN","CONFIG_TYPE_STRING","CONFIG_TYPE_INT","CONFIG_TYPE_SHORT","CONFIG_TYPE_LONG","CONFIG_TYPE_DOUBLE","CONFIG_TYPE_LIST","CONFIG_TYPE_CLASS","CONFIG_TYPE_PASSWORD"],"type":"string"},"Configuration":{"properties":{"config_synonyms":{"description":"If no config value is set at the topic level, it will inherit the value\nset at the broker or cluster level. `name` is the corresponding config\nkey whose value is inherited. `source` indicates whether the inherited\nconfig is default, broker, etc.","items":{"$ref":"#/components/schemas/ConfigSynonym"},"type":"array"},"documentation":{"description":"Config documentation.","nullable":true,"type":"string"},"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"read_only":{"description":"Whether the config is read-only, or is dynamic and can be altered.","type":"boolean"},"sensitive":{"description":"Whether this is a sensitive config key and value.","type":"boolean"},"source":{"$ref":"#/components/schemas/ConfigSource"},"type":{"$ref":"#/components/schemas/ConfigType"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConnectCluster":{"properties":{"address":{"type":"string"},"info":{"$ref":"#/components/schemas/ConnectCluster.Info"},"name":{"type":"string"},"plugins":{"items":{"$ref":"#/components/schemas/ConnectorPlugin"},"type":"array"}},"type":"object"},"ConnectCluster.Info":{"properties":{"commit":{"type":"string"},"kafka_cluster_id":{"type":"string"},"version":{"type":"string"}},"type":"object"},"Connector":{"properties":{"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"ConnectorError":{"properties":{"content":{"type":"string"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ConnectorError.Type"}},"title":"ConnectorError is the error of a connector, this is holistic error\nabstraction, made parsing the error trace of connector or Task","type":"object"},"ConnectorError.Type":{"enum":["TYPE_ERROR","TYPE_WARNING"],"type":"string"},"ConnectorHolisticState":{"description":"- CONNECTOR_HOLISTIC_STATE_PAUSED: PAUSED: The connector/task has been administratively paused.\n - CONNECTOR_HOLISTIC_STATE_RESTARTING: RESTARTING: he connector/task is restarting.\n - CONNECTOR_HOLISTIC_STATE_DESTROYED: DESTROYED: Connector is in destroyed state, regardless of any tasks.\n - CONNECTOR_HOLISTIC_STATE_STOPPED: STOPPED: The connector/task has been stopped.\n - CONNECTOR_HOLISTIC_STATE_UNASSIGNED: The connector/task has not yet been assigned to a worker\nUNASSIGNED: Connector is in unassigned state.\n Or Connector is in running state, and there are unassigned tasks.\n - CONNECTOR_HOLISTIC_STATE_HEALTHY: HEALTHY: Connector is in running state, \u003e 0 tasks, all of them in running state.\n - CONNECTOR_HOLISTIC_STATE_UNHEALTHY: UNHEALTHY: Connector is failed state.\n\t\t\tOr Connector is in running state but has 0 tasks.\n\t\t\tOr Connector is in running state, has \u003e 0 tasks, and all tasks are in failed state.\n - CONNECTOR_HOLISTIC_STATE_DEGRADED: DEGRADED: Connector is in running state, has \u003e 0 tasks, but has at least one state in failed state, but not all tasks are failed.\n - CONNECTOR_HOLISTIC_STATE_UNKNOWN: UNKNOWN: The connector/task could no be determined","enum":["CONNECTOR_HOLISTIC_STATE_PAUSED","CONNECTOR_HOLISTIC_STATE_RESTARTING","CONNECTOR_HOLISTIC_STATE_DESTROYED","CONNECTOR_HOLISTIC_STATE_STOPPED","CONNECTOR_HOLISTIC_STATE_UNASSIGNED","CONNECTOR_HOLISTIC_STATE_HEALTHY","CONNECTOR_HOLISTIC_STATE_UNHEALTHY","CONNECTOR_HOLISTIC_STATE_DEGRADED","CONNECTOR_HOLISTIC_STATE_UNKNOWN"],"title":"The following states are possible for a connector or one of its tasks\nimplement the state interface described in the Kafka connect API @see\nhttps://docs.confluent.io/platform/current/connect/monitoring.html#connector-and-task-status\nthis includes holistic unified connector status that takes into account not\njust the connector instance state, but also state of all the tasks within the\nconnector","type":"string"},"ConnectorInfoStatus":{"properties":{"info":{"$ref":"#/components/schemas/ConnectorSpec"},"name":{"title":"name is the connector name","type":"string"},"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"ConnectorPlugin":{"properties":{"class":{"type":"string"},"type":{"type":"string"},"version":{"type":"string"}},"type":"object"},"ConnectorSpec":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskInfo"},"readOnly":true,"type":"array"},"type":{"readOnly":true,"type":"string"}},"required":["name","config"],"title":"ConectorInfo is the spec of the connector, as defined in the Kafka connect\nAPI, it can be used as input of the connector creation or output of the\nconnectors","type":"object"},"ConnectorStatus":{"properties":{"connector":{"$ref":"#/components/schemas/Connector"},"errors":{"items":{"$ref":"#/components/schemas/ConnectorError"},"title":"Errors is list of parsed connectors' and tasks' errors","type":"array"},"holistic_state":{"$ref":"#/components/schemas/ConnectorHolisticState"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskStatus"},"type":"array"},"type":{"type":"string"}},"type":"object"},"CreateACLRequest":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.\nFor requests with resource_type CLUSTER, this will default to \"kafka-cluster\".","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","principal","host","operation","permission_type"],"type":"object"},"CreateACLResponse":{"type":"object"},"CreateConnectSecretResponse":{"description":"CreateConnectSecretResponse is the response of CreateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"CreateConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"CreateTopicRequest.Topic":{"properties":{"configs":{"description":"An array of key-value config pairs for a topic.\nThese correspond to Kafka topic-level configs.","items":{"$ref":"#/components/schemas/Config"},"type":"array"},"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions to give the topic. If specifying\npartitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default partition count, set to null.","format":"int32","nullable":true,"type":"integer"},"replica_assignments":{"description":"Manually specify broker ID assignments for partition replicas. If manually assigning replicas, both `replication_factor` and\n`partition_count` must be -1.","items":{"$ref":"#/components/schemas/ReplicaAssignment"},"type":"array"},"replication_factor":{"description":"The number of replicas every partition must have.\nIf specifying partitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default replication factor, set to null.","format":"int32","nullable":true,"type":"integer"}},"type":"object"},"CreateTopicResponse":{"properties":{"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions created for the topic.\nThis field has a default value of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"},"replication_factor":{"description":"The number of replicas per topic partition.\nThis field has a default of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"}},"type":"object"},"CreateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"CreateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/CreateUserResponse.User"}},"type":"object"},"CreateUserResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"title":"Name of newly-created user","type":"string"}},"type":"object"},"DeleteACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","operation","permission_type"],"type":"object"},"DeleteACLsResponse":{"properties":{"matching_acls":{"items":{"$ref":"#/components/schemas/MatchingACL"},"type":"array"}},"type":"object"},"DeleteConnectSecretResponse":{"description":"DeleteConnectSecretResponse is the response of DeleteConnectSecret.","type":"object"},"DeleteTopicResponse":{"type":"object"},"DeleteTransformResponse":{"type":"object"},"DeleteUserResponse":{"type":"object"},"DeployTransformRequest":{"description":"Metadata required to deploy a new Wasm\ntransform in a Redpanda cluster.","properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"The input topic to apply the transform to.","example":"orders","type":"string"},"name":{"description":"Name of the transform.","example":"redact-payment-details-in-orders","type":"string"},"output_topic_names":{"description":"Output topic to write the transform results to.","example":"orders-redacted","items":{"type":"string"},"type":"array"}},"required":["name","input_topic_name","output_topic_names"],"type":"object"},"EnvironmentVariable":{"properties":{"key":{"description":"The key of your environment variable.","example":"LOG_LEVEL","type":"string"},"value":{"description":"The value of your environment variable.","example":"DEBUG","type":"string"}},"required":["key","value"],"type":"object"},"ErrorInfo":{"description":"Describes the cause of the error with structured details.\n\nExample of an error when contacting the \"pubsub.googleapis.com\" API when it\nis not enabled:\n\n { \"reason\": \"API_DISABLED\"\n \"domain\": \"googleapis.com\"\n \"metadata\": {\n \"resource\": \"projects/123\",\n \"service\": \"pubsub.googleapis.com\"\n }\n }\n\nThis response indicates that the pubsub.googleapis.com API is not enabled.\n\nExample of an error that is returned when attempting to create a Spanner\ninstance in a region that is out of stock:\n\n { \"reason\": \"STOCKOUT\"\n \"domain\": \"spanner.googleapis.com\",\n \"metadata\": {\n \"availableRegions\": \"us-central1,us-east2\"\n }\n }","properties":{"domain":{"description":"The logical grouping to which the \"reason\" belongs. The error domain\nis typically the registered service name of the tool or product that\ngenerates the error. Example: \"pubsub.googleapis.com\". If the error is\ngenerated by some common infrastructure, the error domain must be a\nglobally unique value that identifies the infrastructure. For Google API\ninfrastructure, the error domain is \"googleapis.com\".","type":"string"},"metadata":{"additionalProperties":{"type":"string"},"description":"Additional structured details about this error.\n\nKeys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in\nlength. When identifying the current value of an exceeded limit, the units\nshould be contained in the key, not the value. For example, rather than\n{\"instanceLimit\": \"100/request\"}, should be returned as,\n{\"instanceLimitPerRequest\": \"100\"}, if the client exceeds the number of\ninstances that can be created in a single (batch) request.","type":"object"},"reason":{"description":"The reason of the error. This is a constant value that identifies the\nproximate cause of the error. Error reasons are unique within a particular\ndomain of errors. This should be at most 63 characters and match a\nregular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents\nUPPER_SNAKE_CASE.","type":"string"}},"title":"ErrorInfo","type":"object"},"FieldViolation":{"description":"A message type used to describe a single bad request field.","properties":{"description":{"description":"A description of why the request element is bad.","type":"string"},"field":{"description":"A path that leads to a field in the request body. The value will be a\nsequence of dot-separated identifiers that identify a protocol buffer\nfield.\n\nConsider the following:\n\n message CreateContactRequest {\n message EmailAddress {\n enum Type {\n TYPE_UNSPECIFIED = 0;\n HOME = 1;\n WORK = 2;\n }\n\n optional string email = 1;\n repeated EmailType type = 2;\n }\n\n string full_name = 1;\n repeated EmailAddress email_addresses = 2;\n }\n\nIn this example, in proto `field` could take one of the following values:\n\n* `full_name` for a violation in the `full_name` value\n* `email_addresses[1].email` for a violation in the `email` field of the\n first `email_addresses` message\n* `email_addresses[3].type[2]` for a violation in the second `type`\n value in the third `email_addresses` message.\n\nIn JSON, the same values are represented as:\n\n* `fullName` for a violation in the `fullName` value\n* `emailAddresses[1].email` for a violation in the `email` field of the\n first `emailAddresses` message\n* `emailAddresses[3].type[2]` for a violation in the second `type`\n value in the third `emailAddresses` message.","type":"string"}},"type":"object"},"GetConnectClusterResponse":{"properties":{"cluster":{"$ref":"#/components/schemas/ConnectCluster"}},"type":"object"},"GetConnectSecretResponse":{"description":"GetConnectSecretResponse is the response of GetConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"GetConnectorConfigResponse":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"GetConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"GetConnectorStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"GetTopicConfigurationsResponse":{"properties":{"configurations":{"items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"GetTransformResponse":{"properties":{"transform":{"$ref":"#/components/schemas/TransformMetadata"}},"type":"object"},"Help":{"description":"Provides links to documentation or for performing an out of band action.\n\nFor example, if a quota check failed with an error indicating the calling\nproject hasn't enabled the accessed service, this can contain a URL pointing\ndirectly to the right place in the developer console to flip the bit.","properties":{"links":{"description":"URL(s) pointing to additional information on handling the current error.","items":{"$ref":"#/components/schemas/Link"},"type":"array"}},"title":"Help","type":"object"},"Link":{"description":"Describes a URL link.","properties":{"description":{"description":"Describes what the link offers.","type":"string"},"url":{"description":"The URL of the link.","type":"string"}},"type":"object"},"ListACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ListACLsResponse":{"properties":{"resources":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"type":"object"},"ListConnectClustersResponse":{"properties":{"clusters":{"items":{"$ref":"#/components/schemas/ConnectCluster"},"type":"array"}},"type":"object"},"ListConnectSecretsResponse":{"description":"ListConnectSecretsResponse is the response of ListConnectSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListConnectorTopicsResponse":{"properties":{"topics":{"items":{"type":"string"},"type":"array"}},"type":"object"},"ListConnectorsResponse":{"properties":{"connectors":{"items":{"$ref":"#/components/schemas/ConnectorInfoStatus"},"title":"connectors is the list of connectors the key is the connector name","type":"array"},"next_page_token":{"description":"Page Token to fetch the next page. The value can be used as page_token in the next call to this endpoint.","type":"string"}},"type":"object"},"ListSecretsFilter":{"description":"ListSecretsFilter are the filter options for listing secrets.","properties":{"labels[string]":{"additionalProperties":{"type":"string"},"description":"The secret labels to search for.","type":"object"},"name_contains":{"description":"Substring match on secret name. Case-sensitive.","type":"string"}},"type":"object"},"ListSecretsResponse":{"description":"ListSecretsResponse is the response of ListSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListTopicsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on topic name. Case-sensitive.","type":"string"}},"type":"object"},"ListTopicsResponse":{"properties":{"next_page_token":{"type":"string"},"topics":{"items":{"$ref":"#/components/schemas/ListTopicsResponse.Topic"},"type":"array"}},"type":"object"},"ListTopicsResponse.Topic":{"properties":{"internal":{"description":"Whether topic is internal only.","type":"boolean"},"name":{"description":"Topic name.","type":"string"},"partition_count":{"description":"Topic partition count.","format":"int32","type":"integer"},"replication_factor":{"description":"Topic replication factor.","format":"int32","type":"integer"}},"type":"object"},"ListTransformsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on transform name. Case-sensitive.","type":"string"}},"type":"object"},"ListTransformsResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"transforms":{"items":{"$ref":"#/components/schemas/TransformMetadata"},"type":"array"}},"type":"object"},"ListUsersRequest.Filter":{"properties":{"name":{"description":"Username.","type":"string"},"name_contains":{"description":"Substring match on username. Case-sensitive.","type":"string"}},"type":"object"},"ListUsersResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"users":{"items":{"$ref":"#/components/schemas/ListUsersResponse.User"},"type":"array"}},"type":"object"},"ListUsersResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"}},"type":"object"},"MatchingACL":{"properties":{"error":{"$ref":"#/components/schemas/Status"},"host":{"description":"The host address to for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"Options":{"properties":{"include_tasks":{"type":"boolean"},"only_failed":{"type":"boolean"}},"type":"object"},"PartitionStatus":{"enum":["PARTITION_STATUS_RUNNING","PARTITION_STATUS_INACTIVE","PARTITION_STATUS_ERRORED","PARTITION_STATUS_UNKNOWN"],"type":"string"},"PartitionTransformStatus":{"properties":{"broker_id":{"format":"int32","type":"integer"},"lag":{"format":"int32","type":"integer"},"partition_id":{"format":"int32","type":"integer"},"status":{"$ref":"#/components/schemas/PartitionStatus"}},"type":"object"},"PermissionType":{"description":"Whether the operation should be allowed or denied.","enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"},"Policy":{"properties":{"host":{"description":"The host address for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"}},"type":"object"},"QuotaFailure":{"description":"Describes how a quota check failed.\n\nFor example if a daily limit was exceeded for the calling project,\na service could respond with a QuotaFailure detail containing the project\nid and the description of the quota limit that was exceeded. If the\ncalling project hasn't enabled the service in the developer console, then\na service could respond with the project id and set `service_disabled`\nto true.\n\nAlso see RetryInfo and Help types for other details about handling a\nquota failure.","properties":{"violations":{"description":"Describes all quota violations.","items":{"$ref":"#/components/schemas/QuotaFailure.Violation"},"type":"array"}},"title":"QuotaFailure","type":"object"},"QuotaFailure.Violation":{"description":"A message type used to describe a single quota violation. For example, a\ndaily quota or a custom quota that was exceeded.","properties":{"description":{"description":"A description of how the quota check failed. Clients can use this\ndescription to find more about the quota configuration in the service's\npublic documentation, or find the relevant quota limit to adjust through\ndeveloper console.\n\nFor example: \"Service disabled\" or \"Daily Limit for read operations\nexceeded\".","type":"string"},"subject":{"description":"The subject on which the quota check failed.\nFor example, \"clientip:\u003cip address of client\u003e\" or \"project:\u003cGoogle\ndeveloper project id\u003e\".","type":"string"}},"type":"object"},"ReplicaAssignment":{"properties":{"partition_id":{"description":"A partition to create.","format":"int32","type":"integer"},"replica_ids":{"description":"The broker IDs the partition replicas are assigned to.","items":{"format":"int32","type":"integer"},"type":"array"}},"type":"object"},"Resource":{"properties":{"acls":{"items":{"$ref":"#/components/schemas/Policy"},"type":"array"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ResourcePatternType":{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"},"ResourceType":{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"},"SASLMechanism":{"description":"SASL mechanism to use for authentication.","enum":["SASL_MECHANISM_SCRAM_SHA_256","SASL_MECHANISM_SCRAM_SHA_512"],"type":"string"},"Secret":{"description":"Defines the secret resource.","properties":{"id":{"description":"Secret identifier.","readOnly":true,"type":"string"},"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"}},"type":"object"},"SetConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"SetTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after this update.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"Status":{"description":"The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors).","properties":{"code":{"description":"RPC status code, as described [here](https://github.com/googleapis/googleapis/blob/b4c238feaa1097c53798ed77035bbfeb7fc72e96/google/rpc/code.proto#L32).","enum":["OK","CANCELLED","UNKNOWN","INVALID_ARGUMENT","DEADLINE_EXCEEDED","NOT_FOUND","ALREADY_EXISTS","PERMISSION_DENIED","UNAUTHENTICATED","RESOURCE_EXHAUSTED","FAILED_PRECONDITION","ABORTED","OUT_OF_RANGE","UNIMPLEMENTED","INTERNAL","UNAVAILABLE","DATA_LOSS"],"format":"int32","type":"string"},"details":{"items":{"description":"Details of the error.","oneOf":[{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.BadRequest"],"type":"string"}}},{"$ref":"#/components/schemas/BadRequest"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.ErrorInfo"],"type":"string"}}},{"$ref":"#/components/schemas/ErrorInfo"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.QuotaFailure"],"type":"string"}}},{"$ref":"#/components/schemas/QuotaFailure"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.Help"],"type":"string"}}},{"$ref":"#/components/schemas/Help"}]}]},"type":"array"},"message":{"description":"Detailed error message. No compatibility guarantees are given for the text contained in this message.","type":"string"}},"type":"object"},"TaskInfo":{"properties":{"connector":{"type":"string"},"task":{"format":"int32","type":"integer"}},"type":"object"},"TaskStatus":{"properties":{"id":{"format":"int32","type":"integer"},"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"TransformMetadata":{"properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"Input topic to apply the transform to.","type":"string"},"name":{"description":"Name of transform.","type":"string"},"output_topic_names":{"description":"Output topics to write the transform results to.","items":{"type":"string"},"type":"array"},"statuses":{"items":{"$ref":"#/components/schemas/PartitionTransformStatus"},"type":"array"}},"type":"object"},"UpdateConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"operation":{"$ref":"#/components/schemas/ConfigAlterOperation"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"UpdateConnectSecretResponse":{"description":"UpdateConnectSecretResponse is the response of UpdateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"UpdateTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after applying this partial patch.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"UpdateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"UpdateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/UpdateUserResponse.User"}},"type":"object"},"UpdateUserResponse.User":{"description":"Updated user's name and SASL mechanism.","properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"type":"string"}},"type":"object"},"UpsertConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"v1alpha1.Topic":{"type":"object"}},"securitySchemes":{"auth0":{"description":"RedpandaCloud","flows":{"implicit":{"authorizationUrl":"https://prod-cloudv2.us.auth0.com/oauth/authorize","scopes":{},"x-client-id":"dQjapNIAHhF7EQqQToRla3yEII9sUSap"}},"type":"oauth2"}}},"info":{"description":"Welcome to Redpanda Cloud's Dataplane API documentation.","title":"Redpanda Cloud","version":"v1alpha1"},"openapi":"3.0.3","paths":{"/v1alpha1/acls":{"delete":{"description":"Delete all ACLs that match the filter criteria. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_DeleteACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","required":true,"schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","required":true,"schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","required":true,"schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","required":true,"schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete ACLs","tags":["ACLService"]},"get":{"description":"List all ACLs. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_ListACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List ACLs","tags":["ACLService"]},"post":{"description":"Create a new ACL.","operationId":"ACLService_CreateACL","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLRequest"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLResponse"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create ACL","tags":["ACLService"]}},"/v1alpha1/connect/clusters":{"get":{"description":"List connect clusters available for being consumed by the console's kafka-connect service.","operationId":"KafkaConnectService_ListConnectClusters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectClustersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connect clusters","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}":{"get":{"description":"Get information about an available Kafka Connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","operationId":"KafkaConnectService_GetConnectCluster","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectCluster"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Connect cluster not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connect cluster","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors":{"get":{"description":"List connectors managed by the Kafka Connect service.","operationId":"KafkaConnectService_ListConnectors","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connectors","tags":["KafkaConnectService"]},"post":{"description":"Create a connector with the specified configuration.","operationId":"KafkaConnectService_CreateConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"required":true,"x-originalParamName":"connector"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}":{"delete":{"description":"Delete a connector. This operation force stops all tasks and also deletes the connector configuration.","operationId":"KafkaConnectService_DeleteConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete connector","tags":["KafkaConnectService"]},"get":{"description":"Get information about a connector in a specific cluster.","operationId":"KafkaConnectService_GetConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/config":{"get":{"description":"Get the configuration for the connector.","operationId":"KafkaConnectService_GetConnectorConfig","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector configuration","tags":["KafkaConnectService"]},"put":{"description":"Create a new connector using the given configuration, or update the configuration for an existing connector. Returns information about the connector after the change has been made.","operationId":"KafkaConnectService_UpsertConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"required":["config"],"type":"object"}}},"required":true,"x-originalParamName":"config"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Updated"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Upsert connector configuration","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/pause":{"put":{"description":"Pause the connector and its tasks, which stops messages from processing until the connector is resumed. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_PauseConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Pause request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Pause connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/restart":{"post":{"description":"Restarts a connector, triggers a connector restart, you can specify the only_failed or/and the include_tasks options.","operationId":"KafkaConnectService_RestartConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Options"}}},"required":true,"x-originalParamName":"options"},"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Restart connector request success"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Restart connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/resume":{"put":{"description":"Resume a paused connector and its tasks, and resumes message processing. This call is asynchronous and may take some time to process. If the connector was not paused, this operation does not do anything.","operationId":"KafkaConnectService_ResumeConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Resume request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Resume connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/status":{"get":{"description":"Gets the current status of the connector, including the state for each of its tasks, error information, etc.","operationId":"KafkaConnectService_GetConnectorStatus","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorStatus"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector status","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/stop":{"put":{"description":"Stops a connector, but does not delete the connector. All tasks for the connector are shut down completely. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_StopConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Stop connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics":{"get":{"description":"Returns a list of connector topic names. If the connector is inactive, this call returns an empty list.","operationId":"KafkaConnectService_ListConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics/reset":{"put":{"description":"Resets the set of topic names that the connector is using.","operationId":"KafkaConnectService_ResetConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Reset connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets":{"get":{"description":"List Kafka Connect cluster secrets. Optional: filter based on secret name and labels.","operationId":"SecretService_ListConnectSecrets","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Substring match on secret name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"This is a request variable of the map type. The query format is \"map_name[key]=value\", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age[\"bob\"]=18","in":"query","name":"filter.labels[string]","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSecretsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Connect Cluster Secrets","tags":["SecretService"]},"post":{"description":"Create a Kafka Connect cluster secret.","operationId":"SecretService_CreateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"CreateConnectSecretRequest is the request of CreateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"name":{"description":"Name of connector.","type":"string"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["name","secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"Secret created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets/{id}":{"delete":{"description":"Delete a Kafka Connect cluster secret.","operationId":"SecretService_DeleteConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to delete.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Secret deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Connect Cluster Secret","tags":["SecretService"]},"get":{"description":"Get a specific Kafka Connect cluster secret.","operationId":"SecretService_GetConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"The ID of the secret to retrieve.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Connect Cluster Secret","tags":["SecretService"]},"put":{"description":"Update a Kafka Connect cluster secret.","operationId":"SecretService_UpdateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to update.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"UpdateConnectSecretRequest is the request of UpdateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/topics":{"get":{"description":"List topics, with partition count and replication factor. Optional: filter based on topic name.","operationId":"TopicService_ListTopics","parameters":[{"description":"Substring match on topic name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Topics","tags":["TopicService"]},"post":{"description":"Create a topic.","operationId":"TopicService_CreateTopic","parameters":[{"description":"If true, makes this request a dry run; everything is validated but\nno topics are actually created.","in":"query","name":"validate_only","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTopicRequest.Topic"}}},"description":"The topic to create.","required":true,"x-originalParamName":"topic"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1alpha1.Topic"}}},"description":"Topic created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Topic","tags":["TopicService"]}},"/v1alpha1/topics/{name}":{"delete":{"description":"Delete the Kafka topic with the requested name.","operationId":"TopicService_DeleteTopic","parameters":[{"description":"Topic name.","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Topic deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Requested topic does not exist"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Topic","tags":["TopicService"]}},"/v1alpha1/topics/{topic_name}/configurations":{"get":{"description":"Get key-value configs for a topic.","operationId":"TopicService_GetTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Topic Configurations","tags":["TopicService"]},"patch":{"description":"Update a subset of the topic configurations.","operationId":"TopicService_UpdateTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UpdateConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Topic Configuration","tags":["TopicService"]},"put":{"description":"Update the entire set of key-value configurations for a topic. Config entries that are not provided in the request are removed and will fall back to their default values.","operationId":"TopicService_SetTopicConfigurations","parameters":[{"description":"Name of topic.","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SetConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Set Topic Configurations","tags":["TopicService"]}},"/v1alpha1/transforms":{"get":{"description":"Retrieve a list of Wasm transforms. Optional: filter based on transform name.","operationId":"TransformService_ListTransforms","parameters":[{"description":"Substring match on transform name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","transforms":[{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic11","output-topic12"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]},{"environment_variables":[],"input_topic_name":"topic2","name":"transform2","output_topic_names":["output-topic21","output-topic22"],"statuses":[{"broker_id":2,"lag":2,"partition_id":2,"status":"PARTITION_STATUS_RUNNING"}]}]},"schema":{"$ref":"#/components/schemas/ListTransformsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Transforms","tags":["TransformService"]},"put":{"description":"Initiate deployment of a new Wasm transform. This endpoint uses multipart/form-data encoding. Following deployment, a brief period is required before the Wasm transform becomes operational. Monitor the partition statuses to check whether the transform is active. This usually takes around 3s, but no longer than 10s.","operationId":"TransformService_DeployTransform","requestBody":{"content":{"multipart/form-data":{"schema":{"example":"{\"name\":\"redact-orders\", \"input_topic_name\":\"orders\", \"output_topic_names\":[\"orders-redacted\"], \"environment_variables\":[{\"key\":\"LOGGER_LEVEL\", \"value\":\"DEBUG\"}]}","properties":{"metadata":{"$ref":"#/components/schemas/DeployTransformRequest"},"wasm_binary":{"description":"Binary file containing the compiled WASM transform. The maximum size for this file is 10MiB.","format":"binary","type":"string"}},"type":"object"}}},"description":"Transform metadata as well as the WASM binary","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransformMetadata"}}},"description":"Created"}},"summary":"Deploy Transform","tags":["TransformService"]}},"/v1alpha1/transforms/{name}":{"delete":{"description":"Delete a Wasm transform with the requested name.","operationId":"TransformService_DeleteTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"Transform deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Transform","tags":["TransformService"]},"get":{"description":"Get a specific Wasm transform.","operationId":"TransformService_GetTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"transform":{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic1","output-topic2"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]}},"schema":{"$ref":"#/components/schemas/GetTransformResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Transform","tags":["TransformService"]}},"/v1alpha1/users":{"get":{"description":"List users. Optional: filter based on username.","operationId":"UserService_ListUsers","parameters":[{"description":"Username.","in":"query","name":"filter.name","schema":{"type":"string"}},{"description":"Substring match on username. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","users":[{"name":"payment-service"},{"name":"jane"}]},"schema":{"$ref":"#/components/schemas/ListUsersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Users","tags":["UserService"]},"post":{"description":"Create a new user.","operationId":"UserService_CreateUser","requestBody":{"content":{"application/json":{"example":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service","password":"secure-password"},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"required":true,"x-originalParamName":"user"},"responses":{"201":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"description":"User created"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create User","tags":["UserService"]}},"/v1alpha1/users/{name}":{"delete":{"description":"Delete the specified user","operationId":"UserService_DeleteUser","parameters":[{"description":"Username","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"User deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"example":{"code":"NOT_FOUND","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_RESOURCE_NOT_FOUND"}],"message":"user not found"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete User","tags":["UserService"]}},"/v1alpha1/users/{user.name}":{"put":{"description":"Update a user's credentials.","operationId":"UserService_UpdateUser","parameters":[{"description":"Username.","in":"path","name":"user.name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","password":"new-password"}},"schema":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"password":{"description":"Password.","type":"string"}},"type":"object"}}},"required":true,"x-originalParamName":"user"},"responses":{"200":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/UpdateUserResponse.User"}}},"description":"OK"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update User","tags":["UserService"]}}},"security":[{"auth0":[]}],"servers":[{"description":"Dataplane API","url":"{dataplane_api_url}","variables":{"dataplane_api_url":{"default":"https://api-a4cb21.ck09ma3c4vs12cng3cig.fmc.prd.cloud.redpanda.com","description":"Dataplane API.\u003cbr\u003e\n\t\t\t\t\tThe Dataplane API allows management of Topics,ACLs,Service accounts. It is exposed by each individual cluster.\n\t\t\t\t\tRetrieve the Dataplane API URL of a cluster by using the dataplane_api.url field returned by the Get Cluster endpoint.\u003cbr\u003e\u003cbr\u003e\n\t\t\t\t\tExample (Dedicated): https://api-a4cb21.ck09mi9c4vs17hng9gig.fmc.prd.cloud.redpanda.com\u003cbr\u003e\n\t\t\t\t\tExample (BYOC): https://api-a4cb21.ck09mi9c4vs17hng9gig.byoc.prd.cloud.redpanda.com"}}}],"tags":[{"name":"ACLService"},{"name":"TransformService"},{"name":"KafkaConnectService"},{"name":"SecretService"},{"name":"TopicService"},{"name":"UserService"}]} \ No newline at end of file diff --git a/proto/gen/openapi/openapi.yaml b/proto/gen/openapi/openapi.yaml index 75e8f3e41..826c88fba 100644 --- a/proto/gen/openapi/openapi.yaml +++ b/proto/gen/openapi/openapi.yaml @@ -2794,7 +2794,7 @@ paths: content: multipart/form-data: schema: - example: '{"name":"redact-orders","input_topic_name":"orders","output_topic_names":["orders-redacted"],"environment_variables":[{"key":"LOGGER_LEVEL","value":"DEBUG"}]}' + example: '{"name":"redact-orders", "input_topic_name":"orders", "output_topic_names":["orders-redacted"], "environment_variables":[{"key":"LOGGER_LEVEL", "value":"DEBUG"}]}' properties: metadata: $ref: '#/components/schemas/DeployTransformRequest' diff --git a/proto/redpanda/api/console/v1alpha1/security.proto b/proto/redpanda/api/console/v1alpha1/security.proto new file mode 100644 index 000000000..f923c70ad --- /dev/null +++ b/proto/redpanda/api/console/v1alpha1/security.proto @@ -0,0 +1,217 @@ +syntax = "proto3"; + +package redpanda.api.console.v1alpha1; + +import "buf/validate/validate.proto"; + +// Role defines a role in the system. +message Role { + // The name of the role. + string name = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$" + ]; +} + +// ListRolesRequest is the request for ListRoles. +message ListRolesRequest { + // Filter options. + message Filter { + // Filter results only roles named with the prefix. + string name_prefix = 1 [ + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^([a-zA-Z0-9-_]*)$" + ]; + + // Filter results to only roles with names which contain the string. + string name_contains = 2 [ + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^([a-zA-Z0-9-_]*)$" + ]; + + // Return only roles assigned to this principal. + string principal = 3 [(buf.validate.field).string.max_len = 128]; + } + + // Optional filter. + optional Filter filter = 1; + + // Page size. + int32 page_size = 2 [(buf.validate.field).int32 = { + gte: -1, + lte: 1000 + }]; + + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + string page_token = 3; +} + +// ListRolesResponse is the response for ListRoles. +message ListRolesResponse { + // The roles in the system. + repeated Role roles = 1; + + // Token to retrieve the next page. + string next_page_token = 2; +} + +// CreateRoleRequest is the request for CreateRole. +message CreateRoleRequest { + // The role to create. + Role role = 1; +} + +// CreateRoleResponse is the response for CreateRole. +message CreateRoleResponse { + // The role. + Role role = 1; +} + +// CreateRoleRequest is the request for CreateRole. +message GetRoleRequest { + // The role name. + string role_name = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$" + ]; +} + +// GetRoleResponse is the response to GetRole. +message GetRoleResponse { + // The Role. + Role role = 1; + + // Members assigned to the role. + repeated RoleMembership members = 2; +} + +// DeleteRoleRequest is the request for DeleteRole. +message DeleteRoleRequest { + // The role name. + string role_name = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$" + ]; + + // Whether to delete the ACLs bound to the role. + bool delete_acls = 2; +} + +// DeleteRoleResponse is the response for DeleteRole. +message DeleteRoleResponse {} + +// List role members for a role. That is user principals assigned to that role. +message ListRoleMembersRequest { + // Filter options. + message Filter { + // Filter results to only members with names which contain the string. + string name_contains = 1 [(buf.validate.field).string.max_len = 128]; + } + + // The role name. + string role_name = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$" + ]; + + // Optional filter. + optional Filter filter = 2; + + // Page size. + int32 page_size = 3 [(buf.validate.field).int32 = { + gte: -1, + lte: 1000 + }]; + + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + string page_token = 4; +} + +// ListRoleMembersResponse is the response for ListRoleMembers. +message ListRoleMembersResponse { + // The role name. + string role_name = 1; + + // Members assigned to the role. + repeated RoleMembership members = 2; + + // Token to retrieve the next page. + string next_page_token = 3; +} + +// RoleMembership is the role membership. +message RoleMembership { + // The name of the principal assigned to the role. + string principal = 1; +} + +// UpdateRoleMembershipRequest is the request to UpdateRoleMembership. +message UpdateRoleMembershipRequest { + // The role name. + string role_name = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$" + ]; + + // Create the role if it doesn't already exist. + // If the role is created in this way, the “add” list will be respected, but the “remove” list will be ignored. + bool create = 2; + + // Members to assign to the role. + repeated RoleMembership add = 3; + + // Members to remove from the role. + repeated RoleMembership remove = 4; +} + +// UpdateRoleMembershipResponse is the response for UpdateRoleMembership. +message UpdateRoleMembershipResponse { + // The role name. + string role_name = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$" + ]; + + // Members assigned to the role. + repeated RoleMembership added = 2; + + // Members removed from the role. + repeated RoleMembership removed = 3; +} + +service SecurityService { + // ListRoles lists all the roles based on optional filter. + rpc ListRoles(ListRolesRequest) returns (ListRolesResponse) {} + + rpc CreateRole(CreateRoleRequest) returns (CreateRoleResponse) {} + + // GetRole retrieves the specific role. + rpc GetRole(GetRoleRequest) returns (GetRoleResponse) {} + + // DeleteRole deletes the role from the system. + rpc DeleteRole(DeleteRoleRequest) returns (DeleteRoleResponse) {} + + // ListRoleMembership lists all the members assigned to a role based on optional filter. + rpc ListRoleMembers(ListRoleMembersRequest) returns (ListRoleMembersResponse) {} + + // UpdateRoleMembership updates role membership. + // Partially update role membership, adding or removing from a role + // ONLY those members listed in the “add” or “remove” fields, respectively. + // Adding a member that is already assigned to the role (or removing one that is not) is a no-op, + // and the rest of the members will be added and removed and reported. + rpc UpdateRoleMembership(UpdateRoleMembershipRequest) returns (UpdateRoleMembershipResponse) {} +}