mirror of https://github.com/ory/hydra
355 lines
9.5 KiB
Go
355 lines
9.5 KiB
Go
// Copyright © 2022 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"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/x/configx"
|
|
"github.com/ory/x/contextx"
|
|
"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/prometheusx"
|
|
"github.com/ory/x/reqlog"
|
|
"github.com/ory/x/tlsx"
|
|
"github.com/ory/x/urlx"
|
|
|
|
"github.com/ory/hydra/v2/client"
|
|
"github.com/ory/hydra/v2/consent"
|
|
"github.com/ory/hydra/v2/driver"
|
|
"github.com/ory/hydra/v2/driver/config"
|
|
"github.com/ory/hydra/v2/jwk"
|
|
"github.com/ory/hydra/v2/oauth2"
|
|
"github.com/ory/hydra/v2/x"
|
|
)
|
|
|
|
func ensureNoMemoryDSN(r *driver.RegistrySQL) {
|
|
if r.Config().DSN() == "memory" {
|
|
r.Logger().Fatalf(`When using "hydra serve admin" or "hydra serve public" the DSN can not be set to "memory".`)
|
|
}
|
|
}
|
|
|
|
func RunServeAdmin(dOpts []driver.OptionsModifier) func(cmd *cobra.Command, args []string) error {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
fmt.Println(banner(config.Version))
|
|
|
|
ctx := cmd.Context()
|
|
|
|
d, err := driver.New(ctx, append(dOpts, driver.WithConfigOptions(configx.WithFlags(cmd.Flags())))...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ensureNoMemoryDSN(d)
|
|
|
|
srv, err := adminServer(ctx, d, sqa(ctx, d, cmd))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return srv()
|
|
}
|
|
}
|
|
|
|
func RunServePublic(dOpts []driver.OptionsModifier) func(cmd *cobra.Command, args []string) error {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
fmt.Println(banner(config.Version))
|
|
|
|
ctx := cmd.Context()
|
|
|
|
d, err := driver.New(ctx, append(dOpts, driver.WithConfigOptions(configx.WithFlags(cmd.Flags())))...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ensureNoMemoryDSN(d)
|
|
|
|
srv, err := publicServer(ctx, d, sqa(ctx, d, cmd))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return srv()
|
|
}
|
|
}
|
|
|
|
func RunServeAll(dOpts []driver.OptionsModifier) func(cmd *cobra.Command, args []string) error {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
fmt.Println(banner(config.Version))
|
|
|
|
ctx := cmd.Context()
|
|
|
|
d, err := driver.New(ctx, append(dOpts, driver.WithConfigOptions(configx.WithFlags(cmd.Flags())))...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
ms := sqa(ctx, d, cmd)
|
|
|
|
srvAdmin, err := adminServer(ctx, d, ms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srvPublic, err := publicServer(ctx, d, ms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
eg.Go(srvAdmin)
|
|
eg.Go(srvPublic)
|
|
return eg.Wait()
|
|
}
|
|
}
|
|
|
|
var prometheusManager = prometheusx.NewMetricsManagerWithPrefix("hydra", prometheusx.HTTPMetrics, config.Version, config.Commit, config.Date)
|
|
|
|
func adminServer(ctx context.Context, d *driver.RegistrySQL, sqaMetrics *metricsx.Service) (func() error, error) {
|
|
cfg := d.Config().ServeAdmin(contextx.RootContext)
|
|
|
|
n := negroni.New()
|
|
|
|
logger := reqlog.
|
|
NewMiddlewareFromLogger(d.Logger(),
|
|
fmt.Sprintf("hydra/admin: %s", d.Config().IssuerURL(ctx).String()))
|
|
if cfg.RequestLog.DisableHealth {
|
|
logger.ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath, "/admin"+prometheusx.MetricsPrometheusPath)
|
|
}
|
|
|
|
n.UseFunc(httprouterx.TrimTrailingSlashNegroni)
|
|
n.UseFunc(httprouterx.NoCacheNegroni)
|
|
n.UseFunc(httprouterx.AddAdminPrefixIfNotPresentNegroni)
|
|
n.UseFunc(semconv.Middleware)
|
|
n.Use(logger)
|
|
|
|
if cfg.TLS.Enabled && !networkx.AddressIsUnixSocket(cfg.Host) {
|
|
mw, err := tlsx.EnforceTLSRequests(d, cfg.TLS.AllowTerminationFrom)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n.Use(mw)
|
|
}
|
|
|
|
for _, mw := range d.HTTPMiddlewares() {
|
|
n.Use(mw)
|
|
}
|
|
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
cfg, enabled := d.Config().CORSAdmin(r.Context())
|
|
if !enabled {
|
|
next(w, r)
|
|
return
|
|
}
|
|
cors.New(cfg).ServeHTTP(w, r, next)
|
|
})
|
|
n.Use(sqaMetrics)
|
|
|
|
router := httprouterx.NewRouterAdminWithPrefix(prometheusManager)
|
|
d.RegisterAdminRoutes(router)
|
|
|
|
n.UseHandler(router)
|
|
|
|
n.UseFunc(otelx.SpanNameRecorderNegroniFunc)
|
|
return func() error {
|
|
return serve(ctx, d, cfg, n, "admin")
|
|
}, nil
|
|
}
|
|
|
|
func publicServer(ctx context.Context, d *driver.RegistrySQL, sqaMetrics *metricsx.Service) (func() error, error) {
|
|
cfg := d.Config().ServePublic(contextx.RootContext)
|
|
|
|
n := negroni.New()
|
|
|
|
logger := reqlog.NewMiddlewareFromLogger(
|
|
d.Logger(),
|
|
fmt.Sprintf("hydra/public: %s", d.Config().IssuerURL(ctx).String()),
|
|
)
|
|
if cfg.RequestLog.DisableHealth {
|
|
logger.ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath)
|
|
}
|
|
|
|
n.UseFunc(httprouterx.TrimTrailingSlashNegroni)
|
|
n.UseFunc(httprouterx.NoCacheNegroni)
|
|
n.UseFunc(semconv.Middleware)
|
|
n.Use(logger)
|
|
if cfg.TLS.Enabled && !networkx.AddressIsUnixSocket(cfg.Host) {
|
|
mw, err := tlsx.EnforceTLSRequests(d, cfg.TLS.AllowTerminationFrom)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n.Use(mw)
|
|
}
|
|
|
|
for _, mw := range d.HTTPMiddlewares() {
|
|
n.Use(mw)
|
|
}
|
|
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
cfg, enabled := d.Config().CORSPublic(r.Context())
|
|
if !enabled {
|
|
next(w, r)
|
|
return
|
|
}
|
|
cors.New(cfg).ServeHTTP(w, r, next)
|
|
})
|
|
n.Use(sqaMetrics)
|
|
|
|
router := x.NewRouterPublic(prometheusManager)
|
|
d.RegisterPublicRoutes(ctx, router)
|
|
|
|
n.UseHandler(router)
|
|
n.UseFunc(otelx.SpanNameRecorderNegroniFunc)
|
|
return func() error {
|
|
return serve(ctx, d, cfg, n, "public")
|
|
}, nil
|
|
}
|
|
|
|
func sqa(ctx context.Context, d *driver.RegistrySQL, cmd *cobra.Command) *metricsx.Service {
|
|
urls := []string{
|
|
d.Config().IssuerURL(ctx).Host,
|
|
d.Config().PublicURL(ctx).Host,
|
|
d.Config().AdminURL(ctx).Host,
|
|
d.Config().ServePublic(ctx).BaseURL.Host,
|
|
d.Config().ServeAdmin(ctx).BaseURL.Host,
|
|
d.Config().LoginURL(ctx).Host,
|
|
d.Config().LogoutURL(ctx).Host,
|
|
d.Config().ConsentURL(ctx).Host,
|
|
d.Config().RegistrationURL(ctx).Host,
|
|
}
|
|
if c, y := d.Config().CORSPublic(ctx); y {
|
|
urls = append(urls, c.AllowedOrigins...)
|
|
}
|
|
if c, y := d.Config().CORSAdmin(ctx); y {
|
|
urls = append(urls, c.AllowedOrigins...)
|
|
}
|
|
host := urlx.ExtractPublicAddress(urls...)
|
|
|
|
return metricsx.New(
|
|
cmd,
|
|
d.Logger(),
|
|
d.Config().Source(ctx),
|
|
&metricsx.Options{
|
|
Service: "hydra",
|
|
DeploymentId: metricsx.Hash(d.Persister().NetworkID(ctx).String()),
|
|
IsDevelopment: d.Config().DSN() == "memory" ||
|
|
d.Config().IssuerURL(ctx).String() == "" ||
|
|
strings.Contains(d.Config().IssuerURL(ctx).String(), "localhost"),
|
|
WriteKey: "h8dRH3kVCWKkIFWydBmWsyYHR4M0u0vr",
|
|
WhitelistedPaths: []string{
|
|
"/admin" + jwk.KeyHandlerPath,
|
|
jwk.WellKnownKeysPath,
|
|
|
|
urlx.MustJoin("/admin", client.ClientsHandlerPath),
|
|
client.DynClientsHandlerPath,
|
|
|
|
oauth2.DefaultConsentPath,
|
|
oauth2.DefaultLoginPath,
|
|
oauth2.DefaultPostLogoutPath,
|
|
oauth2.DefaultLogoutPath,
|
|
oauth2.DefaultErrorPath,
|
|
oauth2.TokenPath,
|
|
oauth2.AuthPath,
|
|
oauth2.LogoutPath,
|
|
oauth2.UserinfoPath,
|
|
oauth2.WellKnownPath,
|
|
oauth2.JWKPath,
|
|
"/admin" + oauth2.IntrospectPath,
|
|
"/admin" + oauth2.DeleteTokensPath,
|
|
oauth2.RevocationPath,
|
|
|
|
"/admin" + consent.ConsentPath,
|
|
"/admin" + consent.ConsentPath + "/accept",
|
|
"/admin" + consent.ConsentPath + "/reject",
|
|
"/admin" + consent.LoginPath,
|
|
"/admin" + consent.LoginPath + "/accept",
|
|
"/admin" + consent.LoginPath + "/reject",
|
|
"/admin" + consent.LogoutPath,
|
|
"/admin" + consent.LogoutPath + "/accept",
|
|
"/admin" + consent.LogoutPath + "/reject",
|
|
"/admin" + consent.SessionsPath + "/login",
|
|
"/admin" + consent.SessionsPath + "/consent",
|
|
|
|
healthx.AliveCheckPath,
|
|
healthx.ReadyCheckPath,
|
|
"/admin" + healthx.AliveCheckPath,
|
|
"/admin" + healthx.ReadyCheckPath,
|
|
healthx.VersionPath,
|
|
"/admin" + healthx.VersionPath,
|
|
prometheusx.MetricsPrometheusPath,
|
|
"/admin" + prometheusx.MetricsPrometheusPath,
|
|
"/",
|
|
},
|
|
BuildVersion: config.Version,
|
|
BuildTime: config.Date,
|
|
BuildHash: config.Commit,
|
|
Config: &analytics.Config{
|
|
Endpoint: "https://sqa.ory.sh",
|
|
GzipCompressionLevel: 6,
|
|
BatchMaxSize: 500 * 1000,
|
|
BatchSize: 1000,
|
|
Interval: time.Hour * 6,
|
|
},
|
|
Hostname: host,
|
|
},
|
|
)
|
|
}
|
|
|
|
func serve(
|
|
ctx context.Context,
|
|
d *driver.RegistrySQL,
|
|
cfg *configx.Serve,
|
|
handler http.Handler,
|
|
ifaceName string,
|
|
) error {
|
|
if tracer := d.Tracer(ctx); tracer.IsLoaded() {
|
|
handler = otelx.NewMiddleware(handler, ifaceName,
|
|
otelhttp.WithTracerProvider(tracer.Provider()),
|
|
)
|
|
}
|
|
|
|
var tlsConfig *tls.Config
|
|
if cfg.TLS.Enabled {
|
|
// #nosec G402 - This is a false positive because we use graceful.WithDefaults which sets the correct TLS settings.
|
|
tlsConfig = &tls.Config{GetCertificate: GetOrCreateTLSCertificate(ctx, d, cfg.TLS, ifaceName)}
|
|
}
|
|
|
|
srv := graceful.WithDefaults(&http.Server{
|
|
Handler: handler,
|
|
TLSConfig: tlsConfig,
|
|
ReadHeaderTimeout: time.Second * 5,
|
|
})
|
|
|
|
addr := configx.GetAddress(cfg.Host, cfg.Port)
|
|
return graceful.Graceful(func() error {
|
|
d.Logger().Infof("Setting up http server on %s", addr)
|
|
listener, err := networkx.MakeListener(addr, &cfg.Socket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if networkx.AddressIsUnixSocket(addr) {
|
|
return srv.Serve(listener)
|
|
}
|
|
|
|
if tlsConfig != nil {
|
|
return srv.ServeTLS(listener, "", "")
|
|
}
|
|
|
|
d.Logger().Warnln("HTTPS is disabled. Please ensure that your proxy is configured to provide HTTPS, and that it redirects HTTP to HTTPS.")
|
|
|
|
return srv.Serve(listener)
|
|
}, srv.Shutdown)
|
|
}
|