-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new GatewayConfig model type to represent all Gateway-defined configuration, and a corresponding GatewayReconciler interface for taking this model and generating Pomerium configuration. Update DataBrokerReconciler to implement this new interface, with initial logic for converting from Gateway-defined routes to Pomerium routes.
- Loading branch information
1 parent
a5fb66d
commit 01e87a3
Showing
9 changed files
with
379 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Package model contains common data structures between the controller and pomerium config reconciler | ||
package model | ||
|
||
import ( | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
gateway_v1 "sigs.k8s.io/gateway-api/apis/v1" | ||
) | ||
|
||
// GatewayConfig represents the entirety of the Gateway-defined configuration. | ||
type GatewayConfig struct { | ||
Routes []GatewayHTTPRouteConfig | ||
Certificates []*corev1.Secret | ||
} | ||
|
||
// GatewayHTTPRouteConfig represents a single Gateway-defined route together | ||
// with all objects needed to translate it into Pomerium routes. | ||
type GatewayHTTPRouteConfig struct { | ||
*gateway_v1.HTTPRoute | ||
|
||
// Hostnames this route should match. This may differ from the list of Hostnames in the | ||
// HTTPRoute Spec depending on the Gateway configuration. "All" is represented as "*". | ||
Hostnames []gateway_v1.Hostname | ||
|
||
// ValidBackendRefs determines which BackendRefs are allowed to be used for route "To" URLs. | ||
ValidBackendRefs BackendRefChecker | ||
|
||
// Services is a map of all known services in the cluster. | ||
Services map[types.NamespacedName]*corev1.Service | ||
} | ||
|
||
// BackendRefChecker is used to determine which BackendRefs are valid. | ||
type BackendRefChecker interface { | ||
Valid(obj client.Object, r *gateway_v1.BackendRef) bool | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package gateway | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
|
||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/apimachinery/pkg/util/intstr" | ||
gateway_v1 "sigs.k8s.io/gateway-api/apis/v1" | ||
|
||
"github.com/pomerium/ingress-controller/model" | ||
pb "github.com/pomerium/pomerium/pkg/grpc/config" | ||
) | ||
|
||
// applyBackendRefs translates backendRefs to a weighted set of Pomerium "To" URLs. | ||
// [applyFilters] must be called prior to this method. | ||
func applyBackendRefs( | ||
route *pb.Route, | ||
gc *model.GatewayHTTPRouteConfig, | ||
backendRefs []gateway_v1.HTTPBackendRef, | ||
) { | ||
for i := range backendRefs { | ||
if !gc.ValidBackendRefs.Valid(gc.HTTPRoute, &backendRefs[i].BackendRef) { | ||
log.Printf("backendRef %v not valid", &backendRefs[i].BackendRef) // XXX | ||
continue | ||
} | ||
if u, w := backendRefToToURLAndWeight(gc, &backendRefs[i]); w > 0 { | ||
route.To = append(route.To, u) | ||
route.LoadBalancingWeights = append(route.LoadBalancingWeights, w) | ||
} | ||
} | ||
|
||
// From the spec: "If all entries in BackendRefs are invalid, and there are also no filters | ||
// specified in this route rule, all traffic which matches this rule MUST receive a 500 status | ||
// code." | ||
if route.Redirect == nil && len(route.To) == 0 { | ||
route.Response = &pb.RouteDirectResponse{ | ||
Status: http.StatusInternalServerError, | ||
Body: "no valid backend", | ||
} | ||
} | ||
} | ||
|
||
func backendRefToToURLAndWeight( | ||
gc *model.GatewayHTTPRouteConfig, | ||
br *gateway_v1.HTTPBackendRef, | ||
) (string, uint32) { | ||
// Note: currently the only supported backendRef kind is "Service". | ||
namespace := gc.Namespace | ||
if br.Namespace != nil { | ||
namespace = string(*br.Namespace) | ||
} | ||
|
||
port := int32(*br.Port) | ||
|
||
// For a headless service we need the targetPort instead. | ||
// For now this supports only port numbers, not named ports, but this is enough to pass the | ||
// HTTPRouteServiceTypes conformance test cases. | ||
svc := gc.Services[types.NamespacedName{Namespace: namespace, Name: string(br.Name)}] | ||
if svc != nil && svc.Spec.ClusterIP == "None" { | ||
for i := range svc.Spec.Ports { | ||
p := &svc.Spec.Ports[i] | ||
if p.Port == port && p.TargetPort.Type == intstr.Int { | ||
port = p.TargetPort.IntVal | ||
break | ||
} | ||
} | ||
} | ||
|
||
u := fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", br.Name, namespace, port) | ||
|
||
weight := uint32(1) | ||
if br.Weight != nil { | ||
weight = uint32(*br.Weight) | ||
} | ||
|
||
return u, weight | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package gateway | ||
|
||
import ( | ||
gateway_v1 "sigs.k8s.io/gateway-api/apis/v1" | ||
|
||
pb "github.com/pomerium/pomerium/pkg/grpc/config" | ||
) | ||
|
||
func applyFilters(route *pb.Route, filters []gateway_v1.HTTPRouteFilter) { | ||
for i := range filters { | ||
applyFilter(route, &filters[i]) | ||
} | ||
} | ||
|
||
func applyFilter(route *pb.Route, filter *gateway_v1.HTTPRouteFilter) { | ||
switch filter.Type { | ||
case gateway_v1.HTTPRouteFilterRequestHeaderModifier: | ||
applyRequestHeaderFilter(route, filter.RequestHeaderModifier) | ||
case gateway_v1.HTTPRouteFilterRequestRedirect: | ||
applyRedirectFilter(route, filter.RequestRedirect) | ||
} | ||
} | ||
|
||
func applyRequestHeaderFilter(route *pb.Route, filter *gateway_v1.HTTPHeaderFilter) { | ||
// Note: "append" is not supported yet. | ||
route.SetRequestHeaders = makeHeadersMap(filter.Set) | ||
route.RemoveRequestHeaders = filter.Remove | ||
} | ||
|
||
func makeHeadersMap(headers []gateway_v1.HTTPHeader) map[string]string { | ||
if len(headers) == 0 { | ||
return nil | ||
} | ||
|
||
m := make(map[string]string) | ||
for i := range headers { | ||
m[string(headers[i].Name)] = headers[i].Value | ||
} | ||
return m | ||
} | ||
|
||
func applyRedirectFilter(route *pb.Route, filter *gateway_v1.HTTPRequestRedirectFilter) { | ||
rr := pb.RouteRedirect{ | ||
SchemeRedirect: filter.Scheme, | ||
HostRedirect: (*string)(filter.Hostname), | ||
} | ||
if filter.StatusCode != nil { | ||
code := int32(*filter.StatusCode) | ||
rr.ResponseCode = &code | ||
} | ||
if filter.Port != nil { | ||
port := uint32(*filter.Port) | ||
rr.PortRedirect = &port | ||
} | ||
route.Redirect = &rr | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package gateway | ||
|
||
import ( | ||
gateway_v1 "sigs.k8s.io/gateway-api/apis/v1" | ||
|
||
pb "github.com/pomerium/pomerium/pkg/grpc/config" | ||
) | ||
|
||
func applyMatch(route *pb.Route, match *gateway_v1.HTTPRouteMatch) (ok bool) { | ||
if len(match.Headers) > 0 || len(match.QueryParams) > 0 || match.Method != nil { | ||
return false // these features are not supported yet | ||
} | ||
applyPathMatch(route, match.Path) | ||
return true | ||
} | ||
|
||
func applyPathMatch(route *pb.Route, match *gateway_v1.HTTPPathMatch) { | ||
if match == nil || match.Type == nil || match.Value == nil { | ||
return | ||
} | ||
|
||
switch *match.Type { | ||
case gateway_v1.PathMatchExact: | ||
route.Path = *match.Value | ||
case gateway_v1.PathMatchPathPrefix: | ||
route.Prefix = *match.Value | ||
case gateway_v1.PathMatchRegularExpression: | ||
route.Regex = *match.Value | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package gateway | ||
|
||
import ( | ||
"net/url" | ||
|
||
"github.com/pomerium/pomerium/config" | ||
pb "github.com/pomerium/pomerium/pkg/grpc/config" | ||
"google.golang.org/protobuf/proto" | ||
|
||
"github.com/pomerium/ingress-controller/model" | ||
) | ||
|
||
func GatewayRoutes(gc *model.GatewayHTTPRouteConfig) []*pb.Route { | ||
// A single HTTPRoute may need to be represented using many Pomerium routes: | ||
// - An HTTPRoute may have multiple hostnames. | ||
// - An HTTPRoute may have multiple HTTPRouteRules. | ||
// - An HTTPRouteRule may have multiple HTTPRouteMatches. | ||
// First we'll expand all HTTPRouteRules into "template" Pomerium routes, and then we'll | ||
// repeat each "template" route once per hostname. | ||
trs := templateRoutes(gc) | ||
|
||
prs := make([]*pb.Route, 0, len(gc.Hostnames)*len(trs)) | ||
for _, h := range gc.Hostnames { | ||
from := (&url.URL{ | ||
Scheme: "https", | ||
Host: string(h), | ||
}).String() | ||
for _, tr := range trs { | ||
r := proto.Clone(tr).(*pb.Route) | ||
r.From = from | ||
|
||
// Skip any routes that fail to validate. | ||
coreRoute, err := config.NewPolicyFromProto(r) | ||
if err != nil || coreRoute.Validate() != nil { | ||
continue | ||
} | ||
|
||
prs = append(prs, r) | ||
} | ||
} | ||
|
||
return prs | ||
} | ||
|
||
// templateRoutes converts an HTTPRoute into zero or more Pomerium routes, ignoring hostname. | ||
func templateRoutes(gc *model.GatewayHTTPRouteConfig) []*pb.Route { | ||
var prs []*pb.Route | ||
|
||
rules := gc.Spec.Rules | ||
for i := range rules { | ||
rule := &rules[i] | ||
pr := &pb.Route{} | ||
|
||
// From the spec (near https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.HTTPRoute): | ||
// "Implementations MUST ignore any port value specified in the HTTP Host header while | ||
// performing a match and (absent of any applicable header modification configuration) MUST | ||
// forward this header unmodified to the backend." | ||
pr.PreserveHostHeader = true | ||
|
||
applyFilters(pr, rule.Filters) | ||
applyBackendRefs(pr, gc, rule.BackendRefs) | ||
|
||
if len(rule.Matches) == 0 { | ||
prs = append(prs, pr) | ||
continue | ||
} | ||
|
||
for j := range rule.Matches { | ||
cloned := proto.Clone(pr).(*pb.Route) | ||
if applyMatch(cloned, &rule.Matches[j]) { | ||
prs = append(prs, cloned) | ||
} | ||
} | ||
} | ||
|
||
return prs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.