snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
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
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
This commit is contained in:
371
cmd/daemon/serve.go
Normal file
371
cmd/daemon/serve.go
Normal file
@@ -0,0 +1,371 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
76
cmd/daemon/serve_test.go
Normal file
76
cmd/daemon/serve_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/phayes/freeport"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/ory/kratos/pkg"
|
||||
)
|
||||
|
||||
func TestMetricsRouterPaths(t *testing.T) {
|
||||
// TODO refactor this test to be parallelizable once we rewrite the server setup to be properly testable
|
||||
ports, err := freeport.GetFreePorts(2)
|
||||
require.NoError(t, err)
|
||||
adminPort, publicPort := ports[0], ports[1]
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("sqa-opt-out", true, "")
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]any{
|
||||
"serve.admin.port": adminPort,
|
||||
"serve.public.port": publicPort,
|
||||
}))
|
||||
|
||||
startAdmin, err := serveAdmin(t.Context(), reg, cmd)
|
||||
require.NoError(t, err)
|
||||
startPublic, err := servePublic(t.Context(), reg, cmd)
|
||||
require.NoError(t, err)
|
||||
eg, _ := errgroup.WithContext(t.Context())
|
||||
eg.Go(startAdmin)
|
||||
eg.Go(startPublic)
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
t.Cleanup(transport.CloseIdleConnections)
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/health/ready", publicPort))
|
||||
require.NoError(t, err)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, http.StatusOK, resp.StatusCode, "%s", body)
|
||||
}, 2*time.Second, 10*time.Millisecond)
|
||||
|
||||
// Make some requests that should be recorded in the metrics
|
||||
req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://127.0.0.1:%d/sessions/session-id", publicPort), nil)
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||
resp, err = client.Get(fmt.Sprintf("http://127.0.0.1:%d/admin/identities/8e5f29ec-db29-4e56-8517-75f7d4e2f1bc/sessions", adminPort))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
res, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/admin/metrics/prometheus", adminPort))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, http.StatusOK, res.StatusCode)
|
||||
respBody, err := io.ReadAll(res.Body)
|
||||
body := string(respBody)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, body, `endpoint="/sessions/{param}"`)
|
||||
assert.Contains(t, body, `endpoint="/admin/identities/{param}/sessions"`)
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
Reference in New Issue
Block a user