Files
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

372 lines
10 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package daemon
import (
"context"
"crypto/tls"
"net/http"
"time"
"github.com/pkg/errors"
"github.com/rs/cors"
"github.com/spf13/cobra"
"github.com/urfave/negroni"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"golang.org/x/sync/errgroup"
"github.com/ory/analytics-go/v5"
"github.com/ory/graceful"
"github.com/ory/kratos/cmd/courier"
"github.com/ory/kratos/driver"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/selfservice/errorx"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/selfservice/flow/logout"
"github.com/ory/kratos/selfservice/flow/recovery"
"github.com/ory/kratos/selfservice/flow/registration"
"github.com/ory/kratos/selfservice/flow/settings"
"github.com/ory/kratos/selfservice/flow/verification"
"github.com/ory/kratos/selfservice/strategy/link"
"github.com/ory/kratos/selfservice/strategy/oidc"
"github.com/ory/kratos/session"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/healthx"
"github.com/ory/x/httprouterx"
"github.com/ory/x/metricsx"
"github.com/ory/x/networkx"
"github.com/ory/x/otelx"
"github.com/ory/x/otelx/semconv"
"github.com/ory/x/popx"
"github.com/ory/x/prometheusx"
"github.com/ory/x/reqlog"
"github.com/ory/x/urlx"
)
func init() {
graceful.DefaultShutdownTimeout = 120 * time.Second
}
var httpMetrics = prometheusx.NewHTTPMetrics("kratos", prometheusx.HTTPPrefix, config.Version, config.Commit, config.Date)
func servePublic(ctx context.Context, r *driver.RegistryDefault, cmd *cobra.Command) (func() error, error) {
cfg := r.Config().ServePublic(ctx)
l := r.Logger()
rec := negroni.NewRecovery()
rec.Logger = l
router := httprouterx.NewRouterPublic()
n := negroni.New(
rec,
httpMetrics,
)
for _, mw := range r.HTTPMiddlewares() {
n.Use(mw)
}
publicLogger := reqlog.NewMiddlewareFromLogger(l, "public#"+cfg.BaseURL.String())
if cfg.RequestLog.DisableHealth {
publicLogger.ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath)
}
n.UseFunc(semconv.Middleware)
n.Use(publicLogger)
n.Use(x.HTTPLoaderContextMiddleware(r))
n.UseFunc(httprouterx.NoCacheNegroni)
n.Use(sqa(ctx, cmd, r))
csrf := nosurfx.NewCSRFHandler(router, r)
// we need to always load the CORS middleware even if it is disabled, to allow hot-enabling CORS
n.UseFunc(func(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
cfg, enabled := r.Config().CORSPublic(req.Context())
if !enabled {
next(w, req)
return
}
cors.New(cfg).ServeHTTP(w, req, next)
})
r.WithCSRFHandler(csrf)
n.UseHandler(r.CSRFHandler())
// Disable CSRF for these endpoints
csrf.DisablePath(healthx.AliveCheckPath)
csrf.DisablePath(healthx.ReadyCheckPath)
csrf.DisablePath(healthx.VersionPath)
csrf.DisablePath(prometheusx.MetricsPrometheusPath)
r.RegisterPublicRoutes(ctx, router)
var handler http.Handler = n
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
handler = otelx.NewMiddleware(handler, "servePublic",
otelhttp.WithTracerProvider(tracer.Provider()),
)
}
handler = http.MaxBytesHandler(handler, 5*1024*1024 /* 5 MB */) // Important: this must be the outermost handler or our tracing breaks
certFunc, err := cfg.TLS.GetCertFunc(ctx, l, "public")
if err != nil {
return nil, err
}
//#nosec G112 -- the correct settings are set by graceful.WithDefaults
server := graceful.WithDefaults(&http.Server{
Handler: handler,
TLSConfig: &tls.Config{GetCertificate: certFunc, MinVersion: tls.VersionTLS12},
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
})
addr := cfg.GetAddress()
return func() error {
l.WithField("addr", addr).Info("Starting the public httpd")
if err := graceful.GracefulContext(ctx, func() error {
listener, err := networkx.MakeListener(addr, &cfg.Socket)
if err != nil {
return err
}
if certFunc == nil {
return server.Serve(listener)
}
return server.ServeTLS(listener, "", "")
}, server.Shutdown); err != nil {
if !errors.Is(err, context.Canceled) {
l.Errorf("Failed to gracefully shutdown public httpd: %s", err)
return err
}
}
l.Println("Public httpd was shutdown gracefully")
return nil
}, nil
}
func serveAdmin(ctx context.Context, r *driver.RegistryDefault, cmd *cobra.Command) (func() error, error) {
cfg := r.Config().ServeAdmin(ctx)
l := r.Logger()
rec := negroni.NewRecovery()
rec.Logger = l
router := httprouterx.NewRouterAdminWithPrefix()
n := negroni.New(
rec,
httpMetrics,
)
for _, mw := range r.HTTPMiddlewares() {
n.Use(mw)
}
adminLogger := reqlog.NewMiddlewareFromLogger(l, "admin#"+cfg.BaseURL.String())
if cfg.RequestLog.DisableHealth {
adminLogger.ExcludePaths(
httprouterx.AdminPrefix+healthx.AliveCheckPath,
httprouterx.AdminPrefix+healthx.ReadyCheckPath,
httprouterx.AdminPrefix+prometheusx.MetricsPrometheusPath,
)
}
n.UseFunc(semconv.Middleware)
n.Use(adminLogger)
n.UseFunc(httprouterx.AddAdminPrefixIfNotPresentNegroni)
n.UseFunc(httprouterx.NoCacheNegroni)
n.Use(x.HTTPLoaderContextMiddleware(r))
n.Use(sqa(ctx, cmd, r))
r.RegisterAdminRoutes(ctx, router)
n.UseHandler(router)
var handler http.Handler = n
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
handler = otelx.NewMiddleware(handler, "serveAdmin",
otelhttp.WithTracerProvider(tracer.Provider()),
)
}
handler = http.MaxBytesHandler(handler, 5*1024*1024 /* 5 MB */) // Important: this must be the outermost handler or our tracing breaks
certFunc, err := cfg.TLS.GetCertFunc(ctx, l, "admin")
if err != nil {
return nil, err
}
//#nosec G112 -- the correct settings are set by graceful.WithDefaults
server := graceful.WithDefaults(&http.Server{
Handler: handler,
TLSConfig: &tls.Config{GetCertificate: certFunc, MinVersion: tls.VersionTLS12},
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 120 * time.Second,
IdleTimeout: 600 * time.Second,
})
addr := cfg.GetAddress()
return func() error {
l.WithField("addr", addr).Info("Starting the admin httpd")
if err := graceful.GracefulContext(ctx, func() error {
listener, err := networkx.MakeListener(addr, &cfg.Socket)
if err != nil {
return err
}
if certFunc == nil {
return server.Serve(listener)
}
return server.ServeTLS(listener, "", "")
}, server.Shutdown); err != nil {
if !errors.Is(err, context.Canceled) {
l.Errorf("Failed to gracefully shutdown admin httpd: %s", err)
return err
}
}
l.Println("Admin httpd was shutdown gracefully")
return nil
}, nil
}
func sqa(ctx context.Context, cmd *cobra.Command, d driver.Registry) *metricsx.Service {
urls := []string{
d.Config().ServePublic(ctx).BaseURL.Host,
d.Config().ServeAdmin(ctx).BaseURL.Host,
d.Config().SelfServiceFlowLoginUI(ctx).Host,
d.Config().SelfServiceFlowSettingsUI(ctx).Host,
d.Config().SelfServiceFlowErrorURL(ctx).Host,
d.Config().SelfServiceFlowRegistrationUI(ctx).Host,
d.Config().SelfServiceFlowRecoveryUI(ctx).Host,
d.Config().ServePublic(ctx).Host,
d.Config().ServeAdmin(ctx).Host,
}
if c, y := d.Config().CORSPublic(ctx); y {
urls = append(urls, c.AllowedOrigins...)
}
host := urlx.ExtractPublicAddress(urls...)
// Creates only ones
// instance
return metricsx.New(
cmd,
d.Logger(),
d.Config().GetProvider(ctx),
&metricsx.Options{
Service: "kratos",
DeploymentId: metricsx.Hash(d.Persister().NetworkID(ctx).String()),
DBDialect: d.Persister().GetConnection(ctx).Dialect.Name(),
IsDevelopment: d.Config().IsInsecureDevMode(ctx),
WriteKey: "qQlI6q8Q4WvkzTjKQSor4sHYOikHIvvi",
WhitelistedPaths: []string{
"/",
healthx.AliveCheckPath,
healthx.ReadyCheckPath,
healthx.VersionPath,
oidc.RouteBase,
login.RouteInitBrowserFlow,
login.RouteInitAPIFlow,
login.RouteGetFlow,
login.RouteSubmitFlow,
logout.RouteInitBrowserFlow,
logout.RouteSubmitFlow,
logout.RouteAPIFlow,
registration.RouteInitBrowserFlow,
registration.RouteInitAPIFlow,
registration.RouteGetFlow,
registration.RouteSubmitFlow,
session.RouteWhoami,
httprouterx.AdminPrefix + "/" + schema.SchemasPath,
httprouterx.AdminPrefix + identity.RouteCollection,
settings.RouteInitBrowserFlow,
settings.RouteInitAPIFlow,
settings.RouteGetFlow,
settings.RouteSubmitFlow,
verification.RouteInitAPIFlow,
verification.RouteInitBrowserFlow,
verification.RouteGetFlow,
verification.RouteSubmitFlow,
recovery.RouteInitAPIFlow,
recovery.RouteInitBrowserFlow,
recovery.RouteGetFlow,
recovery.RouteSubmitFlow,
link.RouteAdminCreateRecoveryLink,
errorx.RouteGet,
prometheusx.MetricsPrometheusPath,
},
BuildVersion: config.Version,
BuildHash: config.Commit,
BuildTime: config.Date,
Config: &analytics.Config{
Endpoint: "https://sqa.ory.sh",
GzipCompressionLevel: 6,
BatchMaxSize: 500 * 1000,
BatchSize: 1000,
Interval: time.Hour * 6,
},
Hostname: host,
},
)
}
func courierTask(ctx context.Context, d driver.Registry) func() error {
return func() error {
if d.Config().IsBackgroundCourierEnabled(ctx) {
return courier.Watch(ctx, d)
}
return nil
}
}
func ServeAll(d *driver.RegistryDefault) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
g, ctx := errgroup.WithContext(ctx)
cmd.SetContext(ctx)
if err := popx.VerifyDialect(ctx, d.Persister().GetConnection(ctx)); err != nil {
return errors.WithStack(err)
}
// construct all tasks upfront to avoid race conditions
publicSrv, err := servePublic(ctx, d, cmd)
if err != nil {
return errors.WithStack(err)
}
adminSrv, err := serveAdmin(ctx, d, cmd)
if err != nil {
return errors.WithStack(err)
}
tasks := []func() error{
publicSrv,
adminSrv,
courierTask(ctx, d),
}
for _, task := range tasks {
g.Go(task)
}
return g.Wait()
}
}