Skip to content
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
180 changes: 180 additions & 0 deletions internal/extension/registry/extension_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"math"
"net"
Expand All @@ -17,6 +18,7 @@ import (
"sync"
"testing"

clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
Expand All @@ -26,12 +28,14 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/utils/ptr"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/internal/envoygateway"
"github.com/envoyproxy/gateway/internal/ir"
"github.com/envoyproxy/gateway/proto/extension"
)

Expand Down Expand Up @@ -546,3 +550,179 @@ func Test_Integration_RetryPolicy_MaxAttempts(t *testing.T) {
})
}
}

type clusterUpdateTestServer struct {
extension.UnimplementedEnvoyGatewayExtensionServer
}

func getTargetRefKind(obj *unstructured.Unstructured) (string, error) {
targetRef, found, err := unstructured.NestedMap(obj.Object, "spec", "targetRef")
if err != nil || !found {
return "", errors.New("targetRef not found or error")
}

kind, ok := targetRef["kind"].(string)
if !ok {
return "", errors.New("kind is not a string or missing in targetRef")
}

return kind, nil
}

func (s *clusterUpdateTestServer) PostTranslateModify(ctx context.Context, req *extension.PostTranslateModifyRequest) (*extension.PostTranslateModifyResponse, error) {
clusters := req.GetClusters()
if clusters == nil {
return &extension.PostTranslateModifyResponse{
Clusters: clusters,
Secrets: req.GetSecrets(),
}, errors.New("No clusters found")
}

if len(req.PostTranslateContext.ExtensionResources) == 0 {
return &extension.PostTranslateModifyResponse{
Clusters: clusters,
Secrets: req.GetSecrets(),
}, errors.New("No policy found")
}

for _, extensionResourceBytes := range req.PostTranslateContext.ExtensionResources {
extensionResource := unstructured.Unstructured{}
if err := extensionResource.UnmarshalJSON(extensionResourceBytes.UnstructuredBytes); err != nil {
return &extension.PostTranslateModifyResponse{
Clusters: clusters,
Secrets: req.GetSecrets(),
}, err
}

targetKind, err := getTargetRefKind(&extensionResource)
if err != nil || extensionResource.GetObjectKind().GroupVersionKind().Kind != "ExampleExtPolicy" || targetKind != "Gateway" {
return &extension.PostTranslateModifyResponse{
Clusters: clusters,
Secrets: req.GetSecrets(),
}, errors.New("No matching policy found")
}
}

ret := &extension.PostTranslateModifyResponse{
Clusters: clusters,
Secrets: req.GetSecrets(),
}

return ret, nil
}

func Test_Integration_ClusterUpdateExtensionServer(t *testing.T) {
testCases := []struct {
name string
extensionPolicies []*ir.UnstructuredRef
errorExpected bool
}{
{
name: "valid extension policy with targetRef",
extensionPolicies: []*ir.UnstructuredRef{
{
Object: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "gateway.example.io/v1",
"kind": "ExampleExtPolicy",
"metadata": map[string]any{
"name": "test",
"namespace": "test",
},
"spec": map[string]any{
"targetRef": map[string]any{
"group": "gateway.networking.k8s.io",
"kind": "Gateway",
"name": "test",
},
"data": "some data",
},
},
},
},
},
errorExpected: false,
},

{
name: "invalid extension policy - no target",
extensionPolicies: []*ir.UnstructuredRef{
{
Object: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "gateway.example.io/v1alpha1",
"kind": "ExampleExtPolicy",
"metadata": map[string]any{
"name": "test",
"namespace": "test",
},
"spec": map[string]any{
"data": "some data",
},
},
},
},
},
errorExpected: true,
},
{
name: "invalid extension policy - no spec",
extensionPolicies: []*ir.UnstructuredRef{
{
Object: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "gateway.example.io/v1alpha1",
"kind": "ExampleExtPolicy",
"metadata": map[string]any{
"name": "test",
"namespace": "test",
},
},
},
},
},
errorExpected: true,
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
extManager := egv1a1.ExtensionManager{
Hooks: &egv1a1.ExtensionHooks{
XDSTranslator: &egv1a1.XDSTranslatorHooks{
Post: []egv1a1.XDSTranslatorHook{
egv1a1.XDSTranslation,
},
},
},
Service: &egv1a1.ExtensionService{
BackendEndpoint: egv1a1.BackendEndpoint{
FQDN: &egv1a1.FQDNEndpoint{
Hostname: "example.foo",
Port: 44344,
},
},
},
}

mgr, _, err := NewInMemoryManager(extManager, &clusterUpdateTestServer{})
require.NoError(t, err)

hook, err := mgr.GetPostXDSHookClient(egv1a1.XDSTranslation)
require.NoError(t, err)
require.NotNil(t, hook)

_, _, err = hook.PostTranslateModifyHook(
[]*clusterv3.Cluster{
{
Name: "test-cluster",
},
}, nil, tt.extensionPolicies)

if (err != nil) != tt.errorExpected {
t.Errorf("PostRouteModifyHook() error = %v, errorExpected %v", err, tt.errorExpected)
return
}
})
}
}
22 changes: 18 additions & 4 deletions internal/extension/registry/xds_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/envoyproxy/gateway/internal/extension/types"
"github.com/envoyproxy/gateway/internal/ir"
"github.com/envoyproxy/gateway/proto/extension"
)

Expand Down Expand Up @@ -105,14 +106,27 @@
return resp.Listener, nil
}

func (h *XDSHook) PostTranslateModifyHook(clusters []*cluster.Cluster, secrets []*tls.Secret) ([]*cluster.Cluster, []*tls.Secret, error) {
func (h *XDSHook) PostTranslateModifyHook(clusters []*cluster.Cluster, secrets []*tls.Secret, extensionPolicies []*ir.UnstructuredRef) ([]*cluster.Cluster, []*tls.Secret, error) {
// Make the request to the extension server
// Take all of the unstructured resources for the extension and package them into bytes
unstructuredPolicies := make([]*unstructured.Unstructured, len(extensionPolicies))
for i, policy := range extensionPolicies {
unstructuredPolicies[i] = policy.Object
}
// Convert the unstructured policies to bytes
extensionPoliciesBytes, err := translateUnstructuredToUnstructuredBytes(unstructuredPolicies)
if err != nil {
return nil, nil, err
}

Check warning on line 120 in internal/extension/registry/xds_hook.go

View check run for this annotation

Codecov / codecov/patch

internal/extension/registry/xds_hook.go#L119-L120

Added lines #L119 - L120 were not covered by tests

ctx := context.Background()
resp, err := h.grpcClient.PostTranslateModify(ctx,
&extension.PostTranslateModifyRequest{
PostTranslateContext: &extension.PostTranslateExtensionContext{},
Clusters: clusters,
Secrets: secrets,
PostTranslateContext: &extension.PostTranslateExtensionContext{
ExtensionResources: extensionPoliciesBytes,
},
Clusters: clusters,
Secrets: secrets,
})
if err != nil {
return nil, nil, err
Expand Down
4 changes: 3 additions & 1 deletion internal/extension/types/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/envoyproxy/gateway/internal/ir"
)

type XDSHookClient interface {
Expand Down Expand Up @@ -41,5 +43,5 @@ type XDSHookClient interface {
// An example of how this may be used is to inject a cluster that will be used by an ext_authz http filter created by the extension.
// The list of clusters and secrets returned by the extension are used as the final list of all clusters and secrets
// PostTranslateModifyHook is always executed when an extension is loaded
PostTranslateModifyHook([]*cluster.Cluster, []*tls.Secret) ([]*cluster.Cluster, []*tls.Secret, error)
PostTranslateModifyHook([]*cluster.Cluster, []*tls.Secret, []*ir.UnstructuredRef) ([]*cluster.Cluster, []*tls.Secret, error)
}
9 changes: 8 additions & 1 deletion internal/gatewayapi/extensionserverpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst
var errs error
// Process the policies targeting Gateways. Only update the policy status if it was accepted.
// A policy is considered accepted if at least one targetRef contained inside matched a listener.
for _, policy := range policies {
for policyIndex, policy := range policies {
policy := policy.DeepCopy()
var policyStatus gwapiv1a2.PolicyStatus
accepted := false
Expand All @@ -68,6 +68,13 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst
continue
}

// Append policy extension server policy list for related gateway.
gatewayKey := t.getIRKey(gateway.Gateway)
unstructuredPolicy := &ir.UnstructuredRef{
Object: &policies[policyIndex],
}
xdsIR[gatewayKey].ExtensionServerPolicies = append(xdsIR[gatewayKey].ExtensionServerPolicies, unstructuredPolicy)

// Set conditions for translation if it got any
if t.translateExtServerPolicyForGateway(policy, gateway, currTarget, xdsIR) {
// Set Accepted condition if it is unset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,32 @@ xdsIR:
accessLog:
json:
- path: /dev/stdout
extensionServerPolicies:
- object:
apiVersion: foo.example.io/v1alpha1
kind: Bar
metadata:
name: test1
namespace: envoy-gateway
spec:
data: attached to all listeners
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
- object:
apiVersion: foo.example.io/v1alpha1
kind: Bar
metadata:
name: test2
namespace: envoy-gateway
spec:
data: attached only to listener on port 80
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
sectionName: tcp1
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,32 @@ xdsIR:
accessLog:
json:
- path: /dev/stdout
extensionServerPolicies:
- object:
apiVersion: foo.example.io/v1alpha1
kind: Bar
metadata:
name: test1
namespace: envoy-gateway
spec:
data: attached to all listeners
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
- object:
apiVersion: foo.example.io/v1alpha1
kind: Bar
metadata:
name: test2
namespace: envoy-gateway
spec:
data: attached only to listener on port 162
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
sectionName: udp1
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Loading