diff --git a/.changelog/154.txt b/.changelog/154.txt new file mode 100644 index 000000000..aa3487839 --- /dev/null +++ b/.changelog/154.txt @@ -0,0 +1,3 @@ +```release-note:breaking-change +Gateway listener `certificateRefs` to secrets in a different namespace now require a [ReferencePolicy](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1alpha2.ReferencePolicy) +``` diff --git a/internal/k8s/reconciler/config/errors.yaml b/internal/k8s/reconciler/config/errors.yaml index 57555c492..20280c1f3 100644 --- a/internal/k8s/reconciler/config/errors.yaml +++ b/internal/k8s/reconciler/config/errors.yaml @@ -1,4 +1,4 @@ - name: CertificateResolution - types: ["NotFound","Unsupported"] + types: ["NotFound","NotPermitted","Unsupported"] - name: Bind types: ["RouteKind","ListenerNamespacePolicy","HostnameMismatch","RouteInvalid"] \ No newline at end of file diff --git a/internal/k8s/reconciler/listener.go b/internal/k8s/reconciler/listener.go index 507624748..5568596b5 100644 --- a/internal/k8s/reconciler/listener.go +++ b/internal/k8s/reconciler/listener.go @@ -11,12 +11,13 @@ import ( "k8s.io/apimachinery/pkg/types" gw "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul-api-gateway/internal/common" "github.com/hashicorp/consul-api-gateway/internal/core" "github.com/hashicorp/consul-api-gateway/internal/k8s/gatewayclient" "github.com/hashicorp/consul-api-gateway/internal/k8s/utils" "github.com/hashicorp/consul-api-gateway/internal/store" - "github.com/hashicorp/go-hclog" ) var ( @@ -120,6 +121,19 @@ func (l *K8sListener) validateTLS(ctx context.Context) error { // we only support a single certificate for now ref := *l.listener.TLS.CertificateRefs[0] + + // require ReferencePolicy for cross-namespace certificateRef + allowed, err := gatewayAllowedForSecretRef(ctx, l.gateway, ref, l.client) + if err != nil { + return err + } else if !allowed { + nsName := getNamespacedName(ref.Name, ref.Namespace, l.gateway.Namespace) + l.logger.Warn("Cross-namespace listener certificate not allowed without matching ReferencePolicy", "refName", nsName.Name, "refNamespace", nsName.Namespace) + l.status.ResolvedRefs.InvalidCertificateRef = NewCertificateResolutionErrorNotPermitted( + fmt.Sprintf("Cross-namespace listener certificate not allowed without matching ReferencePolicy for Secret %q", nsName)) + return nil + } + resource, err := l.resolveCertificateReference(ctx, ref) if err != nil { var certificateErr CertificateResolutionError @@ -127,6 +141,7 @@ func (l *K8sListener) validateTLS(ctx context.Context) error { return err } l.status.ResolvedRefs.InvalidCertificateRef = certificateErr + return nil } else { l.tls.Certificates = []string{resource} } diff --git a/internal/k8s/reconciler/listener_test.go b/internal/k8s/reconciler/listener_test.go index 87bf1e70c..8cb85d0ef 100644 --- a/internal/k8s/reconciler/listener_test.go +++ b/internal/k8s/reconciler/listener_test.go @@ -6,17 +6,19 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" k8s "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" gw "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul-api-gateway/internal/core" "github.com/hashicorp/consul-api-gateway/internal/k8s/gatewayclient/mocks" "github.com/hashicorp/consul-api-gateway/internal/store" storeMocks "github.com/hashicorp/consul-api-gateway/internal/store/mocks" - "github.com/hashicorp/go-hclog" ) func TestListenerID(t *testing.T) { @@ -40,268 +42,355 @@ func TestListenerValidate(t *testing.T) { defer ctrl.Finish() client := mocks.NewMockClient(ctrl) - // protocols - listener := NewK8sListener(&gw.Gateway{}, gw.Listener{}, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), + t.Run("Unsupported protocol", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{}, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) + condition = listener.status.Detached.Condition(0) + require.Equal(t, ListenerConditionReasonUnsupportedProtocol, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition := listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) - condition = listener.status.Detached.Condition(0) - require.Equal(t, ListenerConditionReasonUnsupportedProtocol, condition.Reason) - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPProtocolType, - AllowedRoutes: &gw.AllowedRoutes{ - Kinds: []gw.RouteGroupKind{{ - Kind: gw.Kind("UDPRoute"), - }}, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), + t.Run("Invalid route kinds", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPProtocolType, + AllowedRoutes: &gw.AllowedRoutes{ + Kinds: []gw.RouteGroupKind{{ + Kind: "UDPRoute", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.ResolvedRefs.Condition(0) + require.Equal(t, ListenerConditionReasonInvalidRouteKinds, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.ResolvedRefs.Condition(0) - require.Equal(t, ListenerConditionReasonInvalidRouteKinds, condition.Reason) - // Addresses - listener = NewK8sListener(&gw.Gateway{ - Spec: gw.GatewaySpec{ - Addresses: []gw.GatewayAddress{{}}, - }, - }, gw.Listener{ - Protocol: gw.HTTPProtocolType, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), + t.Run("Unsupported address", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{ + Spec: gw.GatewaySpec{ + Addresses: []gw.GatewayAddress{{}}, + }, + }, gw.Listener{ + Protocol: gw.HTTPProtocolType, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Detached.Condition(0) + require.Equal(t, ListenerConditionReasonUnsupportedAddress, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Detached.Condition(0) - require.Equal(t, ListenerConditionReasonUnsupportedAddress, condition.Reason) - // TLS validations - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), + t.Run("Invalid TLS config", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) - - mode := gw.TLSModePassthrough - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - Mode: &mode, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), + + t.Run("Invalid TLS passthrough", func(t *testing.T) { + mode := gw.TLSModePassthrough + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + Mode: &mode, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{}, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), + t.Run("Invalid certificate ref", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{}, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.ResolvedRefs.Condition(0) + require.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.ResolvedRefs.Condition(0) - require.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ - Name: "secret", - }}, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + t.Run("Fail to retrieve secret", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(nil, expected) + require.True(t, errors.Is(listener.Validate(context.Background()), expected)) + }) + + t.Run("No secret found", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(nil, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.ResolvedRefs.Condition(0) + require.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(nil, expected) - require.True(t, errors.Is(listener.Validate(context.Background()), expected)) - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + t.Run("Invalid cross-namespace secret ref with no ReferencePolicy", func(t *testing.T) { + otherNamespace := gw.Namespace("other-namespace") + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Namespace: &otherNamespace, + Name: "secret", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetReferencePoliciesInNamespace(gomock.Any(), string(otherNamespace)).Return([]gw.ReferencePolicy{}, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.ResolvedRefs.Condition(0) + assert.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) + }) + + t.Run("Valid cross-namespace secret ref with ReferencePolicy", func(t *testing.T) { + gatewayNamespace := gw.Namespace("gateway-namespace") + secretNamespace := gw.Namespace("secret-namespace") + listener := NewK8sListener( + &gw.Gateway{ + ObjectMeta: meta.ObjectMeta{Namespace: string(gatewayNamespace)}, + TypeMeta: meta.TypeMeta{APIVersion: "gateway.networking.k8s.io/v1alpha2", Kind: "Gateway"}}, + gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Namespace: &secretNamespace, + Name: "secret", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetReferencePoliciesInNamespace(gomock.Any(), string(secretNamespace)). + Return([]gw.ReferencePolicy{{ + Spec: gw.ReferencePolicySpec{ + From: []gw.ReferencePolicyFrom{{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Namespace: gatewayNamespace, + }}, + To: []gw.ReferencePolicyTo{{ + Kind: "Secret", + }}, + }, + }}, nil) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, + }, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.ResolvedRefs.Condition(0) + assert.Equal(t, meta.ConditionTrue, condition.Status) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(nil, nil) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.ResolvedRefs.Condition(0) - require.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + t.Run("Valid same-namespace secret ref without ReferencePolicy", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, + }, nil) + assert.Len(t, listener.Config().TLS.Certificates, 0) + assert.NoError(t, listener.Validate(context.Background())) + assert.Len(t, listener.Config().TLS.Certificates, 1) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "secret", - }, - }, nil) - require.Len(t, listener.Config().TLS.Certificates, 0) - require.NoError(t, listener.Validate(context.Background())) - require.Len(t, listener.Config().TLS.Certificates, 1) - group := gw.Group("group") - kind := gw.Kind("kind") - namespace := gw.Namespace("namespace") - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ - Namespace: &namespace, - Group: &group, - Kind: &kind, - Name: "secret", - }}, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + t.Run("Unsupported certificate type", func(t *testing.T) { + group := gw.Group("group") + kind := gw.Kind("kind") + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Group: &group, + Kind: &kind, + Name: "secret", + }}, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.ResolvedRefs.Condition(0) + assert.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) }) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.ResolvedRefs.Condition(0) - require.Equal(t, ListenerConditionReasonInvalidCertificateRef, condition.Reason) - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + t.Run("Valid minimum TLS version", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + Options: map[gw.AnnotationKey]gw.AnnotationValue{ + "api-gateway.consul.hashicorp.com/tls_min_version": "TLSv1_2", + }, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - Options: map[gw.AnnotationKey]gw.AnnotationValue{ - "api-gateway.consul.hashicorp.com/tls_min_version": "TLSv1_2", }, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonReady, condition.Reason) + require.Equal(t, "TLSv1_2", listener.tls.MinVersion) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "secret", - }, - }, nil) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonReady, condition.Reason) - require.Equal(t, "TLSv1_2", listener.tls.MinVersion) - - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + + t.Run("Invalid minimum TLS version", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + Options: map[gw.AnnotationKey]gw.AnnotationValue{ + "api-gateway.consul.hashicorp.com/tls_min_version": "foo", + }, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - Options: map[gw.AnnotationKey]gw.AnnotationValue{ - "api-gateway.consul.hashicorp.com/tls_min_version": "foo", }, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) + require.Equal(t, "unrecognized TLS min version", condition.Message) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "secret", - }, - }, nil) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) - require.Equal(t, "unrecognized TLS min version", condition.Message) - - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + + t.Run("Valid TLS cipher suite", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + Options: map[gw.AnnotationKey]gw.AnnotationValue{ + "api-gateway.consul.hashicorp.com/tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + }, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - Options: map[gw.AnnotationKey]gw.AnnotationValue{ - "api-gateway.consul.hashicorp.com/tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", }, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonReady, condition.Reason) + require.Equal(t, []string{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, listener.tls.CipherSuites) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "secret", - }, - }, nil) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonReady, condition.Reason) - require.Equal(t, []string{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, listener.tls.CipherSuites) - - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + + t.Run("TLS cipher suite not allowed", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + Options: map[gw.AnnotationKey]gw.AnnotationValue{ + "api-gateway.consul.hashicorp.com/tls_min_version": "TLSv1_3", + "api-gateway.consul.hashicorp.com/tls_cipher_suites": "foo", + }, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - Options: map[gw.AnnotationKey]gw.AnnotationValue{ - "api-gateway.consul.hashicorp.com/tls_min_version": "TLSv1_3", - "api-gateway.consul.hashicorp.com/tls_cipher_suites": "foo", }, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) + require.Equal(t, "configuring TLS cipher suites is only supported for TLS 1.2 and earlier", condition.Message) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "secret", - }, - }, nil) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) - require.Equal(t, "configuring TLS cipher suites is only supported for TLS 1.2 and earlier", condition.Message) - - listener = NewK8sListener(&gw.Gateway{}, gw.Listener{ - Protocol: gw.HTTPSProtocolType, - TLS: &gw.GatewayTLSConfig{ - CertificateRefs: []*gw.SecretObjectReference{{ + + t.Run("Invalid TLS cipher suite", func(t *testing.T) { + listener := NewK8sListener(&gw.Gateway{}, gw.Listener{ + Protocol: gw.HTTPSProtocolType, + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Name: "secret", + }}, + Options: map[gw.AnnotationKey]gw.AnnotationValue{ + "api-gateway.consul.hashicorp.com/tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, foo", + }, + }, + }, K8sListenerConfig{ + Logger: hclog.NewNullLogger(), + Client: client, + }) + client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ + ObjectMeta: meta.ObjectMeta{ Name: "secret", - }}, - Options: map[gw.AnnotationKey]gw.AnnotationValue{ - "api-gateway.consul.hashicorp.com/tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, foo", }, - }, - }, K8sListenerConfig{ - Logger: hclog.NewNullLogger(), - Client: client, + }, nil) + require.NoError(t, listener.Validate(context.Background())) + condition := listener.status.Ready.Condition(0) + require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) + require.Equal(t, "unrecognized or unsupported TLS cipher suite: foo", condition.Message) }) - client.EXPECT().GetSecret(gomock.Any(), gomock.Any()).Return(&k8s.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "secret", - }, - }, nil) - require.NoError(t, listener.Validate(context.Background())) - condition = listener.status.Ready.Condition(0) - require.Equal(t, ListenerConditionReasonInvalid, condition.Reason) - require.Equal(t, "unrecognized or unsupported TLS cipher suite: foo", condition.Message) } func TestIsKindInSet(t *testing.T) { diff --git a/internal/k8s/reconciler/route.go b/internal/k8s/reconciler/route.go index 2fd4193a3..801533592 100644 --- a/internal/k8s/reconciler/route.go +++ b/internal/k8s/reconciler/route.go @@ -373,8 +373,9 @@ func (r *K8sRoute) Validate(ctx context.Context) error { if err != nil { return err } else if !allowed { - msg := fmt.Sprintf("Cross-namespace routing not allowed without matching ReferencePolicy for Service %q", getServiceID(ref.Name, ref.Namespace, route.GetNamespace())) - r.logger.Warn("Cross-namespace routing not allowed without matching ReferencePolicy", "refName", ref.Name, "refNamespace", ref.Namespace) + nsName := getNamespacedName(ref.Name, ref.Namespace, route.Namespace) + msg := fmt.Sprintf("Cross-namespace routing not allowed without matching ReferencePolicy for Service %q", nsName) + r.logger.Warn("Cross-namespace routing not allowed without matching ReferencePolicy", "refName", nsName.Name, "refNamespace", nsName.Namespace) r.resolutionErrors.Add(service.NewRefNotPermittedError(msg)) continue } @@ -415,8 +416,9 @@ func (r *K8sRoute) Validate(ctx context.Context) error { if err != nil { return err } else if !allowed { - msg := fmt.Sprintf("Cross-namespace routing not allowed without matching ReferencePolicy for Service %q", getServiceID(ref.Name, ref.Namespace, route.GetNamespace())) - r.logger.Warn("Cross-namespace routing not allowed without matching ReferencePolicy", "refName", ref.Name, "refNamespace", ref.Namespace) + nsName := getNamespacedName(ref.Name, ref.Namespace, route.Namespace) + msg := fmt.Sprintf("Cross-namespace routing not allowed without matching ReferencePolicy for Service %q", nsName) + r.logger.Warn("Cross-namespace routing not allowed without matching ReferencePolicy", "refName", nsName.Name, "refNamespace", nsName.Namespace) r.resolutionErrors.Add(service.NewRefNotPermittedError(msg)) return nil } diff --git a/internal/k8s/reconciler/utils.go b/internal/k8s/reconciler/utils.go index 0507c4f21..cbfe97ff3 100644 --- a/internal/k8s/reconciler/utils.go +++ b/internal/k8s/reconciler/utils.go @@ -120,6 +120,35 @@ func routeAllowedForListenerNamespaces(ctx context.Context, gatewayNS string, al return false, nil } +// gatewayAllowedForSecretRef determines whether the gateway is allowed +// for the secret either by being in the same namespace or by having +// an applicable ReferencePolicy in the same namespace as the secret. +func gatewayAllowedForSecretRef(ctx context.Context, gateway *gw.Gateway, secretRef gw.SecretObjectReference, c gatewayclient.Client) (bool, error) { + fromNS := gateway.GetNamespace() + fromGK := metav1.GroupKind{ + Group: gateway.GroupVersionKind().Group, + Kind: gateway.GroupVersionKind().Kind, + } + + toName := string(secretRef.Name) + toNS := "" + if secretRef.Namespace != nil { + toNS = string(*secretRef.Namespace) + } + + // Kind should default to Secret if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/ef773194892636ea8ecbb2b294daf771d4dd5009/apis/v1alpha2/object_reference_types.go#L59 + toGK := metav1.GroupKind{Kind: "Secret"} + if secretRef.Group != nil { + toGK.Group = string(*secretRef.Group) + } + if secretRef.Kind != nil { + toGK.Kind = string(*secretRef.Kind) + } + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, toName, c) +} + // routeAllowedForBackendRef determines whether the route is allowed // for the backend either by being in the same namespace or by having // an applicable ReferencePolicy in the same namespace as the backend. @@ -127,66 +156,82 @@ func routeAllowedForListenerNamespaces(ctx context.Context, gatewayNS string, al // TODO This func is currently called once for each backendRef on a route and results // in fetching ReferencePolicies more than we technically have to in some cases func routeAllowedForBackendRef(ctx context.Context, route Route, backendRef gw.BackendRef, c gatewayclient.Client) (bool, error) { - backendNamespace := "" + fromNS := route.GetNamespace() + fromGK := metav1.GroupKind{ + Group: route.GroupVersionKind().Group, + Kind: route.GroupVersionKind().Kind, + } + + toName := string(backendRef.Name) + toNS := "" if backendRef.Namespace != nil { - backendNamespace = string(*backendRef.Namespace) + toNS = string(*backendRef.Namespace) } - // Allow if route and backend are in the same namespace - if backendNamespace == "" || route.GetNamespace() == backendNamespace { + // Kind should default to Service if not set + // https://github.com/kubernetes-sigs/gateway-api/blob/ef773194892636ea8ecbb2b294daf771d4dd5009/apis/v1alpha2/object_reference_types.go#L105 + toGK := metav1.GroupKind{Kind: "Service"} + if backendRef.Group != nil { + toGK.Group = string(*backendRef.Group) + } + if backendRef.Kind != nil { + toGK.Kind = string(*backendRef.Kind) + } + + return referenceAllowed(ctx, fromGK, fromNS, toGK, toNS, toName, c) +} + +// referenceAllowed checks to see if a reference between resources is allowed. +// In particular, references from one namespace to a resource in a different namespace +// require an applicable ReferencePolicy be found in the namespace containing the resource +// being referred to. +// +// For example, a Gateway in namespace "foo" may only reference a Secret in namespace "bar" +// if a ReferencePolicy in namespace "bar" allows references from namespace "foo". +func referenceAllowed(ctx context.Context, fromGK metav1.GroupKind, fromNamespace string, toGK metav1.GroupKind, toNamespace, toName string, c gatewayclient.Client) (bool, error) { + // Reference does not cross namespaces + if toNamespace == "" || toNamespace == fromNamespace { return true, nil } - // Allow if ReferencePolicy present for route + backend combination - refPolicies, err := c.GetReferencePoliciesInNamespace(ctx, backendNamespace) + // Fetch all ReferencePolicies in the referenced namespace + refPolicies, err := c.GetReferencePoliciesInNamespace(ctx, toNamespace) if err != nil || len(refPolicies) == 0 { return false, err } for _, refPolicy := range refPolicies { - // Check for a From that applies to the route - validFrom := false + // Check for a From that applies + fromMatch := false for _, from := range refPolicy.Spec.From { - // If this policy allows the group, kind and namespace for this route - if route.GroupVersionKind().Group == string(from.Group) && - route.GroupVersionKind().Kind == string(from.Kind) && - route.GetNamespace() == string(from.Namespace) { - validFrom = true + if fromGK.Group == string(from.Group) && fromGK.Kind == string(from.Kind) && fromNamespace == string(from.Namespace) { + fromMatch = true break } } - // If this ReferencePolicy has no applicable From, no need to check for a To - if !validFrom { + if !fromMatch { continue } - // Backend group should default to empty string if not set - backendRefGroup := gw.Group("") - if backendRef.Group != nil { - backendRefGroup = *backendRef.Group - } - - // Backend kind should default to Service if not set - // TODO Should we default to Service here or go look up the kind from K8s API - // See https://github.com/kubernetes-sigs/gateway-api/issues/1092 - backendRefKind := gw.Kind("Service") - if backendRef.Kind != nil { - backendRefKind = *backendRef.Kind - } - - // Check for a To that applies to the backendRef + // Check for a To that applies for _, to := range refPolicy.Spec.To { - // If this policy allows the group, kind, and name for this backend - if to.Group == backendRefGroup && - to.Kind == backendRefKind && - (to.Name == nil || *to.Name == backendRef.Name) { - return true, nil + if toGK.Group == string(to.Group) && toGK.Kind == string(to.Kind) { + if to.Name == nil || *to.Name == "" { + // No name specified is treated as a wildcard within the namespace + return true, nil + } + + if gw.ObjectName(toName) == *to.Name { + // The ReferencePolicy specifically targets this object + return true, nil + } } } } - return false, err + // No ReferencePolicy was found which allows this cross-namespace reference + return false, nil } func toNamespaceSet(name string, labels map[string]string) klabels.Labels { @@ -327,10 +372,11 @@ func gatewayStatusEqual(a, b gw.GatewayStatus) bool { return true } -func getServiceID(name gw.ObjectName, namespace *gw.Namespace, parentNamespace string) string { - serviceID := fmt.Sprintf("%s/%s", name, parentNamespace) +// getNamespacedName returns types.NamespacedName defaulted to a parent +// namespace in the case where the provided namespace is nil. +func getNamespacedName(name gw.ObjectName, namespace *gw.Namespace, parentNamespace string) types.NamespacedName { if namespace != nil { - serviceID = fmt.Sprintf("%s/%s", name, *namespace) + return types.NamespacedName{Namespace: string(*namespace), Name: string(name)} } - return serviceID + return types.NamespacedName{Namespace: parentNamespace, Name: string(name)} } diff --git a/internal/k8s/reconciler/utils_test.go b/internal/k8s/reconciler/utils_test.go index df16d2cd5..a771cb31a 100644 --- a/internal/k8s/reconciler/utils_test.go +++ b/internal/k8s/reconciler/utils_test.go @@ -363,13 +363,122 @@ func TestGatewayStatusEqual(t *testing.T) { })) } +func TestGatewayAllowedForSecretRef(t *testing.T) { + type testCase struct { + name string + fromNS string + toNS *string + toKind *string + toName string + policyFromNS string + policyToName *string + allowed bool + } + + ns1, ns2, ns3 := "namespace1", "namespace2", "namespace3" + secret1, secret2, secret3 := "secret1", "secret2", "secret3" + + for _, tc := range []testCase{ + {name: "unspecified-secret-namespace-allowed", fromNS: ns1, toNS: nil, toName: secret1, policyFromNS: ns1, policyToName: nil, allowed: true}, + {name: "same-namespace-no-name-allowed", fromNS: ns1, toNS: &ns1, toName: secret1, policyFromNS: ns1, policyToName: nil, allowed: true}, + {name: "same-namespace-with-name-allowed", fromNS: ns1, toNS: &ns1, toName: secret1, policyFromNS: ns1, policyToName: &secret1, allowed: true}, + {name: "different-namespace-no-name-allowed", fromNS: ns1, toNS: &ns2, toName: secret2, policyFromNS: ns1, policyToName: nil, allowed: true}, + {name: "different-namespace-with-name-allowed", fromNS: ns1, toNS: &ns2, toName: secret2, policyFromNS: ns1, policyToName: &secret2, allowed: true}, + {name: "mismatched-policy-from-namespace-disallowed", fromNS: ns1, toNS: &ns2, toName: secret2, policyFromNS: ns3, policyToName: &secret2, allowed: false}, + {name: "mismatched-policy-to-name-disallowed", fromNS: ns1, toNS: &ns2, toName: secret2, policyFromNS: ns1, policyToName: &secret3, allowed: false}, + } { + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + + group := gw.Group("") + + secretRef := gw.SecretObjectReference{ + Group: &group, + Name: gw.ObjectName(tc.toName), + } + + if tc.toNS != nil { + ns := gw.Namespace(*tc.toNS) + secretRef.Namespace = &ns + } + + if tc.toKind != nil { + k := gw.Kind(*tc.toKind) + secretRef.Kind = &k + } + + gateway := &gw.Gateway{ + TypeMeta: meta.TypeMeta{APIVersion: "gateway.networking.k8s.io/v1alpha2", Kind: "Gateway"}, + ObjectMeta: meta.ObjectMeta{Namespace: tc.fromNS}, + Spec: gw.GatewaySpec{ + Listeners: []gw.Listener{{ + TLS: &gw.GatewayTLSConfig{ + CertificateRefs: []*gw.SecretObjectReference{{ + Group: &group, + Name: gw.ObjectName(tc.toName), + }}, + }, + }}, + }, + } + + var toName *gw.ObjectName + if tc.policyToName != nil { + on := gw.ObjectName(*tc.policyToName) + toName = &on + } + + if tc.toNS != nil && tc.fromNS != *tc.toNS { + otherName := gw.ObjectName("blah") + + refPolicies := []gw.ReferencePolicy{ + // Create a ReferencePolicy that does not match at all (kind, etc.) + { + ObjectMeta: meta.ObjectMeta{Namespace: *tc.toNS}, + Spec: gw.ReferencePolicySpec{ + From: []gw.ReferencePolicyFrom{{Group: "Kool & The Gang", Kind: "Jungle Boogie", Namespace: "Wild And Peaceful"}}, + To: []gw.ReferencePolicyTo{{Group: "does not exist", Kind: "does not exist", Name: nil}}, + }, + }, + // Create a ReferencePolicy that matches completely except for To.Name + { + ObjectMeta: meta.ObjectMeta{Namespace: *tc.toNS}, + Spec: gw.ReferencePolicySpec{ + From: []gw.ReferencePolicyFrom{{Group: "gateway.networking.k8s.io", Kind: gw.Kind("Gateway"), Namespace: gw.Namespace(tc.policyFromNS)}}, + To: []gw.ReferencePolicyTo{{Group: "", Kind: "Secret", Name: &otherName}}, + }, + }, + // Create a ReferencePolicy that matches completely + { + ObjectMeta: meta.ObjectMeta{Namespace: *tc.toNS}, + Spec: gw.ReferencePolicySpec{ + From: []gw.ReferencePolicyFrom{{Group: "gateway.networking.k8s.io", Kind: gw.Kind("Gateway"), Namespace: gw.Namespace(tc.policyFromNS)}}, + To: []gw.ReferencePolicyTo{{Group: "", Kind: "Secret", Name: toName}}, + }, + }, + } + + client.EXPECT(). + GetReferencePoliciesInNamespace(gomock.Any(), *tc.toNS). + Return(refPolicies, nil) + } + + allowed, err := gatewayAllowedForSecretRef(context.Background(), gateway, secretRef, client) + require.NoError(t, err) + assert.Equal(t, tc.allowed, allowed) + }) + } +} + func TestRouteAllowedForBackendRef(t *testing.T) { type testCase struct { name string - routeNS string - backendNS *string - backendKind *string - backendName string + fromNS string + toNS *string + toKind *string + toName string policyFromNS string policyToName *string allowed bool @@ -379,13 +488,13 @@ func TestRouteAllowedForBackendRef(t *testing.T) { backend1, backend2, backend3 := "backend1", "backend2", "backend3" for _, tc := range []testCase{ - {name: "unspecified-backend-namespace-allowed", routeNS: ns1, backendNS: nil, backendName: backend1, policyFromNS: ns1, policyToName: nil, allowed: true}, - {name: "same-namespace-no-name-allowed", routeNS: ns1, backendNS: &ns1, backendName: backend1, policyFromNS: ns1, policyToName: nil, allowed: true}, - {name: "same-namespace-with-name-allowed", routeNS: ns1, backendNS: &ns1, backendName: backend1, policyFromNS: ns1, policyToName: &backend1, allowed: true}, - {name: "different-namespace-no-name-allowed", routeNS: ns1, backendNS: &ns2, backendName: backend2, policyFromNS: ns1, policyToName: nil, allowed: true}, - {name: "different-namespace-with-name-allowed", routeNS: ns1, backendNS: &ns2, backendName: backend2, policyFromNS: ns1, policyToName: &backend2, allowed: true}, - {name: "mismatched-policy-from-namespace-disallowed", routeNS: ns1, backendNS: &ns2, backendName: backend2, policyFromNS: ns3, policyToName: &backend2, allowed: false}, - {name: "mismatched-policy-to-name-disallowed", routeNS: ns1, backendNS: &ns2, backendName: backend2, policyFromNS: ns1, policyToName: &backend3, allowed: false}, + {name: "unspecified-backend-namespace-allowed", fromNS: ns1, toNS: nil, toName: backend1, policyFromNS: ns1, policyToName: nil, allowed: true}, + {name: "same-namespace-no-name-allowed", fromNS: ns1, toNS: &ns1, toName: backend1, policyFromNS: ns1, policyToName: nil, allowed: true}, + {name: "same-namespace-with-name-allowed", fromNS: ns1, toNS: &ns1, toName: backend1, policyFromNS: ns1, policyToName: &backend1, allowed: true}, + {name: "different-namespace-no-name-allowed", fromNS: ns1, toNS: &ns2, toName: backend2, policyFromNS: ns1, policyToName: nil, allowed: true}, + {name: "different-namespace-with-name-allowed", fromNS: ns1, toNS: &ns2, toName: backend2, policyFromNS: ns1, policyToName: &backend2, allowed: true}, + {name: "mismatched-policy-from-namespace-disallowed", fromNS: ns1, toNS: &ns2, toName: backend2, policyFromNS: ns3, policyToName: &backend2, allowed: false}, + {name: "mismatched-policy-to-name-disallowed", fromNS: ns1, toNS: &ns2, toName: backend2, policyFromNS: ns1, policyToName: &backend3, allowed: false}, } { // Test each case for both HTTPRoute + TCPRoute which should function identically for _, routeType := range []string{"HTTPRoute", "TCPRoute"} { @@ -399,17 +508,17 @@ func TestRouteAllowedForBackendRef(t *testing.T) { backendRef := gw.BackendRef{ BackendObjectReference: gw.BackendObjectReference{ Group: &group, - Name: gw.ObjectName(tc.backendName), + Name: gw.ObjectName(tc.toName), }, } - if tc.backendNS != nil { - ns := gw.Namespace(*tc.backendNS) + if tc.toNS != nil { + ns := gw.Namespace(*tc.toNS) backendRef.BackendObjectReference.Namespace = &ns } - if tc.backendKind != nil { - k := gw.Kind(*tc.backendKind) + if tc.toKind != nil { + k := gw.Kind(*tc.toKind) backendRef.Kind = &k } @@ -417,7 +526,7 @@ func TestRouteAllowedForBackendRef(t *testing.T) { switch routeType { case "HTTPRoute": route = &gw.HTTPRoute{ - ObjectMeta: meta.ObjectMeta{Namespace: tc.routeNS}, + ObjectMeta: meta.ObjectMeta{Namespace: tc.fromNS}, TypeMeta: meta.TypeMeta{APIVersion: "gateway.networking.k8s.io/v1alpha2", Kind: "HTTPRoute"}, Spec: gw.HTTPRouteSpec{ Rules: []gw.HTTPRouteRule{{ @@ -427,7 +536,7 @@ func TestRouteAllowedForBackendRef(t *testing.T) { } case "TCPRoute": route = &gw.TCPRoute{ - ObjectMeta: meta.ObjectMeta{Namespace: tc.routeNS}, + ObjectMeta: meta.ObjectMeta{Namespace: tc.fromNS}, TypeMeta: meta.TypeMeta{APIVersion: "gateway.networking.k8s.io/v1alpha2", Kind: "TCPRoute"}, Spec: gw.TCPRouteSpec{ Rules: []gw.TCPRouteRule{{ @@ -445,10 +554,10 @@ func TestRouteAllowedForBackendRef(t *testing.T) { toName = &on } - if tc.backendNS != nil && tc.routeNS != *tc.backendNS { + if tc.toNS != nil && tc.fromNS != *tc.toNS { referencePolicy := gw.ReferencePolicy{ TypeMeta: meta.TypeMeta{}, - ObjectMeta: meta.ObjectMeta{Namespace: *tc.backendNS}, + ObjectMeta: meta.ObjectMeta{Namespace: *tc.toNS}, Spec: gw.ReferencePolicySpec{ From: []gw.ReferencePolicyFrom{{ Group: "gateway.networking.k8s.io", @@ -464,7 +573,7 @@ func TestRouteAllowedForBackendRef(t *testing.T) { } throwawayPolicy := gw.ReferencePolicy{ - ObjectMeta: meta.ObjectMeta{Namespace: *tc.backendNS}, + ObjectMeta: meta.ObjectMeta{Namespace: *tc.toNS}, Spec: gw.ReferencePolicySpec{ From: []gw.ReferencePolicyFrom{{ Group: "Kool & The Gang", @@ -480,7 +589,7 @@ func TestRouteAllowedForBackendRef(t *testing.T) { } client.EXPECT(). - GetReferencePoliciesInNamespace(gomock.Any(), *tc.backendNS). + GetReferencePoliciesInNamespace(gomock.Any(), *tc.toNS). Return([]gw.ReferencePolicy{throwawayPolicy, referencePolicy}, nil) } diff --git a/internal/k8s/reconciler/zz_generated_errors.go b/internal/k8s/reconciler/zz_generated_errors.go index c0e15c529..55ec1576b 100644 --- a/internal/k8s/reconciler/zz_generated_errors.go +++ b/internal/k8s/reconciler/zz_generated_errors.go @@ -6,6 +6,7 @@ type CertificateResolutionErrorType int const ( CertificateResolutionErrorTypeNotFound CertificateResolutionErrorType = iota + CertificateResolutionErrorTypeNotPermitted CertificateResolutionErrorTypeUnsupported ) @@ -17,6 +18,9 @@ type CertificateResolutionError struct { func NewCertificateResolutionErrorNotFound(inner string) CertificateResolutionError { return CertificateResolutionError{inner, CertificateResolutionErrorTypeNotFound} } +func NewCertificateResolutionErrorNotPermitted(inner string) CertificateResolutionError { + return CertificateResolutionError{inner, CertificateResolutionErrorTypeNotPermitted} +} func NewCertificateResolutionErrorUnsupported(inner string) CertificateResolutionError { return CertificateResolutionError{inner, CertificateResolutionErrorTypeUnsupported} } diff --git a/internal/k8s/reconciler/zz_generated_errors_test.go b/internal/k8s/reconciler/zz_generated_errors_test.go index 0ef99a5df..b27d826f4 100644 --- a/internal/k8s/reconciler/zz_generated_errors_test.go +++ b/internal/k8s/reconciler/zz_generated_errors_test.go @@ -15,6 +15,8 @@ func TestCertificateResolutionErrorType(t *testing.T) { require.Equal(t, expected, NewCertificateResolutionErrorNotFound(expected).Error()) require.Equal(t, CertificateResolutionErrorTypeNotFound, NewCertificateResolutionErrorNotFound(expected).Kind()) + require.Equal(t, expected, NewCertificateResolutionErrorNotPermitted(expected).Error()) + require.Equal(t, CertificateResolutionErrorTypeNotPermitted, NewCertificateResolutionErrorNotPermitted(expected).Kind()) require.Equal(t, expected, NewCertificateResolutionErrorUnsupported(expected).Error()) require.Equal(t, CertificateResolutionErrorTypeUnsupported, NewCertificateResolutionErrorUnsupported(expected).Kind()) }