From 139bc80529118446aa8d3fc743cf5f05130a5388 Mon Sep 17 00:00:00 2001 From: Admin Bot Date: Thu, 27 Aug 2026 11:16:15 -0700 Subject: [PATCH] refactor: dispatcher as dumb pipe + add gRPC detection for Temporal BREAKING CHANGE: Gateway no longer validates JWTs at dispatcher level. Each upstream service (MinIO, Authentik, Temporal) validates bearer tokens independently. Gateway passes Authorization header through unchanged. Changes: - Removed JWT validation from Dispatcher - Removed internal/auth JWT validator usage - Added gRPC URL scheme detection (grpc://) - Added temporal-frontend with gRPC config (returns 501 not-implemented) - All adapters now auth: required: false (services validate own tokens) - Gateway is now a transparent routing layer, not auth gateway gRPC forwarding requires grpcproxy middleware (future Phase 9). For now, gRPC clients should connect directly to temporal-frontend:7233. --- go.mod | 16 ++-- go.sum | 43 ++++------ internal/serviceadapter/router.go | 136 +++++++++++++++++------------- k8s/configmap.yaml | 31 +++++-- 4 files changed, 122 insertions(+), 104 deletions(-) diff --git a/go.mod b/go.mod index 85e25c9..cd3dd60 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,11 @@ module forgejo.riotpiao.com/rock/homelab-frontend go 1.25.4 require ( + github.com/golang-jwt/jwt/v5 v5.3.1 + go.temporal.io/api v1.63.5 go.temporal.io/sdk v1.48.0 + google.golang.org/grpc v1.83.2 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) @@ -11,7 +15,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect @@ -22,14 +25,11 @@ require ( github.com/robfig/cron v1.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect - go.temporal.io/api v1.63.5 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.83.1 // indirect - google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index 77506c1..7414459 100644 --- a/go.sum +++ b/go.sum @@ -49,23 +49,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.temporal.io/api v1.63.4 h1:p4dVIAP3dJop0MfcyH9QSzjU7+V/ttLDhxFhSRUar58= -go.temporal.io/api v1.63.4/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.temporal.io/api v1.63.5 h1:c11+kPYHkXXL3UiShPdbMD+xtvqGsbTibUA9ypmiCa4= go.temporal.io/api v1.63.5/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= go.temporal.io/sdk v1.48.0 h1:WDctKDVuh0Z8Nf7euAyqs/EwcPg1JTIIq1Fut8Tq118= @@ -81,27 +74,27 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -115,18 +108,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= -google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= -google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/serviceadapter/router.go b/internal/serviceadapter/router.go index 70f3af2..9eaeae3 100644 --- a/internal/serviceadapter/router.go +++ b/internal/serviceadapter/router.go @@ -1,36 +1,33 @@ package serviceadapter import ( + "context" "fmt" "net" "net/http" "net/http/httputil" "net/url" + "strings" "time" - "forgejo.riotpiao.com/rock/homelab-frontend/internal/auth" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "forgejo.riotpiao.com/rock/homelab-frontend/internal/problem" ) -// Dispatcher handles X-Service based routing to service adapters. +// Dispatcher routes X-Service requests to upstreams. +// Acts as a dumb pipe: passes Authorization header through unchanged. +// Each upstream service validates JWTs independently. type Dispatcher struct { - registry *Registry - validators map[string]*auth.Validator // Per-service JWT validators + registry *Registry } -// NewDispatcher creates a new service adapter dispatcher with Authentik JWT validators. +// NewDispatcher creates a new service adapter dispatcher. func NewDispatcher(registry *Registry) *Dispatcher { - dispatcher := &Dispatcher{ - registry: registry, - validators: make(map[string]*auth.Validator), + return &Dispatcher{ + registry: registry, } - - // Create validators for all registered services - for _, adapter := range registry.List() { - dispatcher.validators[adapter.ServiceName] = auth.NewValidator(adapter.ServiceName) - } - - return dispatcher } // Matches returns true if the request should be dispatched based on X-Service header. @@ -43,7 +40,6 @@ func (d *Dispatcher) Matches(r *http.Request) bool { func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) { serviceName := r.Header.Get("X-Service") if serviceName == "" { - // No X-Service header — this shouldn't happen if Matches() was called d.writeError(w, problem.BadRequest("X-Service header required")) return } @@ -93,61 +89,35 @@ func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) { return } - // Check auth requirements using JWT validation - requiredCapability := "" - auth := resource.Auth - if auth == nil { - auth = &adapter.Spec.Auth - } - if method.Auth != nil { - auth = method.Auth + // Detect protocol from upstream URL scheme + upstreamURL := adapter.Spec.Upstream.URL + if strings.HasPrefix(upstreamURL, "grpc://") { + // gRPC upstream (Temporal, etc.) + d.dispatchGRPC(w, r, upstreamURL, method, adapter) + } else { + // HTTP upstream (MinIO, Authentik, etc.) + d.dispatchHTTP(w, r, upstreamURL, method, adapter) } +} - if auth != nil && auth.Required && auth.Capability != "" { - requiredCapability = auth.Capability - - // Validate JWT token - validator := d.validators[serviceName] - if validator == nil { - p := problem.NewProblem(http.StatusInternalServerError, "about:blank#server-error", - "Internal Server Error", fmt.Sprintf("no validator for service '%s'", serviceName)) - _ = p.Write(w) - return - } - - claims, err := validator.ValidateToken(r.Header.Get("Authorization")) - if err != nil { - p := problem.NewProblem(http.StatusForbidden, "about:blank#forbidden", - "Forbidden", fmt.Sprintf("authentication failed: %v", err)) - _ = p.Write(w) - return - } - - // Check permission - if !validator.HasPermission(claims, requiredCapability) { - p := problem.NewProblem(http.StatusForbidden, "about:blank#forbidden", - "Forbidden", fmt.Sprintf("permission '%s' required", requiredCapability)) - _ = p.Write(w) - return - } - } - - // Build upstream URL - upstreamURL, err := url.Parse(adapter.Spec.Upstream.URL) +// dispatchHTTP forwards HTTP requests to upstream, passing Authorization header through. +func (d *Dispatcher) dispatchHTTP(w http.ResponseWriter, r *http.Request, upstreamURL string, method *Method, adapter *ServiceAdapter) { + parsedURL, err := url.Parse(upstreamURL) if err != nil { d.writeError(w, problem.NewProblem(http.StatusInternalServerError, "about:blank#server-error", "Internal Server Error", fmt.Sprintf("invalid upstream URL: %v", err))) return } - // Create reverse proxy with director that rewrites the path - proxy := httputil.NewSingleHostReverseProxy(upstreamURL) + // Create reverse proxy + proxy := httputil.NewSingleHostReverseProxy(parsedURL) proxy.Director = func(req *http.Request) { - req.URL.Scheme = upstreamURL.Scheme - req.URL.Host = upstreamURL.Host + req.URL.Scheme = parsedURL.Scheme + req.URL.Host = parsedURL.Host req.URL.Path = method.UpstreamPath req.RequestURI = "" - req.Host = upstreamURL.Host + req.Host = parsedURL.Host + // Authorization header passes through unchanged } // Set timeout @@ -164,7 +134,53 @@ func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) { proxy.ServeHTTP(w, r) } +// dispatchGRPC forwards gRPC requests to upstream. +// gRPC URL format: grpc://host:port +func (d *Dispatcher) dispatchGRPC(w http.ResponseWriter, r *http.Request, upstreamURL string, method *Method, adapter *ServiceAdapter) { + // Extract host:port from grpc://host:port + host := strings.TrimPrefix(upstreamURL, "grpc://") + if host == upstreamURL { + d.writeError(w, problem.NewProblem(http.StatusInternalServerError, "about:blank#server-error", + "Internal Server Error", "invalid gRPC URL format")) + return + } + // Validate that this is a gRPC request + if !strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { + d.writeError(w, problem.NewProblem(http.StatusBadRequest, "about:blank#bad-request", + "Bad Request", "gRPC service requires application/grpc content-type")) + return + } + + // Set timeout + timeout := adapter.Spec.Upstream.TimeoutSeconds + if timeout <= 0 { + timeout = 30 + } + + ctx, cancel := context.WithTimeout(r.Context(), time.Duration(timeout)*time.Second) + defer cancel() + + // Dial gRPC upstream + conn, err := grpc.DialContext(ctx, host, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(100 * 1024 * 1024), // 100MB + ), + ) + if err != nil { + d.writeError(w, problem.NewProblem(http.StatusBadGateway, "about:blank#bad-gateway", + "Bad Gateway", fmt.Sprintf("failed to dial gRPC upstream: %v", err))) + return + } + defer conn.Close() + + // Forward gRPC request + // Note: Full gRPC forwarding requires grpcproxy or custom middleware. + // For now, return unimplemented (Temporal support coming in Phase 9) + d.writeError(w, problem.NewProblem(http.StatusNotImplemented, "about:blank#not-implemented", + "Not Implemented", "gRPC forwarding not yet implemented - use in-cluster gRPC clients directly")) +} func (d *Dispatcher) writeError(w http.ResponseWriter, p *problem.Problem) { _ = p.Write(w) diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml index 2925a32..de0d07d 100644 --- a/k8s/configmap.yaml +++ b/k8s/configmap.yaml @@ -45,6 +45,7 @@ data: timeoutSeconds: 30 auth: required: false + # Note: Services validate their own JWTs. Gateway is dumb pipe. resources: - name: send-message methods: @@ -59,12 +60,25 @@ data: - verb: GET upstreamPath: /sqs/queues - # NOTE: temporal-frontend (port 7233) is gRPC-only, not HTTP. - # External worker/SDK access is unresolved per homelab/project-usage/jwt-auth-rollout.md - # Disabled until a gRPC-capable ingress is added. - # - serviceName: workflow - # upstream: - # url: http://temporal-frontend.temporal.svc.cluster.local:7233 + - serviceName: workflow + upstream: + url: grpc://temporal-frontend.temporal.svc.cluster.local:7233 + timeoutSeconds: 60 + auth: + required: false + resources: + - name: execute + methods: + - verb: POST + upstreamPath: /temporal.api.workflowservice.v1.WorkflowService/ExecuteWorkflow + - name: describe + methods: + - verb: GET + upstreamPath: /temporal.api.workflowservice.v1.WorkflowService/DescribeWorkflowExecution + - name: list + methods: + - verb: GET + upstreamPath: /temporal.api.workflowservice.v1.WorkflowService/ListWorkflowExecutions - serviceName: memory upstream: @@ -92,6 +106,7 @@ data: timeoutSeconds: 30 auth: required: false + # MinIO validates bearer tokens independently resources: - name: list-objects methods: @@ -111,8 +126,8 @@ data: url: http://authentik-server.iam.svc.cluster.local:80 timeoutSeconds: 30 auth: - required: true - capability: "iam:admin" + required: false + # Authentik validates bearer tokens independently resources: - name: list-roles methods: