mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 00:57:33 +00:00
feat: port reduction (#323)
* move mgmt pkg * begin package restructure * rename auth package to authz * begin start api * move auth * move admin * fix merge * configs and interceptors * interceptor * revert generate-grpc.sh * some cleanups * console * move console * fix tests and merging * js linting * merge * merging and configs * change k8s base to current ports * fixes * cleanup * regenerate proto * remove unnecessary whitespace * missing param * go mod tidy * fix merging * move login pkg * cleanup * move api pkgs again * fix pkg naming * fix generate-static.sh for login * update workflow * fixes * logging * remove duplicate * comment for optional gateway interfaces * regenerate protos * fix proto imports for grpc web * protos * grpc web generate * grpc web generate * fix changes * add translation interceptor * fix merging * regenerate mgmt proto
This commit is contained in:
@@ -3,12 +3,14 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
grpc_util "github.com/caos/zitadel/internal/api/grpc"
|
||||
client_middleware "github.com/caos/zitadel/internal/api/grpc/client/middleware"
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||
@@ -22,26 +24,41 @@ const (
|
||||
var (
|
||||
DefaultJSONMarshaler = &runtime.JSONPb{OrigName: false, EmitDefaults: false}
|
||||
|
||||
DefaultServeMuxOptions = []runtime.ServeMuxOption{
|
||||
runtime.WithMarshalerOption(DefaultJSONMarshaler.ContentType(), DefaultJSONMarshaler),
|
||||
runtime.WithMarshalerOption(mimeWildcard, DefaultJSONMarshaler),
|
||||
runtime.WithMarshalerOption(runtime.MIMEWildcard, DefaultJSONMarshaler),
|
||||
runtime.WithIncomingHeaderMatcher(runtime.DefaultHeaderMatcher),
|
||||
runtime.WithOutgoingHeaderMatcher(runtime.DefaultHeaderMatcher),
|
||||
DefaultServeMuxOptions = func(customHeaders ...string) []runtime.ServeMuxOption {
|
||||
return []runtime.ServeMuxOption{
|
||||
runtime.WithMarshalerOption(DefaultJSONMarshaler.ContentType(), DefaultJSONMarshaler),
|
||||
runtime.WithMarshalerOption(mimeWildcard, DefaultJSONMarshaler),
|
||||
runtime.WithMarshalerOption(runtime.MIMEWildcard, DefaultJSONMarshaler),
|
||||
runtime.WithIncomingHeaderMatcher(DefaultHeaderMatcher(customHeaders...)),
|
||||
runtime.WithOutgoingHeaderMatcher(runtime.DefaultHeaderMatcher),
|
||||
}
|
||||
}
|
||||
|
||||
DefaultHeaderMatcher = func(customHeaders ...string) runtime.HeaderMatcherFunc {
|
||||
return func(header string) (string, bool) {
|
||||
for _, customHeader := range customHeaders {
|
||||
if strings.HasPrefix(strings.ToLower(header), customHeader) {
|
||||
return header, true
|
||||
}
|
||||
}
|
||||
return runtime.DefaultHeaderMatcher(header)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
type Gateway interface {
|
||||
GRPCEndpoint() string
|
||||
GatewayPort() string
|
||||
Gateway() GatewayFunc
|
||||
RegisterGateway() GatewayFunc
|
||||
GatewayPathPrefix() string
|
||||
}
|
||||
|
||||
type GatewayFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error
|
||||
|
||||
//optional extending interfaces of Gateway below
|
||||
|
||||
type gatewayCustomServeMuxOptions interface {
|
||||
GatewayServeMuxOptions() []runtime.ServeMuxOption
|
||||
}
|
||||
|
||||
type grpcGatewayCustomInterceptor interface {
|
||||
GatewayHTTPInterceptor(http.Handler) http.Handler
|
||||
}
|
||||
@@ -50,28 +67,62 @@ type gatewayCustomCallOptions interface {
|
||||
GatewayCallOptions() []grpc.DialOption
|
||||
}
|
||||
|
||||
func StartGateway(ctx context.Context, g Gateway) {
|
||||
mux := createMux(ctx, g)
|
||||
serveGateway(ctx, mux, gatewayPort(g.GatewayPort()), g)
|
||||
type GatewayHandler struct {
|
||||
mux *http.ServeMux
|
||||
serverPort string
|
||||
gatewayPort string
|
||||
customHeaders []string
|
||||
}
|
||||
|
||||
func createMux(ctx context.Context, g Gateway) *runtime.ServeMux {
|
||||
muxOptions := DefaultServeMuxOptions
|
||||
func CreateGatewayHandler(config grpc_util.Config) *GatewayHandler {
|
||||
return &GatewayHandler{
|
||||
mux: http.NewServeMux(),
|
||||
serverPort: config.ServerPort,
|
||||
gatewayPort: config.GatewayPort,
|
||||
customHeaders: config.CustomHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
//RegisterGateway registers a handler (Gateway interface) on defined port
|
||||
//Gateway interface may be extended with optional implementation of interfaces (gatewayCustomServeMuxOptions, ...)
|
||||
func (g *GatewayHandler) RegisterGateway(ctx context.Context, gateway Gateway) {
|
||||
handler := createGateway(ctx, gateway, g.serverPort, g.customHeaders...)
|
||||
prefix := gateway.GatewayPathPrefix()
|
||||
g.RegisterHandler(prefix, handler)
|
||||
}
|
||||
|
||||
func (g *GatewayHandler) RegisterHandler(prefix string, handler http.Handler) {
|
||||
http_util.RegisterHandler(g.mux, prefix, handler)
|
||||
}
|
||||
|
||||
func (g *GatewayHandler) Serve(ctx context.Context) {
|
||||
http_util.Serve(ctx, g.mux, g.gatewayPort, "api")
|
||||
}
|
||||
|
||||
func createGateway(ctx context.Context, g Gateway, port string, customHeaders ...string) http.Handler {
|
||||
mux := createMux(g, customHeaders...)
|
||||
opts := createDialOptions(g)
|
||||
err := g.RegisterGateway()(ctx, mux, http_util.Endpoint(port), opts)
|
||||
logging.Log("SERVE-7B7G0E").OnError(err).Panic("failed to register grpc gateway")
|
||||
return addInterceptors(mux, g)
|
||||
}
|
||||
|
||||
func createMux(g Gateway, customHeaders ...string) *runtime.ServeMux {
|
||||
muxOptions := DefaultServeMuxOptions(customHeaders...)
|
||||
if customOpts, ok := g.(gatewayCustomServeMuxOptions); ok {
|
||||
muxOptions = customOpts.GatewayServeMuxOptions()
|
||||
}
|
||||
mux := runtime.NewServeMux(muxOptions...)
|
||||
return runtime.NewServeMux(muxOptions...)
|
||||
}
|
||||
|
||||
func createDialOptions(g Gateway) []grpc.DialOption {
|
||||
opts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
opts = append(opts, client_middleware.DefaultTracingStatsClient())
|
||||
|
||||
if customOpts, ok := g.(gatewayCustomCallOptions); ok {
|
||||
opts = append(opts, customOpts.GatewayCallOptions()...)
|
||||
}
|
||||
err := g.Gateway()(ctx, mux, g.GRPCEndpoint(), opts)
|
||||
logging.Log("SERVE-7B7G0E").OnError(err).Panic("failed to create mux for grpc gateway")
|
||||
|
||||
return mux
|
||||
return opts
|
||||
}
|
||||
|
||||
func addInterceptors(handler http.Handler, g Gateway) http.Handler {
|
||||
@@ -83,26 +134,6 @@ func addInterceptors(handler http.Handler, g Gateway) http.Handler {
|
||||
return http_mw.CORSInterceptorOpts(http_mw.DefaultCORSOptions, handler)
|
||||
}
|
||||
|
||||
func serveGateway(ctx context.Context, handler http.Handler, port string, g Gateway) {
|
||||
server := &http.Server{
|
||||
Handler: addInterceptors(handler, g),
|
||||
}
|
||||
|
||||
listener := http_util.CreateListener(port)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
err := server.Shutdown(ctx)
|
||||
logging.Log("SERVE-m7kBlq").OnError(err).Warn("error during graceful shutdown of grpc gateway")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := server.Serve(listener)
|
||||
logging.Log("SERVE-tBHR60").OnError(err).Panic("grpc gateway serve failed")
|
||||
}()
|
||||
logging.LogWithFields("SERVE-KHh0Cb", "port", port).Info("grpc gateway is listening")
|
||||
}
|
||||
|
||||
func gatewayPort(port string) string {
|
||||
if port == "" {
|
||||
return defaultGatewayPort
|
||||
|
@@ -2,37 +2,39 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/caos/zitadel/internal/api"
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
grpc_util "github.com/caos/zitadel/internal/api/grpc"
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
)
|
||||
|
||||
func AuthorizationInterceptor(verifier auth.TokenVerifier, authConfig *auth.Config, authMethods auth.MethodMapping) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
authOpt, needsToken := authMethods[info.FullMethod]
|
||||
if !needsToken {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
authToken := ""
|
||||
//TODO: Remove check internal as soon as authentification is implemented
|
||||
if !auth.CheckInternal(ctx) {
|
||||
authToken = grpc_util.GetAuthorizationHeader(ctx)
|
||||
if authToken == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, "auth header missing")
|
||||
}
|
||||
}
|
||||
orgID := grpc_util.GetHeader(ctx, api.ZitadelOrgID)
|
||||
|
||||
ctx, err := auth.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
return authorize(ctx, req, info, handler, verifier, authConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func authorize(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier *authz.TokenVerifier, authConfig authz.Config) (interface{}, error) {
|
||||
authOpt, needsToken := verifier.CheckAuthMethod(info.FullMethod)
|
||||
if !needsToken {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
authToken := grpc_util.GetAuthorizationHeader(ctx)
|
||||
if authToken == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, "auth header missing")
|
||||
}
|
||||
|
||||
orgID := grpc_util.GetHeader(ctx, http.ZitadelOrgID)
|
||||
|
||||
ctx, err := authz.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt, info.FullMethod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
148
internal/api/grpc/server/middleware/auth_interceptor_test.go
Normal file
148
internal/api/grpc/server/middleware/auth_interceptor_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
)
|
||||
|
||||
var (
|
||||
mockMethods = authz.MethodMapping{
|
||||
"need.authentication": authz.Option{
|
||||
Permission: "authenticated",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type verifierMock struct{}
|
||||
|
||||
func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
func (v *verifierMock) ResolveGrants(ctx context.Context) (*authz.Grant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (v *verifierMock) ProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (v *verifierMock) VerifierClientID(ctx context.Context, appName string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func Test_authorize(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req interface{}
|
||||
info *grpc.UnaryServerInfo
|
||||
handler grpc.UnaryHandler
|
||||
verifier *authz.TokenVerifier
|
||||
authConfig authz.Config
|
||||
authMethods authz.MethodMapping
|
||||
}
|
||||
type res struct {
|
||||
want interface{}
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no token needed ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
req: &mockReq{},
|
||||
info: mockInfo("/no/token/needed"),
|
||||
handler: emptyMockHandler,
|
||||
verifier: func() *authz.TokenVerifier {
|
||||
verifier := authz.Start(&verifierMock{})
|
||||
verifier.RegisterServer("need", "need", authz.MethodMapping{})
|
||||
return verifier
|
||||
}(),
|
||||
authMethods: mockMethods,
|
||||
},
|
||||
res{
|
||||
&mockReq{},
|
||||
false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"auth header missing error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
req: &mockReq{},
|
||||
info: mockInfo("/need/authentication"),
|
||||
handler: emptyMockHandler,
|
||||
verifier: func() *authz.TokenVerifier {
|
||||
verifier := authz.Start(&verifierMock{})
|
||||
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
||||
return verifier
|
||||
}(),
|
||||
authConfig: authz.Config{},
|
||||
authMethods: mockMethods,
|
||||
},
|
||||
res{
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"unauthorized error",
|
||||
args{
|
||||
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "wrong")),
|
||||
req: &mockReq{},
|
||||
info: mockInfo("/need/authentication"),
|
||||
handler: emptyMockHandler,
|
||||
verifier: func() *authz.TokenVerifier {
|
||||
verifier := authz.Start(&verifierMock{})
|
||||
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
||||
return verifier
|
||||
}(),
|
||||
authConfig: authz.Config{},
|
||||
authMethods: mockMethods,
|
||||
},
|
||||
res{
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"authorized ok",
|
||||
args{
|
||||
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "Bearer token")),
|
||||
req: &mockReq{},
|
||||
info: mockInfo("/need/authentication"),
|
||||
handler: emptyMockHandler,
|
||||
verifier: func() *authz.TokenVerifier {
|
||||
verifier := authz.Start(&verifierMock{})
|
||||
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
||||
return verifier
|
||||
}(),
|
||||
authConfig: authz.Config{},
|
||||
authMethods: mockMethods,
|
||||
},
|
||||
res{
|
||||
&mockReq{},
|
||||
false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := authorize(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier, tt.args.authConfig)
|
||||
if (err != nil) != tt.res.wantErr {
|
||||
t.Errorf("authorize() error = %v, wantErr %v", err, tt.res.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.res.want) {
|
||||
t.Errorf("authorize() got = %v, want %v", got, tt.res.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -4,18 +4,22 @@ import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
grpc_util "github.com/caos/zitadel/internal/api/grpc"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
_ "github.com/caos/zitadel/internal/statik"
|
||||
)
|
||||
|
||||
func ErrorHandler(defaultLanguage language.Tag) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
func ErrorHandler(defaultLanguage language.Tag) grpc.UnaryServerInterceptor {
|
||||
translator := newZitadelTranslator(defaultLanguage)
|
||||
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
resp, err := handler(ctx, req)
|
||||
return resp, grpc_util.CaosToGRPCError(err, ctx, translator)
|
||||
return toGRPCError(ctx, req, handler, translator)
|
||||
}
|
||||
}
|
||||
|
||||
func toGRPCError(ctx context.Context, req interface{}, handler grpc.UnaryHandler, translator *i18n.Translator) (interface{}, error) {
|
||||
resp, err := handler(ctx, req)
|
||||
return resp, grpc_util.CaosToGRPCError(ctx, err, translator)
|
||||
}
|
||||
|
@@ -0,0 +1,63 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func Test_toGRPCError(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req interface{}
|
||||
handler grpc.UnaryHandler
|
||||
}
|
||||
type res struct {
|
||||
want interface{}
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
req: &mockReq{},
|
||||
handler: emptyMockHandler,
|
||||
},
|
||||
res{
|
||||
&mockReq{},
|
||||
false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
req: &mockReq{},
|
||||
handler: errorMockHandler,
|
||||
},
|
||||
res{
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := toGRPCError(tt.args.ctx, tt.args.req, tt.args.handler, nil)
|
||||
if (err != nil) != tt.res.wantErr {
|
||||
t.Errorf("toGRPCError() error = %v, wantErr %v", err, tt.res.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.res.want) {
|
||||
t.Errorf("toGRPCError() got = %v, want %v", got, tt.res.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
26
internal/api/grpc/server/middleware/mock_test.go
Normal file
26
internal/api/grpc/server/middleware/mock_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func emptyMockHandler(_ context.Context, req interface{}) (interface{}, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func errorMockHandler(_ context.Context, req interface{}) (interface{}, error) {
|
||||
return nil, errors.ThrowInternal(nil, "test", "error")
|
||||
}
|
||||
|
||||
type mockReq struct{}
|
||||
|
||||
func mockInfo(path string) *grpc.UnaryServerInfo {
|
||||
return &grpc.UnaryServerInfo{
|
||||
Server: nil,
|
||||
FullMethod: path,
|
||||
}
|
||||
}
|
@@ -9,13 +9,28 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/stats"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/tracing"
|
||||
)
|
||||
|
||||
type GRPCMethod string
|
||||
|
||||
func TracingStatsServer(ignoredMethods ...GRPCMethod) grpc.ServerOption {
|
||||
return grpc.StatsHandler(&tracingServerHandler{ignoredMethods, ocgrpc.ServerHandler{StartOptions: trace.StartOptions{Sampler: tracing.Sampler(), SpanKind: trace.SpanKindServer}}})
|
||||
return grpc.StatsHandler(
|
||||
&tracingServerHandler{
|
||||
ignoredMethods,
|
||||
ocgrpc.ServerHandler{
|
||||
StartOptions: trace.StartOptions{
|
||||
Sampler: tracing.Sampler(),
|
||||
SpanKind: trace.SpanKindServer,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func DefaultTracingStatsServer() grpc.ServerOption {
|
||||
return TracingStatsServer(http.Healthz, http.Readiness, http.Validation)
|
||||
}
|
||||
|
||||
type tracingServerHandler struct {
|
||||
|
71
internal/api/grpc/server/middleware/tracing_test.go
Normal file
71
internal/api/grpc/server/middleware/tracing_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
func Test_tracingServerHandler_TagRPC(t *testing.T) {
|
||||
type fields struct {
|
||||
IgnoredMethods []GRPCMethod
|
||||
ServerHandler ocgrpc.ServerHandler
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
tagInfo *stats.RPCTagInfo
|
||||
}
|
||||
type res struct {
|
||||
wantSpan bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"ignored method",
|
||||
fields{
|
||||
IgnoredMethods: []GRPCMethod{"ignore"},
|
||||
ServerHandler: ocgrpc.ServerHandler{},
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
tagInfo: &stats.RPCTagInfo{
|
||||
FullMethodName: "ignore",
|
||||
},
|
||||
},
|
||||
res{false},
|
||||
},
|
||||
{
|
||||
"tag",
|
||||
fields{
|
||||
IgnoredMethods: []GRPCMethod{"ignore"},
|
||||
ServerHandler: ocgrpc.ServerHandler{},
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
tagInfo: &stats.RPCTagInfo{
|
||||
FullMethodName: "tag",
|
||||
},
|
||||
},
|
||||
res{true},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &tracingServerHandler{
|
||||
IgnoredMethods: tt.fields.IgnoredMethods,
|
||||
ServerHandler: tt.fields.ServerHandler,
|
||||
}
|
||||
got := s.TagRPC(tt.args.ctx, tt.args.tagInfo)
|
||||
if (trace.FromContext(got) != nil) != tt.res.wantSpan {
|
||||
t.Errorf("TagRPC() = %v, want %v", got, tt.res.wantSpan)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -26,7 +26,10 @@ func (v *Validator) Healthz(_ context.Context, e *empty.Empty) (*empty.Empty, er
|
||||
}
|
||||
|
||||
func (v *Validator) Ready(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {
|
||||
return e, ready(ctx, v.validations)
|
||||
if len(validate(ctx, v.validations)) == 0 {
|
||||
return e, nil
|
||||
}
|
||||
return nil, errors.ThrowInternal(nil, "API-2jD9a", "not ready")
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(ctx context.Context, _ *empty.Empty) (*structpb.Struct, error) {
|
||||
@@ -34,13 +37,6 @@ func (v *Validator) Validate(ctx context.Context, _ *empty.Empty) (*structpb.Str
|
||||
return proto.ToPBStruct(validations)
|
||||
}
|
||||
|
||||
func ready(ctx context.Context, validations map[string]ValidationFunction) error {
|
||||
if len(validate(ctx, validations)) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.ThrowInternal(nil, "API-2jD9a", "not ready")
|
||||
}
|
||||
|
||||
func validate(ctx context.Context, validations map[string]ValidationFunction) map[string]error {
|
||||
errors := make(map[string]error)
|
||||
for id, validation := range validations {
|
||||
|
158
internal/api/grpc/server/probes_test.go
Normal file
158
internal/api/grpc/server/probes_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestValidator_Healthz(t *testing.T) {
|
||||
type fields struct {
|
||||
validations map[string]ValidationFunction
|
||||
}
|
||||
type res struct {
|
||||
want *empty.Empty
|
||||
hasErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
fields{},
|
||||
res{
|
||||
&empty.Empty{},
|
||||
false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &Validator{
|
||||
validations: tt.fields.validations,
|
||||
}
|
||||
got, err := v.Healthz(nil, &empty.Empty{})
|
||||
if (err != nil) != tt.res.hasErr {
|
||||
t.Errorf("Healthz() error = %v, wantErr %v", err, tt.res.hasErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.res.want) {
|
||||
t.Errorf("Healthz() got = %v, want %v", got, tt.res.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidator_Ready(t *testing.T) {
|
||||
type fields struct {
|
||||
validations map[string]ValidationFunction
|
||||
}
|
||||
type res struct {
|
||||
want *empty.Empty
|
||||
hasErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"unready error",
|
||||
fields{validations: map[string]ValidationFunction{
|
||||
"error": func(_ context.Context) error {
|
||||
return errors.ThrowInternal(nil, "id", "message")
|
||||
},
|
||||
}},
|
||||
res{
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ready ok",
|
||||
fields{validations: map[string]ValidationFunction{
|
||||
"ok": func(_ context.Context) error {
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
res{
|
||||
&empty.Empty{},
|
||||
false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &Validator{
|
||||
validations: tt.fields.validations,
|
||||
}
|
||||
got, err := v.Ready(context.Background(), &empty.Empty{})
|
||||
if (err != nil) != tt.res.hasErr {
|
||||
t.Errorf("Ready() error = %v, wantErr %v", err, tt.res.hasErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.res.want) {
|
||||
t.Errorf("Ready() got = %v, want %v", got, tt.res.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validate(t *testing.T) {
|
||||
type args struct {
|
||||
validations map[string]ValidationFunction
|
||||
}
|
||||
type res struct {
|
||||
want map[string]error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no error empty",
|
||||
args{
|
||||
validations: map[string]ValidationFunction{
|
||||
"ok": func(_ context.Context) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
map[string]error{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"error in list",
|
||||
args{
|
||||
validations: map[string]ValidationFunction{
|
||||
"ok": func(_ context.Context) error {
|
||||
return nil
|
||||
},
|
||||
"error": func(_ context.Context) error {
|
||||
return errors.ThrowInternal(nil, "id", "message")
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
map[string]error{
|
||||
"error": errors.ThrowInternal(nil, "id", "message"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := validate(context.Background(), tt.args.validations); !reflect.DeepEqual(got, tt.res.want) {
|
||||
t.Errorf("validate() = %v, want %v", got, tt.res.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -2,12 +2,14 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"net"
|
||||
|
||||
"github.com/caos/logging"
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/grpc/server/middleware"
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
)
|
||||
|
||||
@@ -16,30 +18,36 @@ const (
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
GRPCPort() string
|
||||
GRPCServer(defaults systemdefaults.SystemDefaults) (*grpc.Server, error)
|
||||
Gateway
|
||||
RegisterServer(*grpc.Server)
|
||||
AppName() string
|
||||
MethodPrefix() string
|
||||
AuthMethods() authz.MethodMapping
|
||||
}
|
||||
|
||||
func StartServer(ctx context.Context, s Server, defaults systemdefaults.SystemDefaults) {
|
||||
port := grpcPort(s.GRPCPort())
|
||||
listener := http.CreateListener(port)
|
||||
server := createGrpcServer(s, defaults)
|
||||
serveServer(ctx, server, listener, port)
|
||||
func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, lang language.Tag) *grpc.Server {
|
||||
return grpc.NewServer(
|
||||
middleware.TracingStatsServer(http.Healthz, http.Readiness, http.Validation),
|
||||
grpc.UnaryInterceptor(
|
||||
grpc_middleware.ChainUnaryServer(
|
||||
middleware.ErrorHandler(lang),
|
||||
middleware.TranslationHandler(lang),
|
||||
grpc_middleware.ChainUnaryServer(
|
||||
middleware.AuthorizationInterceptor(verifier, authConfig),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func createGrpcServer(s Server, defaults systemdefaults.SystemDefaults) *grpc.Server {
|
||||
grpcServer, err := s.GRPCServer(defaults)
|
||||
logging.Log("SERVE-k280HZ").OnError(err).Panic("failed to create grpc server")
|
||||
return grpcServer
|
||||
}
|
||||
|
||||
func serveServer(ctx context.Context, server *grpc.Server, listener net.Listener, port string) {
|
||||
func Serve(ctx context.Context, server *grpc.Server, port string) {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
server.GracefulStop()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
listener := http.CreateListener(port)
|
||||
err := server.Serve(listener)
|
||||
logging.Log("SERVE-Ga3e94").OnError(err).Panic("grpc server serve failed")
|
||||
}()
|
||||
|
Reference in New Issue
Block a user