diff --git a/cmd/start/start.go b/cmd/start/start.go index 163d1de2f2..a3175bd7d0 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -357,7 +357,7 @@ func startAPIs( http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))), ) limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, &config.Quotas.Access.AccessConfig) - apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor) + apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, config.ExternalDomain, limitingAccessInterceptor) if err != nil { return fmt.Errorf("error creating api %w", err) } @@ -404,7 +404,7 @@ func startAPIs( if err := apis.RegisterService(ctx, execution_v3_alpha.CreateServer(commands, queries)); err != nil { return err } - instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...) + instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, config.ExternalDomain, login.IgnoreInstanceEndpoints...) assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge) apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle)) diff --git a/internal/api/api.go b/internal/api/api.go index 538d22f48d..00d4a568e3 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -49,7 +49,7 @@ func New( queries *query.Queries, verifier internal_authz.APITokenVerifier, authZ internal_authz.Config, - tlsConfig *tls.Config, http2HostName, http1HostName string, + tlsConfig *tls.Config, http2HostName, http1HostName, externalDomain string, accessInterceptor *http_mw.AccessInterceptor, ) (_ *API, err error) { api := &API{ @@ -62,7 +62,7 @@ func New( accessInterceptor: accessInterceptor, } - api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService()) + api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, externalDomain, tlsConfig, accessInterceptor.AccessService()) api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor, tlsConfig) if err != nil { return nil, err diff --git a/internal/api/assets/asset.go b/internal/api/assets/asset.go index 5836d66e0b..5f30c94a29 100644 --- a/internal/api/assets/asset.go +++ b/internal/api/assets/asset.go @@ -2,6 +2,7 @@ package assets import ( "context" + "errors" "fmt" "io" "net/http" @@ -12,14 +13,17 @@ import ( "github.com/gabriel-vasile/mimetype" "github.com/gorilla/mux" "github.com/zitadel/logging" + "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" http_util "github.com/zitadel/zitadel/internal/api/http" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/static" + "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -73,19 +77,29 @@ type Downloader interface { type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error, defaultCode int) -func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, defaultCode int) { - logging.WithFields("uri", r.RequestURI).WithError(err).Warn("error occurred on asset api") - code, ok := http_util.ZitadelErrorToHTTPStatusCode(err) - if !ok { - code = defaultCode +func DefaultErrorHandler(translator *i18n.Translator) func(w http.ResponseWriter, r *http.Request, err error, defaultCode int) { + return func(w http.ResponseWriter, r *http.Request, err error, defaultCode int) { + logging.WithFields("uri", r.RequestURI).WithError(err).Warn("error occurred on asset api") + code, ok := http_util.ZitadelErrorToHTTPStatusCode(err) + if !ok { + code = defaultCode + } + zErr := new(zerrors.ZitadelError) + if errors.As(err, &zErr) { + zErr.SetMessage(translator.LocalizeFromCtx(r.Context(), zErr.GetMessage(), nil)) + zErr.Parent = nil // ensuring we don't leak any unwanted information + err = zErr + } + http.Error(w, err.Error(), code) } - http.Error(w, err.Error(), code) } func NewHandler(commands *command.Commands, verifier authz.APITokenVerifier, authConfig authz.Config, idGenerator id.Generator, storage static.Storage, queries *query.Queries, callDurationInterceptor, instanceInterceptor, assetCacheInterceptor, accessInterceptor func(handler http.Handler) http.Handler) http.Handler { + translator, err := i18n.NewZitadelTranslator(language.English) + logging.OnError(err).Panic("unable to get translator") h := &Handler{ commands: commands, - errorHandler: DefaultErrorHandler, + errorHandler: DefaultErrorHandler(translator), authInterceptor: http_mw.AuthorizationInterceptor(verifier, authConfig), idGenerator: idGenerator, storage: storage, diff --git a/internal/api/grpc/gerrors/zitadel_errors.go b/internal/api/grpc/gerrors/zitadel_errors.go index 60e8473898..94f9c177ce 100644 --- a/internal/api/grpc/gerrors/zitadel_errors.go +++ b/internal/api/grpc/gerrors/zitadel_errors.go @@ -2,6 +2,7 @@ package gerrors import ( "errors" + "strings" "github.com/zitadel/logging" "google.golang.org/grpc/codes" @@ -35,6 +36,9 @@ func ExtractZITADELError(err error) (c codes.Code, msg, id string, ok bool) { if err == nil { return codes.OK, "", "", false } + if strings.Contains(err.Error(), "failed to connect to") { // version of pgx does not yet export the error type + return codes.Internal, "db connection error", "", true + } zitadelErr := new(zerrors.ZitadelError) if ok := errors.As(err, &zitadelErr); !ok { return codes.Unknown, err.Error(), "", false diff --git a/internal/api/grpc/server/middleware/instance_interceptor.go b/internal/api/grpc/server/middleware/instance_interceptor.go index 31f5e2c168..ba637d59fa 100644 --- a/internal/api/grpc/server/middleware/instance_interceptor.go +++ b/internal/api/grpc/server/middleware/instance_interceptor.go @@ -14,6 +14,7 @@ import ( "google.golang.org/grpc/status" "github.com/zitadel/zitadel/internal/api/authz" + zitadel_http "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" @@ -23,15 +24,15 @@ const ( HTTP1Host = "x-zitadel-http1-host" ) -func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string, explicitInstanceIdServices ...string) grpc.UnaryServerInterceptor { +func InstanceInterceptor(verifier authz.InstanceVerifier, headerName, externalDomain string, explicitInstanceIdServices ...string) grpc.UnaryServerInterceptor { translator, err := i18n.NewZitadelTranslator(language.English) logging.OnError(err).Panic("unable to get translator") return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - return setInstance(ctx, req, info, handler, verifier, headerName, translator, explicitInstanceIdServices...) + return setInstance(ctx, req, info, handler, verifier, headerName, externalDomain, translator, explicitInstanceIdServices...) } } -func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.InstanceVerifier, headerName string, translator *i18n.Translator, idFromRequestsServices ...string) (_ interface{}, err error) { +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) { interceptorCtx, span := tracing.NewServerInterceptorSpan(ctx) defer func() { span.EndWithError(err) }() for _, service := range idFromRequestsServices { @@ -55,18 +56,21 @@ func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInf return handler(authz.WithInstance(ctx, instance), req) } } - host, err := hostFromContext(interceptorCtx, headerName) if err != nil { return nil, status.Error(codes.NotFound, err.Error()) } instance, err := verifier.InstanceByHost(interceptorCtx, host) if err != nil { - notFoundErr := new(zerrors.NotFoundError) - if errors.As(err, ¬FoundErr) { - notFoundErr.Message = translator.LocalizeFromCtx(ctx, notFoundErr.GetMessage(), nil) + origin := zitadel_http.ComposedOrigin(ctx) + logging.WithFields("origin", origin, "externalDomain", externalDomain).WithError(err).Error("unable to set instance") + zErr := new(zerrors.ZitadelError) + if errors.As(err, &zErr) { + zErr.SetMessage(translator.LocalizeFromCtx(ctx, zErr.GetMessage(), nil)) + zErr.Parent = err + return nil, status.Error(codes.NotFound, fmt.Sprintf("unable to set instance using origin %s (ExternalDomain is %s): %s", origin, externalDomain, zErr)) } - return nil, status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, fmt.Sprintf("unable to set instance using origin %s (ExternalDomain is %s)", origin, externalDomain)) } span.End() return handler(authz.WithInstance(ctx, instance), req) diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go index c957dda022..4f0a6140bc 100644 --- a/internal/api/grpc/server/server.go +++ b/internal/api/grpc/server/server.go @@ -39,6 +39,7 @@ func CreateServer( authConfig authz.Config, queries *query.Queries, hostHeaderName string, + externalDomain string, tlsConfig *tls.Config, accessSvc *logstore.Service[*record.AccessLog], ) *grpc.Server { @@ -50,7 +51,7 @@ func CreateServer( middleware.DefaultTracingServer(), middleware.MetricsHandler(metricTypes, grpc_api.Probes...), middleware.NoCacheInterceptor(), - middleware.InstanceInterceptor(queries, hostHeaderName, system_pb.SystemService_ServiceDesc.ServiceName, healthpb.Health_ServiceDesc.ServiceName), + middleware.InstanceInterceptor(queries, hostHeaderName, externalDomain, system_pb.SystemService_ServiceDesc.ServiceName, healthpb.Health_ServiceDesc.ServiceName), middleware.AccessStorageInterceptor(accessSvc), middleware.ErrorHandler(), middleware.LimitsInterceptor(system_pb.SystemService_ServiceDesc.ServiceName), diff --git a/internal/api/http/middleware/instance_interceptor.go b/internal/api/http/middleware/instance_interceptor.go index ac944278b1..2117b98d30 100644 --- a/internal/api/http/middleware/instance_interceptor.go +++ b/internal/api/http/middleware/instance_interceptor.go @@ -19,16 +19,17 @@ import ( ) type instanceInterceptor struct { - verifier authz.InstanceVerifier - headerName string - ignoredPrefixes []string - translator *i18n.Translator + verifier authz.InstanceVerifier + headerName, externalDomain string + ignoredPrefixes []string + translator *i18n.Translator } -func InstanceInterceptor(verifier authz.InstanceVerifier, headerName string, ignoredPrefixes ...string) *instanceInterceptor { +func InstanceInterceptor(verifier authz.InstanceVerifier, headerName, externalDomain string, ignoredPrefixes ...string) *instanceInterceptor { return &instanceInterceptor{ verifier: verifier, headerName: headerName, + externalDomain: externalDomain, ignoredPrefixes: ignoredPrefixes, translator: newZitadelTranslator(), } @@ -55,11 +56,15 @@ func (a *instanceInterceptor) handleInstance(w http.ResponseWriter, r *http.Requ } ctx, err := setInstance(r, a.verifier, a.headerName) if err != nil { - caosErr := new(zerrors.NotFoundError) - if errors.As(err, &caosErr) { - caosErr.Message = a.translator.LocalizeFromRequest(r, caosErr.GetMessage(), nil) + origin := zitadel_http.ComposedOrigin(r.Context()) + logging.WithFields("origin", origin, "externalDomain", a.externalDomain).WithError(err).Error("unable to set instance") + zErr := new(zerrors.ZitadelError) + if errors.As(err, &zErr) { + zErr.SetMessage(a.translator.LocalizeFromRequest(r, zErr.GetMessage(), nil)) + http.Error(w, fmt.Sprintf("unable to set instance using origin %s (ExternalDomain is %s): %s", origin, a.externalDomain, zErr), http.StatusNotFound) + return } - http.Error(w, err.Error(), http.StatusNotFound) + http.Error(w, fmt.Sprintf("unable to set instance using origin %s (ExternalDomain is %s)", origin, a.externalDomain), http.StatusNotFound) return } r = r.WithContext(ctx) @@ -68,13 +73,13 @@ func (a *instanceInterceptor) handleInstance(w http.ResponseWriter, r *http.Requ func setInstance(r *http.Request, verifier authz.InstanceVerifier, headerName string) (_ context.Context, err error) { ctx := r.Context() - authCtx, span := tracing.NewServerInterceptorSpan(ctx) defer func() { span.EndWithError(err) }() host, err := HostFromRequest(r, headerName) + if err != nil { - return nil, zerrors.ThrowNotFound(err, "INST-zWq7X", "Errors.Instance.NotFound") + return nil, zerrors.ThrowNotFound(err, "INST-zWq7X", "Errors.IAM.NotFound") } instance, err := verifier.InstanceByHost(authCtx, host)