2022-03-29 09:53:19 +00:00
|
|
|
package middleware
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-12-08 14:30:55 +00:00
|
|
|
"errors"
|
2022-03-29 09:53:19 +00:00
|
|
|
"fmt"
|
2022-04-21 10:37:39 +00:00
|
|
|
"strings"
|
2022-03-29 09:53:19 +00:00
|
|
|
|
2022-08-17 06:07:41 +00:00
|
|
|
"github.com/zitadel/logging"
|
|
|
|
"golang.org/x/text/language"
|
2022-03-29 09:53:19 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
2022-04-26 23:01:45 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
2024-02-28 10:49:57 +00:00
|
|
|
zitadel_http "github.com/zitadel/zitadel/internal/api/http"
|
2022-08-17 06:07:41 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/i18n"
|
2022-05-02 15:26:54 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
2023-12-08 14:30:55 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
2022-03-29 09:53:19 +00:00
|
|
|
)
|
|
|
|
|
2022-06-03 12:44:04 +00:00
|
|
|
const (
|
|
|
|
HTTP1Host = "x-zitadel-http1-host"
|
|
|
|
)
|
|
|
|
|
2024-02-28 10:49:57 +00:00
|
|
|
func InstanceInterceptor(verifier authz.InstanceVerifier, headerName, externalDomain string, explicitInstanceIdServices ...string) grpc.UnaryServerInterceptor {
|
2023-12-05 11:12:01 +00:00
|
|
|
translator, err := i18n.NewZitadelTranslator(language.English)
|
2022-08-17 06:07:41 +00:00
|
|
|
logging.OnError(err).Panic("unable to get translator")
|
2022-03-29 09:53:19 +00:00
|
|
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
2024-02-28 10:49:57 +00:00
|
|
|
return setInstance(ctx, req, info, handler, verifier, headerName, externalDomain, translator, explicitInstanceIdServices...)
|
2022-03-29 09:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-28 10:49:57 +00:00
|
|
|
func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName, externalDomain string, translator *i18n.Translator, idFromRequestsServices ...string) (_ interface{}, err error) {
|
2022-05-02 15:26:54 +00:00
|
|
|
interceptorCtx, span := tracing.NewServerInterceptorSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
2023-02-15 01:52:11 +00:00
|
|
|
for _, service := range idFromRequestsServices {
|
2022-05-30 11:38:30 +00:00
|
|
|
if !strings.HasPrefix(service, "/") {
|
|
|
|
service = "/" + service
|
|
|
|
}
|
2022-04-21 10:37:39 +00:00
|
|
|
if strings.HasPrefix(info.FullMethod, service) {
|
2023-02-15 01:52:11 +00:00
|
|
|
withInstanceIDProperty, ok := req.(interface{ GetInstanceId() string })
|
|
|
|
if !ok {
|
|
|
|
return handler(ctx, req)
|
|
|
|
}
|
|
|
|
ctx = authz.WithInstanceID(ctx, withInstanceIDProperty.GetInstanceId())
|
|
|
|
instance, err := verifier.InstanceByID(ctx)
|
|
|
|
if err != nil {
|
2023-12-08 14:30:55 +00:00
|
|
|
notFoundErr := new(zerrors.NotFoundError)
|
|
|
|
if errors.As(err, ¬FoundErr) {
|
2023-02-15 01:52:11 +00:00
|
|
|
notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil)
|
|
|
|
}
|
|
|
|
return nil, status.Error(codes.NotFound, err.Error())
|
|
|
|
}
|
|
|
|
return handler(authz.WithInstance(ctx, instance), req)
|
2022-04-21 10:37:39 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-03 12:44:04 +00:00
|
|
|
host, err := hostFromContext(interceptorCtx, headerName)
|
2022-03-29 09:53:19 +00:00
|
|
|
if err != nil {
|
2022-08-17 06:07:41 +00:00
|
|
|
return nil, status.Error(codes.NotFound, err.Error())
|
2022-03-29 09:53:19 +00:00
|
|
|
}
|
2022-05-02 15:26:54 +00:00
|
|
|
instance, err := verifier.InstanceByHost(interceptorCtx, host)
|
2022-03-29 09:53:19 +00:00
|
|
|
if err != nil {
|
2024-02-28 10:49:57 +00:00
|
|
|
err = fmt.Errorf("unable to set instance using origin %s (ExternalDomain is %s): %w", zitadel_http.ComposedOrigin(ctx), externalDomain, err)
|
|
|
|
zErr := new(zerrors.ZitadelError)
|
|
|
|
if errors.As(err, &zErr) {
|
|
|
|
zErr.SetMessage(translator.LocalizeFromCtx(ctx, zErr.GetMessage(), nil))
|
|
|
|
zErr.Parent = err
|
|
|
|
err = zErr
|
2022-08-17 06:07:41 +00:00
|
|
|
}
|
|
|
|
return nil, status.Error(codes.NotFound, err.Error())
|
2022-03-29 09:53:19 +00:00
|
|
|
}
|
2022-05-02 15:26:54 +00:00
|
|
|
span.End()
|
2022-03-29 09:53:19 +00:00
|
|
|
return handler(authz.WithInstance(ctx, instance), req)
|
|
|
|
}
|
|
|
|
|
2022-06-03 12:44:04 +00:00
|
|
|
func hostFromContext(ctx context.Context, headerName string) (string, error) {
|
2022-03-29 09:53:19 +00:00
|
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("cannot read metadata")
|
|
|
|
}
|
2022-06-03 12:44:04 +00:00
|
|
|
host, ok := md[HTTP1Host]
|
|
|
|
if ok && len(host) == 1 {
|
|
|
|
if !isAllowedToSendHTTP1Header(md) {
|
|
|
|
return "", fmt.Errorf("no valid host header")
|
|
|
|
}
|
|
|
|
return host[0], nil
|
|
|
|
}
|
|
|
|
host, ok = md[headerName]
|
2022-03-29 09:53:19 +00:00
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("cannot find header: %v", headerName)
|
|
|
|
}
|
|
|
|
if len(host) != 1 {
|
|
|
|
return "", fmt.Errorf("invalid host header: %v", host)
|
|
|
|
}
|
|
|
|
return host[0], nil
|
|
|
|
}
|
2022-06-03 12:44:04 +00:00
|
|
|
|
2022-10-31 13:03:23 +00:00
|
|
|
// isAllowedToSendHTTP1Header check if the gRPC call was sent to `localhost`
|
|
|
|
// this is only possible when calling the server directly running on localhost
|
|
|
|
// or through the gRPC gateway
|
2022-06-03 12:44:04 +00:00
|
|
|
func isAllowedToSendHTTP1Header(md metadata.MD) bool {
|
|
|
|
authority, ok := md[":authority"]
|
|
|
|
return ok && len(authority) == 1 && strings.Split(authority[0], ":")[0] == "localhost"
|
|
|
|
}
|