snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (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:
upstream-mirror
2026-09-16 01:08:34 +05:00
commit 9456a4e922
2560 changed files with 120911 additions and 0 deletions

570
internal/driver/daemon.go Normal file
View File

@@ -0,0 +1,570 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package driver
import (
"context"
stderrors "errors"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"runtime/debug"
"strings"
"syscall"
"time"
"connectrpc.com/connect"
"connectrpc.com/grpchealth"
"connectrpc.com/grpcreflect"
"github.com/ory/analytics-go/v5"
"github.com/ory/graceful"
"github.com/ory/herodot"
"github.com/ory/x/healthx"
"github.com/ory/x/httprouterx"
"github.com/ory/x/logrusx"
"github.com/ory/x/metricsx"
"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/urlx"
"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"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/protobuf/reflect/protoregistry"
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
"github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2/relationtuplesconnect"
"github.com/ory/keto/internal/check"
"github.com/ory/keto/internal/driver/config"
"github.com/ory/keto/internal/expand"
"github.com/ory/keto/internal/namespace/namespacehandler"
"github.com/ory/keto/internal/relationtuple"
"github.com/ory/keto/schema"
)
func (r *RegistryDefault) enableSqa(cmd *cobra.Command) {
ctx := cmd.Context()
var urls []string
addr, _ := r.Config(ctx).ReadAPIListenOn()
urls = append(urls, addr)
addr, _ = r.Config(ctx).WriteAPIListenOn()
urls = append(urls, addr)
addr, _ = r.Config(ctx).MetricsListenOn()
urls = append(urls, addr)
addr, _ = r.Config(ctx).OPLSyntaxAPIListenOn()
urls = append(urls, addr)
if c, y := r.Config(ctx).CORS("read"); y {
urls = append(urls, c.AllowedOrigins...)
}
if c, y := r.Config(ctx).CORS("write"); y {
urls = append(urls, c.AllowedOrigins...)
}
if c, y := r.Config(ctx).CORS("metrics"); y {
urls = append(urls, c.AllowedOrigins...)
}
host := urlx.ExtractPublicAddress(urls...)
r.sqaService = metricsx.New(
cmd,
r.Logger(),
r.Config(ctx).Source(),
&metricsx.Options{
Service: "keto",
DeploymentId: metricsx.Hash(r.Persister().NetworkID(ctx).String()),
IsDevelopment: strings.HasPrefix(r.Config(ctx).DSN(), "sqlite"),
WriteKey: "jk32cFATnj9GKbQdFL7fBB9qtKZdX9j7",
WhitelistedPaths: []string{
"/",
healthx.AliveCheckPath,
healthx.ReadyCheckPath,
healthx.VersionPath,
relationtuplesconnect.VersionServiceGetVersionProcedure,
"/" + grpchealth.HealthV1ServiceName + "/Check",
"/" + grpchealth.HealthV1ServiceName + "/Watch",
relationtuple.ReadRouteBase,
relationtuplesconnect.ReadServiceListRelationTuplesProcedure,
check.RouteBase,
relationtuplesconnect.CheckServiceCheckProcedure,
relationtuplesconnect.CheckServiceBatchCheckProcedure,
expand.RouteBase,
relationtuplesconnect.ExpandServiceExpandProcedure,
relationtuplesconnect.WriteServiceTransactRelationTuplesProcedure,
relationtuplesconnect.WriteServiceDeleteRelationTuplesProcedure,
},
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 (r *RegistryDefault) ServeAllSQA(cmd *cobra.Command) error {
r.enableSqa(cmd)
return r.ServeAll(cmd.Context())
}
func (r *RegistryDefault) ServeAll(ctx context.Context) error {
innerCtx, cancel := context.WithCancel(ctx)
defer cancel()
serveFuncs := []func(context.Context, chan<- struct{}) func() error{
r.serveRead,
r.serveWrite,
r.serveOPLSyntax,
r.serveMetrics,
}
doneShutdown := make(chan struct{}, len(serveFuncs))
go func() {
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
select {
case <-osSignals:
cancel()
case <-innerCtx.Done():
}
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), graceful.DefaultShutdownTimeout)
defer cancel()
nWaitingForShutdown := len(serveFuncs)
for {
select {
case <-ctx.Done():
return
case <-doneShutdown:
nWaitingForShutdown--
if nWaitingForShutdown == 0 {
// graceful shutdown done
return
}
}
}
}()
eg := &errgroup.Group{}
// We need to separate the setup (invoking the functions that return the serve functions) from running the serve
// functions to mitigate race conditions in the HTTP router.
for _, serve := range serveFuncs {
eg.Go(serve(innerCtx, doneShutdown))
}
return eg.Wait()
}
func (r *RegistryDefault) serveRead(ctx context.Context, done chan<- struct{}) func() error {
rt := r.ReadRouter(ctx)
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
rt = otelx.NewMiddleware(rt, "serveRead",
otelhttp.WithTracerProvider(tracer.Provider()),
)
}
return func() error {
addr, listenFile := r.Config(ctx).ReadAPIListenOn()
return servePort(ctx, r.Logger().WithField("endpoint", "read"), addr, listenFile, rt, done)
}
}
func (r *RegistryDefault) serveWrite(ctx context.Context, done chan<- struct{}) func() error {
rt := r.WriteRouter(ctx)
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
rt = otelx.NewMiddleware(rt, "serveWrite",
otelhttp.WithTracerProvider(tracer.Provider()),
)
}
return func() error {
addr, listenFile := r.Config(ctx).WriteAPIListenOn()
return servePort(ctx, r.Logger().WithField("endpoint", "write"), addr, listenFile, rt, done)
}
}
func (r *RegistryDefault) serveOPLSyntax(ctx context.Context, done chan<- struct{}) func() error {
rt := r.OPLSyntaxRouter(ctx)
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
rt = otelx.NewMiddleware(rt, "serveOPLSyntax",
otelhttp.WithTracerProvider(tracer.Provider()),
)
}
return func() error {
addr, listenFile := r.Config(ctx).OPLSyntaxAPIListenOn()
return servePort(ctx, r.Logger().WithField("endpoint", "opl"), addr, listenFile, rt, done)
}
}
func (r *RegistryDefault) serveMetrics(ctx context.Context, done chan<- struct{}) func() error {
ctx, cancel := context.WithCancel(ctx)
//nolint:gosec // graceful.WithDefaults already sets a timeout
s := graceful.WithDefaults(&http.Server{
Handler: r.metricsRouter(ctx),
})
return func() error {
defer cancel()
eg := &errgroup.Group{}
addr, listenFile := r.Config(ctx).MetricsListenOn()
l, err := listenAndWriteFile(ctx, addr, listenFile)
if err != nil {
return err
}
eg.Go(func() error {
if err := s.Serve(l); !errors.Is(err, http.ErrServerClosed) {
return errors.WithStack(err)
}
return nil
})
eg.Go(func() (err error) {
defer func() {
l := r.Logger().WithField("endpoint", "metrics")
if err != nil {
l.WithError(err).Error("graceful shutdown failed")
} else {
l.Info("gracefully shutdown server")
}
done <- struct{}{}
}()
<-ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), graceful.DefaultShutdownTimeout)
defer cancel()
return s.Shutdown(ctx)
})
return eg.Wait()
}
}
func servePort(ctx context.Context, log *logrusx.Logger, addr, listenFile string, router http.Handler, done chan<- struct{}) error {
l, err := listenAndWriteFile(ctx, addr, listenFile)
if err != nil {
return err
}
p := new(http.Protocols)
p.SetHTTP1(true)
// For gRPC clients, it's convenient to support HTTP/2 without TLS.
p.SetUnencryptedHTTP2(true)
p.SetHTTP2(true)
//nolint:gosec // graceful.WithDefaults already sets a timeout
restS := graceful.WithDefaults(&http.Server{
Handler: router,
Protocols: p,
})
eg := &errgroup.Group{}
eg.Go(func() error {
if err := restS.Serve(l); !errors.Is(err, http.ErrServerClosed) {
return errors.WithStack(err)
}
return nil
})
eg.Go(func() (err error) {
defer func() {
if err != nil {
log.WithError(err).Error("graceful shutdown failed")
} else {
log.Info("gracefully shutdown server")
}
done <- struct{}{}
}()
<-ctx.Done()
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), graceful.DefaultShutdownTimeout)
defer cancel()
shutdownEg := errgroup.Group{}
shutdownEg.Go(func() error {
// we ignore net.ErrClosed, because a cmux listener's close func is actually the one of the root listener (which is closed in a racy fashion)
if err := restS.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) {
// unexpected error
return errors.WithStack(err)
}
return nil
})
shutdownEg.Go(func() error {
gracefulDone := make(chan struct{})
go func() {
close(gracefulDone)
}()
select {
case <-gracefulDone:
return nil
case <-ctx.Done():
return errors.New("graceful stop of gRPC server timed out, had to force it")
}
})
return shutdownEg.Wait()
})
return eg.Wait()
}
func (r *RegistryDefault) allHandlers() []Handler {
return []Handler{
relationtuple.NewReadHandler(r),
relationtuple.NewWriteHandler(r),
check.NewHandler(r),
expand.NewHandler(r),
namespacehandler.New(r),
schema.NewHandler(r),
}
}
func listenAndWriteFile(ctx context.Context, addr, listenFile string) (net.Listener, error) {
l, err := (&net.ListenConfig{}).Listen(ctx, "tcp", addr)
if err != nil {
return nil, errors.WithStack(fmt.Errorf("unable to listen on %q: %w", addr, err))
}
const filePrefix = "file://"
if strings.HasPrefix(listenFile, filePrefix) {
if err := os.WriteFile(listenFile[len(filePrefix):], []byte(l.Addr().String()), 0o600); err != nil {
return nil, errors.WithStack(fmt.Errorf("unable to write listen file %q: %w", listenFile, err))
}
}
return l, nil
}
var httpMetrics = prometheusx.NewHTTPMetrics("keto", prometheusx.HTTPPrefix, config.Version, config.Commit, config.Date)
func (r *RegistryDefault) ReadRouter(ctx context.Context) http.Handler {
recovery := negroni.NewRecovery()
recovery.Logger = r.Logger()
router := httprouterx.NewRouterPublic()
n := negroni.New(
recovery,
httpMetrics,
)
for _, f := range r.defaultHttpMiddlewares {
n.UseFunc(f)
}
n.UseFunc(semconv.Middleware)
n.Use(reqlog.NewMiddlewareFromLogger(r.l, "read#Ory Keto").ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath))
prometheusx.SetMuxRoutes(router)
r.HealthHandler().SetHealthRoutes(router, false)
r.HealthHandler().SetVersionRoutes(router)
for _, h := range registerReflectionAndHealth[ReadHandler](r, router) {
h.RegisterReadRoutes(router)
}
if r.sqaService != nil {
n.Use(r.sqaService)
}
n.UseHandler(router)
var handler http.Handler = n
options, enabled := r.Config(ctx).CORS("read")
if enabled {
handler = cors.New(options).Handler(handler)
}
return handler
}
func (r *RegistryDefault) WriteRouter(ctx context.Context) http.Handler {
recovery := negroni.NewRecovery()
recovery.Logger = r.Logger()
router := httprouterx.NewRouterAdmin()
n := negroni.New(
recovery,
httpMetrics,
)
for _, f := range r.defaultHttpMiddlewares {
n.UseFunc(f)
}
n.UseFunc(semconv.Middleware)
n.Use(reqlog.NewMiddlewareFromLogger(r.l, "write#Ory Keto").ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath))
prometheusx.SetMuxRoutes(router)
r.HealthHandler().SetHealthRoutes(router, false)
r.HealthHandler().SetVersionRoutes(router)
for _, h := range registerReflectionAndHealth[WriteHandler](r, router) {
h.RegisterWriteRoutes(router)
}
if r.sqaService != nil {
n.Use(r.sqaService)
}
n.UseHandler(router)
var handler http.Handler = n
options, enabled := r.Config(ctx).CORS("write")
if enabled {
handler = cors.New(options).Handler(handler)
}
return handler
}
func (r *RegistryDefault) OPLSyntaxRouter(ctx context.Context) http.Handler {
recovery := negroni.NewRecovery()
recovery.Logger = r.Logger()
router := httprouterx.NewRouter()
n := negroni.New(
recovery,
httpMetrics,
)
for _, f := range r.defaultHttpMiddlewares {
n.UseFunc(f)
}
n.UseFunc(semconv.Middleware)
n.Use(reqlog.NewMiddlewareFromLogger(r.l, "syntax#Ory Keto").ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath))
prometheusx.SetMuxRoutes(router)
r.HealthHandler().SetHealthRoutes(router, false)
r.HealthHandler().SetVersionRoutes(router)
for _, h := range registerReflectionAndHealth[OPLSyntaxHandler](r, router) {
h.RegisterSyntaxRoutes(router)
}
if r.sqaService != nil {
n.Use(r.sqaService)
}
n.UseHandler(router)
var handler http.Handler = n
options, enabled := r.Config(ctx).CORS("write")
if enabled {
handler = cors.New(options).Handler(handler)
}
return handler
}
func (r *RegistryDefault) recoveryHandler(_ context.Context, spec connect.Spec, _ http.Header, val any) error {
r.Logger().
WithField("reason", val).
WithField("procedure", spec.Procedure).
WithField("stack_trace", string(debug.Stack())).
Error("panic recovered")
return connect.NewError(connect.CodeInternal, fmt.Errorf("%v", val))
}
func (r *RegistryDefault) HandlerOptions() []connect.HandlerOption {
return append([]connect.HandlerOption{
connect.WithRecover(r.recoveryHandler),
connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
resp, err := next(ctx, req)
if hErr, ok := stderrors.AsType[*herodot.DefaultError](err); ok {
cErr := connect.NewError(connect.Code(hErr.GRPCCodeField), hErr)
if reason := hErr.Reason(); reason != "" {
d, err := connect.NewErrorDetail(&errdetails.ErrorInfo{Reason: reason})
if err == nil {
cErr.AddDetail(d)
}
}
return resp, cErr
}
return resp, err
}
})),
connect.WithInterceptors(r.defaultConnectInterceptors...),
},
r.defaultHandlerOptions...)
}
func (r *RegistryDefault) metricsRouter(ctx context.Context) http.Handler {
recovery := negroni.NewRecovery()
recovery.Logger = r.Logger()
n := negroni.New(
recovery,
reqlog.NewMiddlewareFromLogger(r.Logger(), "keto").ExcludePaths(prometheusx.MetricsPrometheusPath),
)
router := http.NewServeMux()
prometheusx.SetMuxRoutes(router)
n.UseHandler(router)
var handler http.Handler = n
options, enabled := r.Config(ctx).CORS("metrics")
if enabled {
handler = cors.New(options).Handler(handler)
}
return handler
}
func registerReflectionAndHealth[H Handler](r *RegistryDefault, router httprouterx.Router) (handlers []H) {
protoFiles := &protoregistry.Files{}
var protoServices []string
for _, h := range r.allHandlers() {
if h, ok := h.(H); ok {
handlers = append(handlers, h)
for _, f := range h.ProtoFiles() {
if err := protoFiles.RegisterFile(f); err != nil && !strings.Contains(err.Error(), "is already registered") {
r.Logger().WithError(err).
WithField("file_path", f.Path()).
Error("unable to register proto file for reflection")
}
s := f.Services()
for i := range s.Len() {
protoServices = append(protoServices, string(s.Get(i).FullName()))
}
}
}
}
router.Handle(relationtuplesconnect.NewVersionServiceHandler(r, r.HandlerOptions()...))
protoServices = append(protoServices, relationtuplesconnect.VersionServiceName)
if err := protoFiles.RegisterFile(rts.File_ory_keto_relation_tuples_v1alpha2_version_proto); err != nil {
r.Logger().WithError(err).Error("unable to register version proto file for reflection")
}
reflector := grpcreflect.NewReflector(
grpcreflect.NamerFunc(func() []string { return protoServices }),
grpcreflect.WithDescriptorResolver(protoFiles),
)
router.Handle(grpcreflect.NewHandlerV1(reflector, r.HandlerOptions()...))
router.Handle(grpcreflect.NewHandlerV1Alpha(reflector, r.HandlerOptions()...))
router.Handle(grpchealth.NewHandler(grpchealth.NewStaticChecker(protoServices...), r.HandlerOptions()...))
return
}