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
372 lines
10 KiB
Go
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()
|
|
}
|
|
}
|