Skip to content

Commit

Permalink
feat(admission): validate regex supplied in HTTPRoute (stop rejecting) (
Browse files Browse the repository at this point in the history
  • Loading branch information
programmer04 authored Sep 7, 2023
1 parent a6b593b commit 18b1948
Show file tree
Hide file tree
Showing 15 changed files with 432 additions and 277 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ Adding a new version? You'll need three changes:

### Added

- Allow regex expressions in `HTTPRoute` configuration and provide validation in admission webhook.
Before this change admission webhook used to reject entirely such configurations incorrectly as not supported yet.
[#4608](https://github.com/Kong/kubernetes-ingress-controller/pull/4608)
- Add new feature gate `RewriteURIs` to enable/disable the `konghq.com/rewrite`
annotation (default disabled).
[#4360](https://github.com/Kong/kubernetes-ingress-controller/pull/4360)
Expand Down
8 changes: 8 additions & 0 deletions internal/admission/adminapi_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ func (p DefaultAdminAPIServicesProvider) GetInfoService() (kong.AbstractInfoServ
return c.Info, true
}

func (p DefaultAdminAPIServicesProvider) GetRoutesService() (kong.AbstractRouteService, bool) {
c, ok := p.designatedAdminAPIClient()
if !ok {
return nil, ok
}
return c.Routes, true
}

func (p DefaultAdminAPIServicesProvider) designatedAdminAPIClient() (*kong.Client, bool) {
gwClients := p.gatewayClientsProvider.GatewayClients()
if len(gwClients) == 0 {
Expand Down
24 changes: 22 additions & 2 deletions internal/admission/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
gatewaycontroller "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/gateway"
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser"
credsvalidation "github.com/kong/kubernetes-ingress-controller/v2/internal/validation/consumers/credentials"
gatewayvalidators "github.com/kong/kubernetes-ingress-controller/v2/internal/validation/gateway"
"github.com/kong/kubernetes-ingress-controller/v2/internal/versions"
Expand All @@ -41,6 +42,7 @@ type AdminAPIServicesProvider interface {
GetPluginsService() (kong.AbstractPluginService, bool)
GetConsumerGroupsService() (kong.AbstractConsumerGroupService, bool)
GetInfoService() (kong.AbstractInfoService, bool)
GetRoutesService() (kong.AbstractRouteService, bool)
}

// KongHTTPValidator implements KongValidator interface to validate Kong
Expand All @@ -50,6 +52,8 @@ type KongHTTPValidator struct {
SecretGetter kongstate.SecretGetter
ManagerClient client.Client
AdminAPIServicesProvider AdminAPIServicesProvider
ParserFeatures parser.FeatureFlags
KongVersion semver.Version

ingressClassMatcher func(*metav1.ObjectMeta, string, annotations.ClassMatching) bool
}
Expand All @@ -63,13 +67,17 @@ func NewKongHTTPValidator(
managerClient client.Client,
ingressClass string,
servicesProvider AdminAPIServicesProvider,
parserFeatures parser.FeatureFlags,
kongVersion semver.Version,
) KongHTTPValidator {
matcher := annotations.IngressClassValidatorFuncFromObjectMeta(ingressClass)
return KongHTTPValidator{
Logger: logger,
SecretGetter: &managerClientSecretGetter{managerClient: managerClient},
ManagerClient: managerClient,
AdminAPIServicesProvider: servicesProvider,
ParserFeatures: parserFeatures,
KongVersion: kongVersion,

ingressClassMatcher: matcher,
}
Expand Down Expand Up @@ -413,9 +421,21 @@ func (validator KongHTTPValidator) ValidateHTTPRoute(
return true, "", nil
}

// now that we know whether or not the HTTPRoute is linked to a managed
// Now that we know whether or not the HTTPRoute is linked to a managed
// Gateway we can run it through full validation.
return gatewayvalidators.ValidateHTTPRoute(&httproute, managedGateways...)
var routeValidator gatewayvalidators.RouteValidator = noOpRoutesValidator{}
if routesSvc, ok := validator.AdminAPIServicesProvider.GetRoutesService(); ok {
routeValidator = routesSvc
}
return gatewayvalidators.ValidateHTTPRoute(
ctx, routeValidator, validator.ParserFeatures, validator.KongVersion, &httproute, managedGateways...,
)
}

type noOpRoutesValidator struct{}

func (noOpRoutesValidator) Validate(_ context.Context, _ *kong.Route) (bool, string, error) {
return true, "", nil
}

// -----------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions internal/admission/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type fakeServicesProvider struct {
consumerSvc kong.AbstractConsumerService
consumerGroupSvc kong.AbstractConsumerGroupService
infoSvc kong.AbstractInfoService
routeSvc kong.AbstractRouteService
}

func (f fakeServicesProvider) GetConsumersService() (kong.AbstractConsumerService, bool) {
Expand Down Expand Up @@ -78,6 +79,13 @@ func (f fakeServicesProvider) GetPluginsService() (kong.AbstractPluginService, b
return nil, false
}

func (f fakeServicesProvider) GetRoutesService() (kong.AbstractRouteService, bool) {
if f.routeSvc != nil {
return f.routeSvc, true
}
return nil, false
}

func TestKongHTTPValidator_ValidatePlugin(t *testing.T) {
store, _ := store.NewFakeStore(store.FakeObjects{})
type args struct {
Expand Down
6 changes: 4 additions & 2 deletions internal/dataplane/parser/translate_httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (p *Parser) ingressRulesFromHTTPRouteWithCombinedServiceRoutes(httproute *g

// generate the routes for the service and attach them to the service
for _, kongRouteTranslation := range kongServiceTranslation.KongRoutes {
routes, err := generateKongRouteFromTranslation(httproute, kongRouteTranslation, p.featureFlags.RegexPathPrefix, p.featureFlags.ExpressionRoutes, p.kongVersion)
routes, err := GenerateKongRouteFromTranslation(httproute, kongRouteTranslation, p.featureFlags.RegexPathPrefix, p.featureFlags.ExpressionRoutes, p.kongVersion)
if err != nil {
return err
}
Expand Down Expand Up @@ -284,7 +284,9 @@ func generateKongRoutesFromHTTPRouteRule(
return routes, nil
}

func generateKongRouteFromTranslation(
// GenerateKongRouteFromTranslation generates Kong routes from HTTPRoute
// pointing to a specific backend. It is used for both traditional and expression based routes.
func GenerateKongRouteFromTranslation(
httproute *gatewayv1beta1.HTTPRoute,
translation translators.KongRouteTranslation,
addRegexPrefix bool,
Expand Down
11 changes: 6 additions & 5 deletions internal/manager/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,19 @@ func Run(ctx context.Context, c *Config, diagnostic util.ConfigDumpDiagnostic, d
clientsManager.Run()
}

setupLog.Info("Starting Admission Server")
if err := setupAdmissionServer(ctx, c, clientsManager, mgr.GetClient(), deprecatedLogger); err != nil {
return err
}

parserFeatureFlags := parser.NewFeatureFlags(
deprecatedLogger,
featureGates,
kongSemVersion,
routerFlavor,
c.UpdateStatus,
)

setupLog.Info("Starting Admission Server")
if err := setupAdmissionServer(ctx, c, clientsManager, mgr.GetClient(), deprecatedLogger, parserFeatureFlags, kongSemVersion); err != nil {
return err
}

cache := store.NewCacheStores()
configParser, err := parser.NewParser(
deprecatedLogger,
Expand Down
6 changes: 6 additions & 0 deletions internal/manager/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/avast/retry-go/v4"
"github.com/blang/semver/v4"
"github.com/bombsimon/logrusr/v4"
"github.com/go-logr/logr"
"github.com/kong/deck/cprint"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/v2/internal/admission"
"github.com/kong/kubernetes-ingress-controller/v2/internal/clients"
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser"
"github.com/kong/kubernetes-ingress-controller/v2/internal/manager/scheme"
"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
dataplaneutil "github.com/kong/kubernetes-ingress-controller/v2/internal/util/dataplane"
Expand Down Expand Up @@ -171,6 +173,8 @@ func setupAdmissionServer(
clientsManager *clients.AdminAPIClientsManager,
managerClient client.Client,
deprecatedLogger logrus.FieldLogger,
parserFeatures parser.FeatureFlags,
kongVersion semver.Version,
) error {
logger := deprecatedLogger.WithField("component", "admission-server")

Expand All @@ -186,6 +190,8 @@ func setupAdmissionServer(
managerClient,
managerConfig.IngressClassName,
adminAPIServicesProvider,
parserFeatures,
kongVersion,
),
Logger: logger,
}, logger)
Expand Down
105 changes: 79 additions & 26 deletions internal/validation/gateway/httproute.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
package gateway

import (
"context"
"fmt"
"strings"

"github.com/blang/semver/v4"
"github.com/kong/go-kong/kong"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser"
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser/translators"
)

type RouteValidator interface {
Validate(context.Context, *kong.Route) (bool, string, error)
}

// -----------------------------------------------------------------------------
// Validation - HTTPRoute - Public Functions
// -----------------------------------------------------------------------------

// ValidateHTTPRoute provides a suite of validation for a given HTTPRoute and
// any number of Gateway resources it's attached to that the caller wants to
// have it validated against.
func ValidateHTTPRoute(httproute *gatewayv1beta1.HTTPRoute, attachedGateways ...*gatewayv1beta1.Gateway) (bool, string, error) {
// have it validated against. It checks supported features, linked objects,
// and uses provided routesValidator to validate the route against Kong Gateway
// validation endpoint.
func ValidateHTTPRoute(
ctx context.Context,
routesValidator RouteValidator,
parserFeatures parser.FeatureFlags,
kongVersion semver.Version,
httproute *gatewayv1beta1.HTTPRoute,
attachedGateways ...*gatewayv1beta1.Gateway,
) (bool, string, error) {
// validate that no unsupported features are in use
if err := validateHTTPRouteFeatures(httproute); err != nil {
return false, "httproute spec did not pass validation", err
}

// perform Gateway validations for the HTTPRoute (e.g. listener validation, namespace validation, e.t.c.)
for _, gateway := range attachedGateways {
// TODO: validate that the namespace is supported by the linked Gateway objects
Expand All @@ -39,12 +64,7 @@ func ValidateHTTPRoute(httproute *gatewayv1beta1.HTTPRoute, attachedGateways ...
}
}

// validate that no unsupported features are in use
if err := validateHTTPRouteFeatures(httproute); err != nil {
return false, "httproute spec did not pass validation", err
}

return true, "", nil
return validateWithKongGateway(ctx, routesValidator, parserFeatures, kongVersion, httproute)
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -80,28 +100,13 @@ func validateHTTPRouteListener(listener *gatewayv1beta1.Listener) error {
func validateHTTPRouteFeatures(httproute *gatewayv1beta1.HTTPRoute) error {
for _, rule := range httproute.Spec.Rules {
for _, match := range rule.Matches {
// we don't support queryparam matching rules
// See: https://github.com/Kong/kubernetes-ingress-controller/issues/2152
// We don't support query parameters matching rules yet
// See: https://github.com/Kong/kubernetes-ingress-controller/issues/3679
if len(match.QueryParams) != 0 {
return fmt.Errorf("queryparam matching is not yet supported for httproute")
}

// we don't support regex path matching rules
// See: https://github.com/Kong/kubernetes-ingress-controller/issues/2153
if match.Path != nil && match.Path.Type != nil && *match.Path.Type == gatewayv1beta1.PathMatchRegularExpression {
return fmt.Errorf("regex path matching is not yet supported for httproute")
}

// we don't support regex header matching rules
// See: https://github.com/Kong/kubernetes-ingress-controller/issues/2154
for _, hdr := range match.Headers {
if hdr.Type != nil && *hdr.Type == gatewayv1beta1.HeaderMatchRegularExpression {
return fmt.Errorf("regex header matching is not yet supported for httproute")
}
}
}

// we don't support any backendRef types except Kubernetes Services
// We don't support any backendRef types except Kubernetes Services.
for _, ref := range rule.BackendRefs {
if ref.BackendRef.Group != nil && *ref.BackendRef.Group != "core" && *ref.BackendRef.Group != "" {
return fmt.Errorf("%s is not a supported group for httproute backendRefs, only core is supported", *ref.BackendRef.Group)
Expand Down Expand Up @@ -181,3 +186,51 @@ func getListenersForHTTPRouteValidation(sectionName *gatewayv1beta1.SectionName,

return listenersForValidation, nil
}

func validateWithKongGateway(
ctx context.Context, routesValidator RouteValidator, parserFeatures parser.FeatureFlags, kongVersion semver.Version, httproute *gatewayv1beta1.HTTPRoute,
) (bool, string, error) {
// Translate HTTPRoute to Kong Route object(s) that can be sent directly to the Admin API for validation.
// Use KIC parser that works both for traditional and expressions based routes.
var kongRoutes []kong.Route
var errMsgs []string
for _, rule := range httproute.Spec.Rules {
translation := translators.KongRouteTranslation{
Name: "validation-attempt",
Matches: rule.Matches,
Filters: rule.Filters,
}
routes, err := parser.GenerateKongRouteFromTranslation(
httproute, translation, parserFeatures.RegexPathPrefix, parserFeatures.ExpressionRoutes, kongVersion,
)
if err != nil {
errMsgs = append(errMsgs, err.Error())
continue
}
for _, r := range routes {
kongRoutes = append(kongRoutes, r.Route)
}
}
if len(errMsgs) > 0 {
return false, validationMsg(errMsgs), nil
}
// Validate by using feature of Kong Gateway.
for _, kg := range kongRoutes {
kg := kg
ok, msg, err := routesValidator.Validate(ctx, &kg)
if err != nil {
return false, fmt.Sprintf("unable to validate HTTPRoute schema: %s", err.Error()), nil
}
if !ok {
errMsgs = append(errMsgs, msg)
}
}
if len(errMsgs) > 0 {
return false, validationMsg(errMsgs), nil
}
return true, "", nil
}

func validationMsg(errMsgs []string) string {
return fmt.Sprintf("HTTPRoute failed schema validation: %s", strings.Join(errMsgs, ", "))
}
Loading

0 comments on commit 18b1948

Please sign in to comment.