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

feat: Implementation of API Key Auth #125

Merged
merged 70 commits into from
Jan 21, 2025
Merged
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
8dcdc3a
llmroute to append secret file to extproc deployment for backend secu…
aabchoo Jan 13, 2025
d7b4bed
Merge branch 'main' into aaron/apikey-auth
aabchoo Jan 15, 2025
6dc6415
update auth in filter config for api key
aabchoo Jan 16, 2025
b1a5da4
add backendsecuritypolicy and move extproc deployment to sink
aabchoo Jan 16, 2025
6fbdd05
mount secret file based on backend + backendsecuritypolicies
aabchoo Jan 16, 2025
2d21435
add api key as header
aabchoo Jan 16, 2025
55bd150
add calls to sync extproc deployment
aabchoo Jan 16, 2025
8fe3dd6
Merge remote-tracking branch 'origin/main' into aaron/apikey-auth
aabchoo Jan 16, 2025
9845736
add back accidental change
aabchoo Jan 16, 2025
5456c70
update PR to use indexer func instead of internal cache
aabchoo Jan 17, 2025
15adf8a
Merge remote-tracking branch 'origin/main' into aaron/apikey-auth
aabchoo Jan 17, 2025
f09e082
mount secret file onto extproc deployment
aabchoo Jan 17, 2025
082da92
account for duplicates, already mounted files and files to unmount
aabchoo Jan 17, 2025
40e38d6
add testing for backend security policy controller
aabchoo Jan 17, 2025
40ab582
remove extproc deployment tests
aabchoo Jan 17, 2025
f543c7b
update tests
aabchoo Jan 17, 2025
481647d
create new sink
aabchoo Jan 17, 2025
0136281
export config sink
aabchoo Jan 17, 2025
5ddd4ff
fix tests
aabchoo Jan 17, 2025
2ee446c
fix api key tests
aabchoo Jan 17, 2025
4acddb2
add tests for mount backend security policy
aabchoo Jan 17, 2025
8c65660
add extproc deployment tests
aabchoo Jan 17, 2025
fdffb47
attempt to add api key as part of e2e test
aabchoo Jan 17, 2025
4869fce
Merge remote-tracking branch 'origin/main' into aaron/apikey-auth
aabchoo Jan 17, 2025
f789899
fixing up references
aabchoo Jan 17, 2025
9523364
fixing up tests
aabchoo Jan 17, 2025
68e1d01
update e2e tests
aabchoo Jan 17, 2025
c10d861
add perm to write file
aabchoo Jan 17, 2025
a8eb88d
fix return typing
aabchoo Jan 17, 2025
83e5910
update perm to allow read access
aabchoo Jan 17, 2025
835637a
write file directly for tests
aabchoo Jan 17, 2025
8bfe8fa
remove unnecessary remove
aabchoo Jan 17, 2025
bd44bda
testing file changes
aabchoo Jan 17, 2025
bb3f085
remove header request header to add
aabchoo Jan 17, 2025
4e22818
remove yaml text replace
aabchoo Jan 17, 2025
dd09262
add back def
aabchoo Jan 17, 2025
9e1297e
ci: fixes check out issue in test_e2e with fork (#123)
mathetake Jan 17, 2025
d90c535
Merge remote-tracking branch 'origin/main' into aaron/apikey-auth
aabchoo Jan 17, 2025
93256da
update string needed to be replaced
aabchoo Jan 17, 2025
87b9eb9
update docs and add back code
aabchoo Jan 17, 2025
092aa94
remove sink + deployment check from aiGatewayRoute controller tests
aabchoo Jan 17, 2025
09be4ab
add defaultExtProcImagePullPolicy
aabchoo Jan 17, 2025
6364bae
Update internal/controller/sink.go
aabchoo Jan 19, 2025
e6fd3a4
Update filterconfig/filterconfig.go
aabchoo Jan 19, 2025
4d0704e
Update filterconfig/filterconfig.go
aabchoo Jan 19, 2025
99b5f0d
Update filterconfig/filterconfig.go
aabchoo Jan 19, 2025
41ce7f0
Update internal/extproc/backendauth/api_key.go
aabchoo Jan 20, 2025
e14857c
Update tests/extproc/extproc_test.go
aabchoo Jan 20, 2025
8a19ce3
stop exporting sink and related fields
aabchoo Jan 19, 2025
3145391
add back streaming tests
aabchoo Jan 20, 2025
cf6fac4
variable for spec.containers[0]
aabchoo Jan 20, 2025
be53642
Update internal/controller/sink_test.go
aabchoo Jan 20, 2025
95ad130
indexed func testing
aabchoo Jan 20, 2025
451bfdc
Merge remote-tracking branch 'origin/main' into aaron/auth-apikey
aabchoo Jan 20, 2025
67ac960
Update filterconfig/filterconfig.go
aabchoo Jan 21, 2025
2fbc860
Merge remote-tracking branch 'origin/main' into aaron/auth-apikey
aabchoo Jan 21, 2025
ba790b7
remove unused test
aabchoo Jan 21, 2025
fa08f56
update router logger name
aabchoo Jan 21, 2025
b5e9b08
fixing tests
aabchoo Jan 21, 2025
e13393a
fixing tests
aabchoo Jan 21, 2025
420f212
fix code style
aabchoo Jan 21, 2025
6ac681e
fixing testing
aabchoo Jan 21, 2025
078c92b
code style
aabchoo Jan 21, 2025
514169d
revert t use ai-eg-route-extproc
aabchoo Jan 21, 2025
d122a62
add kind
aabchoo Jan 21, 2025
03d929b
check if values are assigned internally
aabchoo Jan 21, 2025
e08898d
Update internal/controller/ai_service_backend_test.go
aabchoo Jan 21, 2025
3f131ea
Update internal/controller/ai_service_backend_test.go
aabchoo Jan 21, 2025
e2e95cb
Update internal/controller/sink.go
aabchoo Jan 21, 2025
1d151de
Update tests/extproc/extproc_test.go
aabchoo Jan 21, 2025
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
Prev Previous commit
Next Next commit
update tests
Signed-off-by: Aaron Choo <achoo30@bloomberg.net>
aabchoo committed Jan 17, 2025
commit f543c7b1f292308655596b33dd0f4cbac4c5f35c
1 change: 1 addition & 0 deletions internal/controller/ai_gateway_route.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package controller
import (
"context"
"fmt"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
6 changes: 4 additions & 2 deletions internal/controller/backend_security_policy.go
Original file line number Diff line number Diff line change
@@ -2,12 +2,14 @@ package controller

import (
"context"
"github.com/envoyproxy/ai-gateway/api/v1alpha1"

"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1"
)

// TODO-AC: rewrite
@@ -34,7 +36,7 @@ func newBackendSecurityPolicyController(client client.Client, kube kubernetes.In

// Reconcile implements the [reconcile.TypedReconciler] for [aigv1a1.BackendSecurityPolicy].
func (b backendSecurityPolicyController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var backendSecurityPolicy v1alpha1.BackendSecurityPolicy
var backendSecurityPolicy aigv1a1.BackendSecurityPolicy
if err := b.client.Get(ctx, req.NamespacedName, &backendSecurityPolicy); err != nil {
if errors.IsNotFound(err) {
ctrl.Log.Info("Deleting Backend Security Policy",
6 changes: 4 additions & 2 deletions internal/controller/backend_security_policy_test.go
Original file line number Diff line number Diff line change
@@ -2,15 +2,17 @@ package controller

import (
"context"
aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1"
"testing"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
fake2 "k8s.io/client-go/kubernetes/fake"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"testing"

aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1"
)

func TestBackendSecurityController_Reconcile(t *testing.T) {
14 changes: 10 additions & 4 deletions internal/controller/sink.go
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@ package controller
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"

"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/utils/ptr"
@@ -23,7 +23,7 @@ const selectedBackendHeaderKey = "x-envoy-ai-gateway-selected-backend"
// MountedExtProcSecretPath specifies the secret file mounted on the external proc. The idea is to update the mounted
//
// secret with backendSecurityPolicy auth instead of mounting new secret files to the external proc.
const MountedExtProcSecretPath = "/etc/backend_security_policy"
const MountedExtProcSecretPath = "/etc/backend_security_policy" // #nosec G101

// ConfigSinkEvent is the interface for the events that the configSink can handle.
// It can be either an AIServiceBackend, an AIGatewayRoute, or a deletion event.
@@ -156,6 +156,13 @@ func (c *configSink) syncAIGatewayRoute(aiGatewayRoute *aigv1a1.AIGatewayRoute)
c.logger.Error(err, "failed to update extproc configmap", "namespace", aiGatewayRoute.Namespace, "name", aiGatewayRoute.Name)
return
}

// Deploy extproc deployment with potential updates.
err = c.syncExtProcDeployment(context.Background(), aiGatewayRoute)
if err != nil {
c.logger.Error(err, "failed to deploy ext proc", "namespace", aiGatewayRoute.Namespace, "name", aiGatewayRoute.Name)
return
}
}

func (c *configSink) syncAIServiceBackend(aiBackend *aigv1a1.AIServiceBackend) {
@@ -221,7 +228,6 @@ func (c *configSink) updateExtProcConfigMap(aiGatewayRoute *aigv1a1.AIGatewayRou
if bspRef := backendObj.Spec.BackendSecurityPolicyRef; bspRef != nil {
bspKey := fmt.Sprintf("%s.%s", bspRef.Name, aiGatewayRoute.Namespace)
backendSecurityPolicy, err := c.backendSecurityPolicy(aiGatewayRoute.Namespace, string(bspRef.Name))

if err != nil {
return fmt.Errorf("failed to get BackendSecurityPolicy %s: %w", bspRef.Name, err)
}
111 changes: 99 additions & 12 deletions internal/controller/sink_test.go
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ func TestConfigSink_init(t *testing.T) {
kube := fake2.NewClientset()

eventChan := make(chan ConfigSinkEvent)
s := newConfigSink(fakeClient, kube, logr.Discard(), eventChan)
s := newConfigSink(fakeClient, kube, logr.Discard(), eventChan, "defaultExtProcImage")
require.NotNil(t, s)
}

@@ -36,7 +36,7 @@ func TestConfigSink_syncAIGatewayRoute(t *testing.T) {
kube := fake2.NewClientset()

eventChan := make(chan ConfigSinkEvent, 10)
s := newConfigSink(fakeClient, kube, logr.FromSlogHandler(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{})), eventChan)
s := newConfigSink(fakeClient, kube, logr.FromSlogHandler(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{})), eventChan, "defaultExtProcImage")
require.NotNil(t, s)

for _, backend := range []*aigv1a1.AIServiceBackend{
@@ -100,14 +100,21 @@ func TestConfigSink_syncAIGatewayRoute(t *testing.T) {
func TestConfigSink_syncAIServiceBackend(t *testing.T) {
eventChan := make(chan ConfigSinkEvent)
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
s := newConfigSink(fakeClient, nil, logr.Discard(), eventChan)
s := newConfigSink(fakeClient, nil, logr.Discard(), eventChan, "defaultExtProcImage")
s.syncAIServiceBackend(&aigv1a1.AIServiceBackend{ObjectMeta: metav1.ObjectMeta{Name: "apple", Namespace: "ns1"}})
}

func TestConfigSink_syncBackendSecurityPolicy(t *testing.T) {
eventChan := make(chan ConfigSinkEvent)
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
s := newConfigSink(fakeClient, nil, logr.Discard(), eventChan, "defaultExtProcImage")
s.syncBackendSecurityPolicy(&aigv1a1.BackendSecurityPolicy{ObjectMeta: metav1.ObjectMeta{Name: "apple", Namespace: "ns1"}})
}

func Test_newHTTPRoute(t *testing.T) {
eventChan := make(chan ConfigSinkEvent)
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
s := newConfigSink(fakeClient, nil, logr.Discard(), eventChan)
s := newConfigSink(fakeClient, nil, logr.Discard(), eventChan, "defaultExtProcImage")
httpRoute := &gwapiv1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{Name: "route1", Namespace: "ns1"},
Spec: gwapiv1.HTTPRouteSpec{},
@@ -210,21 +217,41 @@ func Test_updateExtProcConfigMap(t *testing.T) {
kube := fake2.NewClientset()

eventChan := make(chan ConfigSinkEvent)
s := newConfigSink(fakeClient, kube, logr.Discard(), eventChan)
s := newConfigSink(fakeClient, kube, logr.Discard(), eventChan, "defaultExtProcImage")
err := fakeClient.Create(context.Background(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "some-secret-policy"}})
require.NoError(t, err)

for _, bsp := range []*aigv1a1.BackendSecurityPolicy{
{
ObjectMeta: metav1.ObjectMeta{Name: "some-backend-security-policy-1", Namespace: "ns"},
Spec: aigv1a1.BackendSecurityPolicySpec{
Type: aigv1a1.BackendSecurityPolicyTypeAPIKey,
APIKey: &aigv1a1.BackendSecurityPolicyAPIKey{
SecretRef: &gwapiv1.SecretObjectReference{Name: "some-secret-policy", Namespace: ptr.To[gwapiv1.Namespace]("ns")},
},
},
},
} {
err := fakeClient.Create(context.Background(), bsp, &client.CreateOptions{})
require.NoError(t, err)
}

for _, b := range []*aigv1a1.AIServiceBackend{
{
ObjectMeta: metav1.ObjectMeta{Name: "apple", Namespace: "ns"},
Spec: aigv1a1.AIServiceBackendSpec{
APISchema: aigv1a1.VersionedAPISchema{
Schema: aigv1a1.APISchemaAWSBedrock,
},
BackendRef: gwapiv1.BackendObjectReference{Name: "some-backend1", Namespace: ptr.To[gwapiv1.Namespace]("ns")},
BackendRef: gwapiv1.BackendObjectReference{Name: "some-backend1", Namespace: ptr.To[gwapiv1.Namespace]("ns")},
BackendSecurityPolicyRef: &gwapiv1.LocalObjectReference{Name: "some-backend-security-policy-1"},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "cat", Namespace: "ns"},
Spec: aigv1a1.AIServiceBackendSpec{
BackendRef: gwapiv1.BackendObjectReference{Name: "some-backend2", Namespace: ptr.To[gwapiv1.Namespace]("ns")},
BackendRef: gwapiv1.BackendObjectReference{Name: "some-backend2", Namespace: ptr.To[gwapiv1.Namespace]("ns")},
BackendSecurityPolicyRef: &gwapiv1.LocalObjectReference{Name: "some-backend-security-policy-1"},
},
},
{
@@ -276,13 +303,24 @@ func Test_updateExtProcConfigMap(t *testing.T) {
Rules: []filterconfig.RouteRule{
{
Backends: []filterconfig.Backend{
{Name: "apple.ns", Weight: 1, OutputSchema: filterconfig.VersionedAPISchema{Schema: filterconfig.APISchemaAWSBedrock}}, {Name: "pineapple.ns", Weight: 2},
{Name: "apple.ns", Weight: 1, OutputSchema: filterconfig.VersionedAPISchema{Schema: filterconfig.APISchemaAWSBedrock}, Auth: &filterconfig.BackendAuth{
Type: filterconfig.AuthTypeAPIKey,
APIKey: &filterconfig.APIKeyAuth{
Filename: "/etc/backend_security_policy/some-backend-security-policy-1.ns",
},
}},
{Name: "pineapple.ns", Weight: 2},
},
Headers: []filterconfig.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "some-ai"}},
},
{
Backends: []filterconfig.Backend{{Name: "cat.ns", Weight: 1}},
Headers: []filterconfig.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "another-ai"}},
Backends: []filterconfig.Backend{{Name: "cat.ns", Weight: 1, Auth: &filterconfig.BackendAuth{
Type: filterconfig.AuthTypeAPIKey,
APIKey: &filterconfig.APIKeyAuth{
Filename: "/etc/backend_security_policy/some-backend-security-policy-1.ns",
},
}}},
Headers: []filterconfig.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "another-ai"}},
},
},
},
@@ -309,6 +347,55 @@ func Test_updateExtProcConfigMap(t *testing.T) {
}
}

// Add test for checking mounted security policy on new backend security policy
// TODO-AC: Finish this test case
// func TestConfigSink_SyncExtProcDeployment(t *testing.T) {
// fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
// kube := fake2.NewClientset()
// eventChan := make(chan ConfigSinkEvent)
//
// ownerRef := []metav1.OwnerReference{{APIVersion: "v1", Kind: "Kind", Name: "Name"}}
// s := newConfigSink(fakeClient, kube, logr.Discard(), eventChan, "defaultExtProcImage")
// require.NotNil(t, s)
//
// // TODO-AC: add backends
// aiGatewayRoute := &aigv1a1.AIGatewayRoute{
// ObjectMeta: metav1.ObjectMeta{Name: "myroute", Namespace: "default"},
// Spec: aigv1a1.AIGatewayRouteSpec{
// FilterConfig: &aigv1a1.AIGatewayFilterConfig{
// Type: aigv1a1.AIGatewayFilterConfigTypeExternalProcess,
// ExternalProcess: &aigv1a1.AIGatewayFilterConfigExternalProcess{
// Replicas: ptr.To[int32](123),
// Resources: &corev1.ResourceRequirements{
// Limits: corev1.ResourceList{
// corev1.ResourceCPU: resource.MustParse("200m"),
// corev1.ResourceMemory: resource.MustParse("100Mi"),
// },
// },
// },
// },
// },
// }
//
// // Create the deployment
//
// // Check
// deployment, err := s.kube.AppsV1().Deployments("default").Get(context.Background(), extProcName(aiGatewayRoute), metav1.GetOptions{})
// require.NoError(t, err)
// require.Equal(t, extProcName(aiGatewayRoute), deployment.Name)
// require.Equal(t, int32(123), *deployment.Spec.Replicas)
// require.Equal(t, ownerRef, deployment.OwnerReferences)
// require.Equal(t, corev1.ResourceRequirements{
// Limits: corev1.ResourceList{
// corev1.ResourceCPU: resource.MustParse("200m"),
// corev1.ResourceMemory: resource.MustParse("100Mi"),
// },
// }, deployment.Spec.Template.Spec.Containers[0].Resources)
// service, err := s.kube.CoreV1().Services("default").Get(context.Background(), extProcName(aiGatewayRoute), metav1.GetOptions{})
// require.NoError(t, err)
// require.Equal(t, extProcName(aiGatewayRoute), service.Name)
//}

// Add test for new security policy needed to be mounted/unmounted
func Test_GetBackendSecurityMountPath(t *testing.T) {
mountPath := getBackendSecurityMountPath("policyName")
require.Equal(t, "/etc/backend_security_policy/policyName", mountPath)
}
6 changes: 4 additions & 2 deletions internal/extproc/backendauth/api_key.go
Original file line number Diff line number Diff line change
@@ -2,10 +2,12 @@ package backendauth

import (
"fmt"
"github.com/envoyproxy/ai-gateway/filterconfig"
"os"

corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
"os"

"github.com/envoyproxy/ai-gateway/filterconfig"
)

// apiKeyHandler implements [Handler] for api key authz.
6 changes: 1 addition & 5 deletions tests/controller/controller_test.go
Original file line number Diff line number Diff line change
@@ -219,12 +219,8 @@ func TestStartControllers(t *testing.T) {

func TestAIGatewayRouteController(t *testing.T) {
c, cfg, k := tests.NewEnvTest(t)
opts := controller.Options{
ExtProcImage: "envoyproxy/ai-gateway-extproc:foo",
EnableLeaderElection: false,
}
ch := make(chan controller.ConfigSinkEvent)
rc := controller.NewAIGatewayRouteController(c, k, logr.Discard(), opts, ch)
rc := controller.NewAIGatewayRouteController(c, k, logr.Discard(), ch)

opt := ctrl.Options{Scheme: c.Scheme(), LeaderElection: false, Controller: config.Controller{SkipNameValidation: ptr.To(true)}}
mgr, err := ctrl.NewManager(cfg, opt)