-
Notifications
You must be signed in to change notification settings - Fork 352
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
backend: add config to enable/disable connectrpc endpoints (#955)
* backend: add config to enable/disable connectrpc endpoints
- Loading branch information
Showing
5 changed files
with
186 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright 2023 Redpanda Data, Inc. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file 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 | ||
|
||
package interceptor | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"connectrpc.com/connect" | ||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" | ||
"go.uber.org/zap" | ||
|
||
apierrors "github.com/redpanda-data/console/backend/pkg/api/connect/errors" | ||
"github.com/redpanda-data/console/backend/pkg/config" | ||
v1alpha1 "github.com/redpanda-data/console/backend/pkg/protogen/redpanda/api/dataplane/v1alpha1" | ||
) | ||
|
||
// EndpointCheckInterceptor checks whether incoming requests on the given endpoint | ||
// should pass or not. An endpoint can be enabled or disabled via the Console config. | ||
type EndpointCheckInterceptor struct { | ||
cfg *config.ConsoleAPI | ||
logger *zap.Logger | ||
} | ||
|
||
// NewEndpointCheckInterceptor creates a new EndpointCheckInterceptor. | ||
func NewEndpointCheckInterceptor(cfg *config.ConsoleAPI, logger *zap.Logger) *EndpointCheckInterceptor { | ||
return &EndpointCheckInterceptor{ | ||
cfg: cfg, | ||
logger: logger, | ||
} | ||
} | ||
|
||
// WrapUnary creates an interceptor to validate Connect requests. | ||
func (in *EndpointCheckInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc { | ||
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { | ||
procedure := req.Spec().Procedure | ||
|
||
// For HTTP paths invoked via gRPC gateway this is expected not to be set. | ||
// Let's try to retrieve it with gRPC gateway's runtime pkg. | ||
if procedure == "" { | ||
path, ok := runtime.RPCMethod(ctx) | ||
if !ok { | ||
return nil, apierrors.NewConnectError( | ||
connect.CodeInternal, | ||
errors.New("failed to extract procedure name"), | ||
apierrors.NewErrorInfo(v1alpha1.Reason_REASON_CONSOLE_ERROR.String())) | ||
} | ||
procedure = path | ||
} | ||
|
||
if procedure == "" { | ||
return nil, apierrors.NewConnectError( | ||
connect.CodeInternal, | ||
errors.New("failed to retrieve procedure name"), | ||
apierrors.NewErrorInfo(v1alpha1.Reason_REASON_CONSOLE_ERROR.String())) | ||
} | ||
|
||
notEnabledError := apierrors.NewConnectError( | ||
connect.CodeUnimplemented, | ||
errors.New("this endpoint has not been enabled"), | ||
apierrors.NewErrorInfo( | ||
v1alpha1.Reason_REASON_FEATURE_NOT_CONFIGURED.String(), | ||
apierrors.KeyVal{ | ||
Key: "requested_procedure", | ||
Value: procedure, | ||
}, | ||
)) | ||
|
||
if !in.cfg.Enabled { | ||
return nil, notEnabledError | ||
} | ||
|
||
// Check wildcard that allows all procedures first | ||
if in.cfg.AllowsAllProcedures { | ||
return next(ctx, req) | ||
} | ||
|
||
_, isAllowed := in.cfg.EnabledProceduresMap[procedure] | ||
if !isAllowed { | ||
return nil, notEnabledError | ||
} | ||
|
||
return next(ctx, req) | ||
} | ||
} | ||
|
||
// WrapStreamingClient is the middleware handler for bidirectional requests from | ||
// the client perspective. | ||
func (*EndpointCheckInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc { | ||
return next | ||
} | ||
|
||
// WrapStreamingHandler is the middleware handler for bidirectional requests from | ||
// the server handling perspective. | ||
func (*EndpointCheckInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc { | ||
return next | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright 2023 Redpanda Data, Inc. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file 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 | ||
|
||
package config | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// ConsoleAPI declares the configuration properties for managing the | ||
// connect/grpc/grpc-gateway API endpoints. | ||
type ConsoleAPI struct { | ||
// Enabled determines whether any of the connect/grpc/grpc-gateway endpoints | ||
// will be mounted to the server. | ||
Enabled bool `yaml:"enabled"` | ||
|
||
// EnabledProcedures is a list of procedure names that shall be allowed. | ||
// If a procedure is called that is not on this list a descriptive error | ||
// will be returned. A procedure name has the following format, regardless | ||
// whether it's called via connect, gRPC or the HTTP interface: | ||
// "/redpanda.api.dataplane.v1alpha1.UserService/ListUsers". | ||
// You can use "*" to enable all procedures. | ||
EnabledProcedures []string `yaml:"enabledProcedures"` | ||
|
||
// ProceduresByKey is the procedures slice in a map, where the key is the procedure. | ||
// This allows a more efficient look-up when comparing the requested procedure against | ||
// the enabled procedures. | ||
EnabledProceduresMap map[string]any | ||
|
||
// AllowsAllProcedures is calculated after parsing the config. It is set to true if | ||
// at least one string of EnabledProcedures contains the wildcard ("*"). | ||
AllowsAllProcedures bool | ||
} | ||
|
||
// Validate configuration options for the Console topic documentation feature. | ||
func (c *ConsoleAPI) Validate() error { | ||
if !c.Enabled { | ||
return nil | ||
} | ||
|
||
for _, p := range c.EnabledProcedures { | ||
if p == "*" { | ||
continue | ||
} | ||
if !strings.HasPrefix(p, "/") { | ||
return fmt.Errorf("every procedure must start with a slash. Entry %q does not start with a slash", p) | ||
} | ||
} | ||
|
||
// Post-processing; config is loaded at this point | ||
c.EnabledProceduresMap = make(map[string]any, len(c.EnabledProcedures)) | ||
for _, p := range c.EnabledProcedures { | ||
c.EnabledProceduresMap[p] = struct{}{} | ||
if p == "*" { | ||
c.AllowsAllProcedures = true | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// SetDefaults for ConsoleAPI. | ||
func (c *ConsoleAPI) SetDefaults() { | ||
c.Enabled = true | ||
c.EnabledProcedures = []string{"*"} | ||
} |