Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[authpolicy-v2] AuthPolicy validation rules #281

Merged
merged 2 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 67 additions & 19 deletions api/v1beta2/authpolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,17 @@
CommonAuthRuleSpec `json:""`
}

// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.routeSelectors)",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.authentication) || !self.rules.authentication.exists(x, has(self.rules.authentication[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.metadata) || !self.rules.metadata.exists(x, has(self.rules.metadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.authorization) || !self.rules.authorization.exists(x, has(self.rules.authorization[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.response) || !has(self.rules.response.success) || self.rules.response.success.headers.exists(x, has(self.rules.response.success.headers[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.response) || !has(self.rules.response.success) || self.rules.response.success.dynamicMetadata.exists(x, has(self.rules.response.success.dynamicMetadata[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.rules.callbacks) || !self.rules.callbacks.exists(x, has(self.rules.callbacks[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
type AuthPolicySpec struct {
// TargetRef identifies an API object to apply policy to.
// +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'"
// +kubebuilder:validation:XValidation:rule="self.kind == 'HTTPRoute' || self.kind == 'Gateway'",message="Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'"
TargetRef gatewayapiv1alpha2.PolicyTargetReference `json:"targetRef"`

// Top-level route selectors.
Expand Down Expand Up @@ -203,25 +212,6 @@
}
}

//+kubebuilder:object:root=true

// AuthPolicyList contains a list of AuthPolicy
type AuthPolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []AuthPolicy `json:"items"`
}

func (l *AuthPolicyList) GetItems() []common.KuadrantPolicy {
return common.Map(l.Items, func(item AuthPolicy) common.KuadrantPolicy {
return &item
})
}

func init() {
SchemeBuilder.Register(&AuthPolicy{}, &AuthPolicyList{})
}

func (ap *AuthPolicy) Validate() error {
if ap.Spec.TargetRef.Group != ("gateway.networking.k8s.io") {
return fmt.Errorf("invalid targetRef.Group %s. The only supported group is gateway.networking.k8s.io", ap.Spec.TargetRef.Group)
Expand All @@ -238,6 +228,37 @@
if ap.Spec.TargetRef.Namespace != nil && string(*ap.Spec.TargetRef.Namespace) != ap.Namespace {
return fmt.Errorf("invalid targetRef.Namespace %s. Currently only supporting references to the same namespace", *ap.Spec.TargetRef.Namespace)
}

// prevents usage of routeSelectors in a gateway AuthPolicy
if ap.Spec.TargetRef.Kind == ("Gateway") {
containRouteSelectors := func(config map[string]RouteSelectorsGetter) bool {
if config == nil {
return false
}

Check warning on line 237 in api/v1beta2/authpolicy_types.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta2/authpolicy_types.go#L236-L237

Added lines #L236 - L237 were not covered by tests
for _, config := range config {
if len(config.GetRouteSelectors()) > 0 {
return true
}
}
return false
}
configs := []map[string]RouteSelectorsGetter{
{"": ap.Spec},
toRouteSelectorGetterMap(ap.Spec.AuthScheme.Authentication),
toRouteSelectorGetterMap(ap.Spec.AuthScheme.Metadata),
toRouteSelectorGetterMap(ap.Spec.AuthScheme.Authorization),
toRouteSelectorGetterMap(ap.Spec.AuthScheme.Callbacks),
}
if r := ap.Spec.AuthScheme.Response; r != nil {
configs = append(configs, toRouteSelectorGetterMap(r.Success.Headers), toRouteSelectorGetterMap(r.Success.DynamicMetadata))
}

Check warning on line 254 in api/v1beta2/authpolicy_types.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta2/authpolicy_types.go#L253-L254

Added lines #L253 - L254 were not covered by tests
for _, config := range configs {
if containRouteSelectors(config) {
return fmt.Errorf("route selectors not supported when targeting a Gateway")
}
}
}

return nil
}

Expand Down Expand Up @@ -283,3 +304,30 @@

return
}

//+kubebuilder:object:root=true

// AuthPolicyList contains a list of AuthPolicy
type AuthPolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []AuthPolicy `json:"items"`
}

func (l *AuthPolicyList) GetItems() []common.KuadrantPolicy {
return common.Map(l.Items, func(item AuthPolicy) common.KuadrantPolicy {
return &item
})
}

func init() {
SchemeBuilder.Register(&AuthPolicy{}, &AuthPolicyList{})
}

func toRouteSelectorGetterMap[T RouteSelectorsGetter](m map[string]T) map[string]RouteSelectorsGetter {
result := make(map[string]RouteSelectorsGetter)
for k, v := range m {
result[k] = v
}
return result
}
204 changes: 204 additions & 0 deletions api/v1beta2/authpolicy_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

authorinoapi "github.com/kuadrant/authorino/api/v1beta2"
"github.com/kuadrant/kuadrant-operator/pkg/common"
)

Expand Down Expand Up @@ -239,6 +240,209 @@ func TestAuthPolicyGetRulesHostnames(t *testing.T) {
}
}

func TestAuthPolicyValidate(t *testing.T) {
testCases := []struct {
name string
policy *AuthPolicy
valid bool
message string
}{
{
name: "valid policy targeting a httproute",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "gateway.networking.k8s.io",
Kind: "HTTPRoute",
Name: "my-route",
},
},
},
valid: true,
},
{
name: "valid policy targeting a gateway",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "gateway.networking.k8s.io",
Kind: "Gateway",
Name: "my-gw",
},
},
},
valid: true,
},
{
name: "invalid targetRef group",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "not-gateway.networking.k8s.io.group",
Kind: "HTTPRoute",
Name: "my-non-gwapi-route",
},
},
},
message: "invalid targetRef.Group not-gateway.networking.k8s.io.group. The only supported group is gateway.networking.k8s.io",
},
{
name: "invalid targetRef kind",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "gateway.networking.k8s.io",
Kind: "TCPRoute",
Name: "my-tcp-route",
},
},
},
message: "invalid targetRef.Kind TCPRoute. The only supported kinds are HTTPRoute and Gateway",
},
{
name: "invalid usage of top-level route selectors with a gateway targetRef",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "gateway.networking.k8s.io",
Kind: "Gateway",
Name: "my-gw",
},
RouteSelectors: []RouteSelector{
{
Hostnames: []gatewayapiv1beta1.Hostname{"*.foo.io"},
Matches: []gatewayapiv1beta1.HTTPRouteMatch{
{
Path: &gatewayapiv1beta1.HTTPPathMatch{
Value: ptr.To("/foo"),
},
},
},
},
},
},
},
message: "route selectors not supported when targeting a Gateway",
},
{
name: "invalid usage of config-level route selectors with a gateway targetRef",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "gateway.networking.k8s.io",
Kind: "Gateway",
Name: "my-gw",
},
AuthScheme: AuthSchemeSpec{
Authentication: map[string]AuthenticationSpec{
"my-rule": {
AuthenticationSpec: authorinoapi.AuthenticationSpec{
AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{
AnonymousAccess: &authorinoapi.AnonymousAccessSpec{},
},
},
CommonAuthRuleSpec: CommonAuthRuleSpec{
RouteSelectors: []RouteSelector{
{
Hostnames: []gatewayapiv1beta1.Hostname{"*.foo.io"},
Matches: []gatewayapiv1beta1.HTTPRouteMatch{
{
Path: &gatewayapiv1beta1.HTTPPathMatch{
Value: ptr.To("/foo"),
},
},
},
},
},
},
},
},
},
},
},
message: "route selectors not supported when targeting a Gateway",
},
{
name: "invalid targetRef namespace",
policy: &AuthPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "my-policy",
Namespace: "my-namespace",
},
Spec: AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: "gateway.networking.k8s.io",
Kind: "HTTPRoute",
Name: "my-route",
Namespace: ptr.To(gatewayapiv1beta1.Namespace("other-namespace")),
},
AuthScheme: AuthSchemeSpec{
Authentication: map[string]AuthenticationSpec{
"my-rule": {
AuthenticationSpec: authorinoapi.AuthenticationSpec{
AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{
AnonymousAccess: &authorinoapi.AnonymousAccessSpec{},
},
},
CommonAuthRuleSpec: CommonAuthRuleSpec{
RouteSelectors: []RouteSelector{
{
Hostnames: []gatewayapiv1beta1.Hostname{"*.foo.io"},
Matches: []gatewayapiv1beta1.HTTPRouteMatch{
{
Path: &gatewayapiv1beta1.HTTPPathMatch{
Value: ptr.To("/foo"),
},
},
},
},
},
},
},
},
},
},
},
message: "invalid targetRef.Namespace other-namespace. Currently only supporting references to the same namespace",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.policy.Validate()
if tc.valid && result != nil {
t.Errorf("Expected policy to be valid, got %t", result)
}
if !tc.valid && result == nil {
t.Error("Expected policy to be invalid, got no validation error")
}
})
}
}

func testBuildRouteSelector() RouteSelector {
return RouteSelector{
Hostnames: []gatewayapiv1beta1.Hostname{"toystore.kuadrant.io"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ metadata:
capabilities: Basic Install
categories: Integration & Delivery
containerImage: quay.io/kuadrant/kuadrant-operator:latest
createdAt: "2023-10-20T10:36:08Z"
createdAt: "2023-10-20T10:46:36Z"
operators.operatorframework.io/builder: operator-sdk-v1.28.1
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/Kuadrant/kuadrant-operator
Expand Down
29 changes: 29 additions & 0 deletions bundle/manifests/kuadrant.io_authpolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4121,6 +4121,12 @@ spec:
- kind
- name
type: object
x-kubernetes-validations:
- message: Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'
rule: self.group == 'gateway.networking.k8s.io'
- message: Invalid targetRef.kind. The only supported values are 'HTTPRoute'
and 'Gateway'
rule: self.kind == 'HTTPRoute' || self.kind == 'Gateway'
when:
description: Overall conditions for the AuthPolicy to be enforced.
If omitted, the AuthPolicy will be enforced at all requests to the
Expand Down Expand Up @@ -4177,6 +4183,29 @@ spec:
required:
- targetRef
type: object
x-kubernetes-validations:
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.routeSelectors)
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.rules.authentication)
|| !self.rules.authentication.exists(x, has(self.rules.authentication[x].routeSelectors))
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.rules.metadata)
|| !self.rules.metadata.exists(x, has(self.rules.metadata[x].routeSelectors))
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.rules.authorization)
|| !self.rules.authorization.exists(x, has(self.rules.authorization[x].routeSelectors))
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.rules.response)
|| !has(self.rules.response.success) || self.rules.response.success.headers.exists(x,
has(self.rules.response.success.headers[x].routeSelectors))
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.rules.response)
|| !has(self.rules.response.success) || self.rules.response.success.dynamicMetadata.exists(x,
has(self.rules.response.success.dynamicMetadata[x].routeSelectors))
- message: route selectors not supported when targeting a Gateway
rule: self.targetRef.kind != 'Gateway' || !has(self.rules.callbacks)
|| !self.rules.callbacks.exists(x, has(self.rules.callbacks[x].routeSelectors))
status:
properties:
conditions:
Expand Down
Loading
Loading