chore: remove gateway extractors

This commit is contained in:
Stavros
2026-09-21 14:19:05 +03:00
parent 90e898abbf
commit 996316b524
6 changed files with 2 additions and 356 deletions
-16
View File
@@ -32,7 +32,6 @@ require (
k8s.io/apimachinery v0.37.0
k8s.io/client-go v0.37.0
modernc.org/sqlite v1.58.0
sigs.k8s.io/gateway-api v1.6.2
)
require (
@@ -71,7 +70,6 @@ require (
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
@@ -79,20 +77,6 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect
github.com/go-openapi/jsonreference v1.0.0 // indirect
github.com/go-openapi/swag v0.27.1 // indirect
github.com/go-openapi/swag/cmdutils v0.27.1 // indirect
github.com/go-openapi/swag/conv v0.27.1 // indirect
github.com/go-openapi/swag/fileutils v0.27.1 // indirect
github.com/go-openapi/swag/jsonutils v0.27.1 // indirect
github.com/go-openapi/swag/loading v0.27.1 // indirect
github.com/go-openapi/swag/mangling v0.27.1 // indirect
github.com/go-openapi/swag/netutils v0.27.1 // indirect
github.com/go-openapi/swag/pools v0.27.1 // indirect
github.com/go-openapi/swag/stringutils v0.27.1 // indirect
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
-8
View File
@@ -138,8 +138,6 @@ github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzr
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
@@ -154,10 +152,6 @@ github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71U
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -446,8 +440,6 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
sigs.k8s.io/gateway-api v1.6.2 h1:vh5YzKlbdBivEaLX61+APKLGRq4tZ7Fj4XfGkv08xB4=
sigs.k8s.io/gateway-api v1.6.2/go.mod h1:FVfx3t389ybeXOqvDghLbdvJdSCfI/PReqCUI3lu3mY=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
@@ -1,47 +0,0 @@
package service
import (
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
gateway "sigs.k8s.io/gateway-api/apis/v1"
)
type KubernetesGRPCRouteExtractor struct {
log *logger.Logger
}
type KubernetesGRPCRouteExtractorInput struct {
Log *logger.Logger
}
func NewKubernetesGRPCRouteExtractor(i KubernetesGRPCRouteExtractorInput) *KubernetesGRPCRouteExtractor {
return &KubernetesGRPCRouteExtractor{
log: i.Log,
}
}
func (k *KubernetesGRPCRouteExtractor) getHosts(hostnames []gateway.Hostname) []string {
var hosts []string
for _, hostname := range hostnames {
if hostname != "" {
hosts = append(hosts, string(hostname))
}
}
return hosts
}
func (k *KubernetesGRPCRouteExtractor) Extract(route *gateway.GRPCRoute) *ExtractionResult {
hosts := k.getHosts(route.Spec.Hostnames)
namespace := route.GetNamespace()
name := route.GetName()
annotations := route.GetAnnotations()
return &ExtractionResult{
typ: ResourceTypeGRPCRoute,
name: name,
namespace: namespace,
hosts: hosts,
annotations: annotations,
}
}
@@ -1,98 +0,0 @@
package service
import (
"slices"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
gateway "sigs.k8s.io/gateway-api/apis/v1"
)
type KubernetesHTTPRouteExtractor struct {
log *logger.Logger
}
type KubernetesHTTPRouteExtractorInput struct {
Log *logger.Logger
}
func NewKubernetesHTTPRouteExtractor(i KubernetesHTTPRouteExtractorInput) *KubernetesHTTPRouteExtractor {
return &KubernetesHTTPRouteExtractor{
log: i.Log,
}
}
func (k *KubernetesHTTPRouteExtractor) getHosts(hostnames []gateway.Hostname) []string {
var hosts []string
for _, hostname := range hostnames {
if hostname != "" {
hosts = append(hosts, string(hostname))
}
}
return hosts
}
func (k *KubernetesHTTPRouteExtractor) getRuleMatchers(matchers []gateway.HTTPRouteMatch) []string {
var res []string
for _, m := range matchers {
if m.Path == nil {
res = append(res, "/")
continue
}
pathType := gateway.PathMatchPathPrefix
if m.Path.Type != nil {
pathType = *m.Path.Type
}
if pathType != gateway.PathMatchPathPrefix {
continue
}
pathValue := "/"
if m.Path.Value != nil {
pathValue = *m.Path.Value
}
res = append(res, pathValue)
}
return res
}
func (k *KubernetesHTTPRouteExtractor) getPaths(rules []gateway.HTTPRouteRule) []string {
var paths []string
for _, rule := range rules {
if len(rule.Matches) == 0 {
paths = append(paths, "/")
continue
}
matchers := k.getRuleMatchers(rule.Matches)
paths = append(paths, matchers...)
}
return paths
}
func (k *KubernetesHTTPRouteExtractor) Extract(route *gateway.HTTPRoute) *ExtractionResult {
hosts := k.getHosts(route.Spec.Hostnames)
paths := k.getPaths(route.Spec.Rules)
namespace := route.GetNamespace()
name := route.GetName()
annotations := route.GetAnnotations()
if !slices.Contains(paths, "/") {
k.log.App.Warn().Str("namespace", namespace).Str("name", name).Strs("paths", paths).Msg("Route does not contain a catch-all path, another route may be able to bypass auth checks if it routes the same host with a different path. Consider adding a catch-all path to this route to ensure auth checks are applied to all paths for this host.")
}
return &ExtractionResult{
typ: ResourceTypeHTTPRoute,
name: name,
namespace: namespace,
hosts: hosts,
annotations: annotations,
}
}
+2 -61
View File
@@ -21,7 +21,6 @@ import (
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
gateway "sigs.k8s.io/gateway-api/apis/v1"
)
type watchedResource struct {
@@ -36,9 +35,7 @@ func (w watchedResource) pretty() string {
type ResourceType string
const (
ResourceTypeIngress ResourceType = "ingress"
ResourceTypeGRPCRoute ResourceType = "grpcroute"
ResourceTypeHTTPRoute ResourceType = "httproute"
ResourceTypeIngress ResourceType = "ingress"
)
var supportedResources = []watchedResource{
@@ -50,22 +47,6 @@ var supportedResources = []watchedResource{
},
typ: ResourceTypeIngress,
},
{
gvr: schema.GroupVersionResource{
Group: "gateway.networking.k8s.io",
Version: "v1",
Resource: "httproutes",
},
typ: ResourceTypeHTTPRoute,
},
{
gvr: schema.GroupVersionResource{
Group: "gateway.networking.k8s.io",
Version: "v1",
Resource: "grpcroutes",
},
typ: ResourceTypeGRPCRoute,
},
}
func hostMatchesHostname(host string, hostname string) bool {
@@ -96,8 +77,6 @@ type ExtractionResult struct {
type typedItem struct {
typ ResourceType
ingress *networking.Ingress
route *gateway.HTTPRoute
grpc *gateway.GRPCRoute
}
func convertFromUnstructured[T any](obj *unstructured.Unstructured) (*T, error) {
@@ -121,24 +100,6 @@ func (ti *typedItem) fromUnstructured(typ ResourceType, obj *unstructured.Unstru
typ: ResourceTypeIngress,
ingress: typed,
}, nil
case ResourceTypeHTTPRoute:
typed, err := convertFromUnstructured[gateway.HTTPRoute](obj)
if err != nil {
return nil, err
}
return &typedItem{
typ: ResourceTypeHTTPRoute,
route: typed,
}, nil
case ResourceTypeGRPCRoute:
typed, err := convertFromUnstructured[gateway.GRPCRoute](obj)
if err != nil {
return nil, err
}
return &typedItem{
typ: ResourceTypeGRPCRoute,
grpc: typed,
}, nil
default:
return nil, fmt.Errorf("unknown resource type %s", typ)
}
@@ -169,9 +130,7 @@ type KubernetesService struct {
connected bool
extractors struct {
ingress *KubernetesIngressExtractor
httproute *KubernetesHTTPRouteExtractor
grpc *KubernetesGRPCRouteExtractor
ingress *KubernetesIngressExtractor
}
}
@@ -203,12 +162,6 @@ func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error)
service.extractors.ingress = NewKubernetesIngressExtractor(KubernetesIngressExtractorInput{
Log: i.Log,
})
service.extractors.httproute = NewKubernetesHTTPRouteExtractor(KubernetesHTTPRouteExtractorInput{
Log: i.Log,
})
service.extractors.grpc = NewKubernetesGRPCRouteExtractor(KubernetesGRPCRouteExtractorInput{
Log: i.Log,
})
watchedGVRs := make(map[string]bool)
@@ -297,18 +250,6 @@ func (k *KubernetesService) updateFromItem(res watchedResource, typedItem *typed
return
}
result = k.extractors.ingress.Extract(typedItem.ingress)
case ResourceTypeHTTPRoute:
if typedItem.route == nil {
k.log.App.Warn().Str("res", res.pretty()).Msg("HTTPRoute is nil, skipping")
return
}
result = k.extractors.httproute.Extract(typedItem.route)
case ResourceTypeGRPCRoute:
if typedItem.grpc == nil {
k.log.App.Warn().Str("res", res.pretty()).Msg("GRPCRoute is nil, skipping")
return
}
result = k.extractors.grpc.Extract(typedItem.grpc)
}
if result == nil {
-126
View File
@@ -11,7 +11,6 @@ import (
networking "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
gateway "sigs.k8s.io/gateway-api/apis/v1"
)
func watchedResourceForTest(t *testing.T, typ ResourceType) watchedResource {
@@ -31,8 +30,6 @@ func newKubernetesServiceForTest(log *logger.Logger) *KubernetesService {
log: log,
}
service.extractors.ingress = NewKubernetesIngressExtractor(KubernetesIngressExtractorInput{Log: log})
service.extractors.httproute = NewKubernetesHTTPRouteExtractor(KubernetesHTTPRouteExtractorInput{Log: log})
service.extractors.grpc = NewKubernetesGRPCRouteExtractor(KubernetesGRPCRouteExtractorInput{Log: log})
return service
}
@@ -50,34 +47,6 @@ func testIngress(name string, annotations map[string]string, hosts ...string) *t
}
}
func testHTTPRoute(name string, annotations map[string]string, hosts ...string) *typedItem {
hostnames := make([]gateway.Hostname, 0, len(hosts))
for _, host := range hosts {
hostnames = append(hostnames, gateway.Hostname(host))
}
return &typedItem{
typ: ResourceTypeHTTPRoute,
route: &gateway.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default", Annotations: annotations},
Spec: gateway.HTTPRouteSpec{Hostnames: hostnames, Rules: []gateway.HTTPRouteRule{{}}},
},
}
}
func testGRPCRoute(name string, annotations map[string]string, hosts ...string) *typedItem {
hostnames := make([]gateway.Hostname, 0, len(hosts))
for _, host := range hosts {
hostnames = append(hostnames, gateway.Hostname(host))
}
return &typedItem{
typ: ResourceTypeGRPCRoute,
grpc: &gateway.GRPCRoute{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default", Annotations: annotations},
Spec: gateway.GRPCRouteSpec{Hostnames: hostnames},
},
}
}
func lookupApp(service *KubernetesService, domain string) *model.App {
var app *model.App
service.getEntry(domain, func(name string, candidate *model.App) bool {
@@ -119,41 +88,6 @@ func TestKubernetesServiceUpdateFromItem(t *testing.T) {
}, "Dashboard.example.com"),
domain: "dashboard.example.com", allow: "alice",
},
{
name: "HTTPRoute matches a configured domain",
resource: ResourceTypeHTTPRoute,
item: testHTTPRoute("http-route", map[string]string{
"tinyauth.apps.api.config.domain": "api.example.com",
"tinyauth.apps.api.users.allow": "bob",
}, "api.example.com"),
domain: "api.example.com", wantConfigDomain: "api.example.com", allow: "bob",
},
{
name: "HTTPRoute wildcard matches nested subdomains",
resource: ResourceTypeHTTPRoute,
item: testHTTPRoute("http-route", map[string]string{
"tinyauth.apps.api.config.domain": "deep.api.example.com",
"tinyauth.apps.api.users.allow": "bob",
}, "*.example.com"),
domain: "deep.api.example.com", wantConfigDomain: "deep.api.example.com", allow: "bob",
},
{
name: "GRPCRoute matches a configured domain",
resource: ResourceTypeGRPCRoute,
item: testGRPCRoute("grpc-route", map[string]string{
"tinyauth.apps.grpc.config.domain": "grpc.example.com",
"tinyauth.apps.grpc.users.allow": "carol",
}, "grpc.example.com"),
domain: "grpc.example.com", wantConfigDomain: "grpc.example.com", allow: "carol",
},
{
name: "GRPCRoute matches an app name through a wildcard",
resource: ResourceTypeGRPCRoute,
item: testGRPCRoute("grpc-route", map[string]string{
"tinyauth.apps.grpc.users.allow": "carol",
}, "*.example.com"),
domain: "grpc.example.com", allow: "carol",
},
}
for _, test := range tests {
@@ -180,13 +114,7 @@ func TestKubernetesServiceUpdateFromItemRemovesStaleEntries(t *testing.T) {
}{
{"Ingress without annotations", ResourceTypeIngress, testIngress("route", nil, "app.example.com")},
{"Ingress without hosts", ResourceTypeIngress, testIngress("route", map[string]string{"tinyauth.apps.app.users.allow": "alice"})},
{"HTTPRoute without annotations", ResourceTypeHTTPRoute, testHTTPRoute("route", nil, "app.example.com")},
{"HTTPRoute without hosts", ResourceTypeHTTPRoute, testHTTPRoute("route", map[string]string{"tinyauth.apps.app.users.allow": "alice"})},
{"GRPCRoute without annotations", ResourceTypeGRPCRoute, testGRPCRoute("route", nil, "app.example.com")},
{"GRPCRoute without hosts", ResourceTypeGRPCRoute, testGRPCRoute("route", map[string]string{"tinyauth.apps.app.users.allow": "alice"})},
{"Ingress with invalid annotations", ResourceTypeIngress, testIngress("route", map[string]string{"tinyauth.apps.app.users.break": "invalid"}, "app.example.com")},
{"HTTPRoute with invalid annotations", ResourceTypeHTTPRoute, testHTTPRoute("route", map[string]string{"tinyauth.apps.app.users.break": "invalid"}, "app.example.com")},
{"GRPCRoute with invalid annotations", ResourceTypeGRPCRoute, testGRPCRoute("route", map[string]string{"tinyauth.apps.app.users.break": "invalid"}, "app.example.com")},
}
for _, test := range tests {
@@ -223,30 +151,6 @@ func TestTypedItemFromUnstructured(t *testing.T) {
assert.Equal(t, "app.example.com", item.ingress.Spec.Rules[0].Host)
},
},
{
name: "HTTPRoute",
resource: ResourceTypeHTTPRoute,
item: unstructured.Unstructured{Object: map[string]any{
"metadata": map[string]any{"name": "http-route", "namespace": "default"},
"spec": map[string]any{"hostnames": []any{"app.example.com"}},
}},
assert: func(t *testing.T, item *typedItem) {
require.NotNil(t, item.route)
assert.Equal(t, gateway.Hostname("app.example.com"), item.route.Spec.Hostnames[0])
},
},
{
name: "GRPCRoute",
resource: ResourceTypeGRPCRoute,
item: unstructured.Unstructured{Object: map[string]any{
"metadata": map[string]any{"name": "grpc-route", "namespace": "default"},
"spec": map[string]any{"hostnames": []any{"app.example.com"}},
}},
assert: func(t *testing.T, item *typedItem) {
require.NotNil(t, item.grpc)
assert.Equal(t, gateway.Hostname("app.example.com"), item.grpc.Spec.Hostnames[0])
},
},
}
for _, test := range tests {
@@ -305,8 +209,6 @@ func TestKubernetesServiceKeepsResourceTypesSeparate(t *testing.T) {
domain string
}{
{ResourceTypeIngress, testIngress("shared", map[string]string{"tinyauth.apps.ingress.config.domain": "ingress.example.com"}, "ingress.example.com"), "ingress.example.com"},
{ResourceTypeHTTPRoute, testHTTPRoute("shared", map[string]string{"tinyauth.apps.http.config.domain": "http.example.com"}, "http.example.com"), "http.example.com"},
{ResourceTypeGRPCRoute, testGRPCRoute("shared", map[string]string{"tinyauth.apps.grpc.config.domain": "grpc.example.com"}, "grpc.example.com"), "grpc.example.com"},
}
for _, resource := range resources {
@@ -317,34 +219,6 @@ func TestKubernetesServiceKeepsResourceTypesSeparate(t *testing.T) {
}
}
func TestKubernetesHTTPRouteExtractorPaths(t *testing.T) {
log := logger.NewLogger().WithTestConfig()
log.Init()
extractor := NewKubernetesHTTPRouteExtractor(KubernetesHTTPRouteExtractorInput{Log: log})
prefix := gateway.PathMatchPathPrefix
exact := gateway.PathMatchExact
api := "/api"
tests := []struct {
name string
rules []gateway.HTTPRouteRule
want []string
}{
{"Rule without matches defaults to catch-all", []gateway.HTTPRouteRule{{}}, []string{"/"}},
{"Match without path defaults to catch-all", []gateway.HTTPRouteRule{{Matches: []gateway.HTTPRouteMatch{{}}}}, []string{"/"}},
{"Path defaults apply independently", []gateway.HTTPRouteRule{{Matches: []gateway.HTTPRouteMatch{{Path: &gateway.HTTPPathMatch{}}}}}, []string{"/"}},
{"Exact paths do not count as catch-all", []gateway.HTTPRouteRule{{Matches: []gateway.HTTPRouteMatch{{Path: &gateway.HTTPPathMatch{Type: &exact}}}}}, nil},
{"Prefix paths are retained", []gateway.HTTPRouteRule{{Matches: []gateway.HTTPRouteMatch{{Path: &gateway.HTTPPathMatch{Type: &prefix, Value: &api}}}}}, []string{"/api"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.want, extractor.getPaths(test.rules))
})
}
}
func TestKubernetesHostMatching(t *testing.T) {
tests := []struct {
name string