Skip to content

Commit bb76dc3

Browse files
authored
moves authz-configmap-ref e2e test to integration test (#2647)
* ports authz-configmap-ref e2e test to int test we want to move as much e2e tests into integration tests as possible where resource creation and assertoin is concerned. this PR moves the authz configmap e2e test into an integration test and then verifies that the runconfig has the correct authz config. we also add an assertion to ensure teh proxyrunner deployment has the runconfig volume and volume mount Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> * removes redundant authz-configmap-ref e2e test Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> * lint Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com> --------- Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com>
1 parent 841f61c commit bb76dc3

File tree

6 files changed

+181
-212
lines changed

6 files changed

+181
-212
lines changed

cmd/thv-operator/test-integration/mcp-server/mcpserver_controller_integration_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var _ = Describe("MCPServer Controller Integration Tests", func() {
2727
interval = time.Millisecond * 250
2828
defaultNamespace = "default"
2929
conditionTypeGroupRefValidated = "GroupRefValidated"
30+
runconfigVolumeName = "runconfig"
3031
)
3132

3233
Context("When creating an Stdio MCPServer", Ordered, func() {
@@ -148,7 +149,7 @@ var _ = Describe("MCPServer Controller Integration Tests", func() {
148149

149150
foundRunconfigVolume := false
150151
for _, v := range templateSpec.Volumes {
151-
if v.Name == "runconfig" && v.ConfigMap != nil && v.ConfigMap.Name == (mcpServerName+"-runconfig") {
152+
if v.Name == runconfigVolumeName && v.ConfigMap != nil && v.ConfigMap.Name == (mcpServerName+"-runconfig") {
152153
foundRunconfigVolume = true
153154
break
154155
}
@@ -160,7 +161,7 @@ var _ = Describe("MCPServer Controller Integration Tests", func() {
160161
// Verify that the runconfig ConfigMap is mounted as a volume
161162
foundRunconfigMount := false
162163
for _, vm := range container.VolumeMounts {
163-
if vm.Name == "runconfig" && vm.MountPath == "/etc/runconfig" {
164+
if vm.Name == runconfigVolumeName && vm.MountPath == "/etc/runconfig" {
164165
foundRunconfigMount = true
165166
break
166167
}

cmd/thv-operator/test-integration/mcp-server/mcpserver_runconfig_integration_test.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
. "github.com/onsi/ginkgo/v2"
1010
. "github.com/onsi/gomega"
11+
appsv1 "k8s.io/api/apps/v1"
1112
corev1 "k8s.io/api/core/v1"
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/apimachinery/pkg/types"
@@ -182,6 +183,56 @@ var _ = Describe("RunConfig ConfigMap Integration Tests", func() {
182183
Expect(runConfig.SchemaVersion).To(Equal(runner.CurrentSchemaVersion))
183184
})
184185

186+
It("Should create deployment with RunConfig volume mounts", func() {
187+
// Wait for the deployment to be created
188+
deployment := &appsv1.Deployment{}
189+
Eventually(func() error {
190+
return k8sClient.Get(ctx, types.NamespacedName{
191+
Name: mcpServerName,
192+
Namespace: namespace,
193+
}, deployment)
194+
}, timeout, interval).Should(Succeed())
195+
196+
// Verify the deployment has the correct volume
197+
var runconfigVolume *corev1.Volume
198+
for i := range deployment.Spec.Template.Spec.Volumes {
199+
vol := &deployment.Spec.Template.Spec.Volumes[i]
200+
if vol.Name == "runconfig" {
201+
runconfigVolume = vol
202+
break
203+
}
204+
}
205+
Expect(runconfigVolume).NotTo(BeNil(), "RunConfig volume should exist in deployment")
206+
207+
// Verify the volume references the correct ConfigMap
208+
Expect(runconfigVolume.ConfigMap).NotTo(BeNil())
209+
Expect(runconfigVolume.ConfigMap.LocalObjectReference.Name).To(Equal(configMapName))
210+
211+
// Find the toolhive container
212+
var toolhiveContainer *corev1.Container
213+
for i := range deployment.Spec.Template.Spec.Containers {
214+
container := &deployment.Spec.Template.Spec.Containers[i]
215+
if container.Name == "toolhive" {
216+
toolhiveContainer = container
217+
break
218+
}
219+
}
220+
Expect(toolhiveContainer).NotTo(BeNil(), "Toolhive container should exist")
221+
222+
// Verify the volume mount exists in the toolhive container
223+
var runconfigMount *corev1.VolumeMount
224+
for i := range toolhiveContainer.VolumeMounts {
225+
mount := &toolhiveContainer.VolumeMounts[i]
226+
if mount.Name == "runconfig" {
227+
runconfigMount = mount
228+
break
229+
}
230+
}
231+
Expect(runconfigMount).NotTo(BeNil(), "RunConfig volume mount should exist in toolhive container")
232+
Expect(runconfigMount.MountPath).To(Equal("/etc/runconfig"))
233+
Expect(runconfigMount.ReadOnly).To(BeTrue())
234+
})
235+
185236
It("Should not update ConfigMap when MCPServer spec is unchanged", func() {
186237
// Get initial ConfigMap state
187238
initialConfigMap := &corev1.ConfigMap{}
@@ -657,5 +708,132 @@ var _ = Describe("RunConfig ConfigMap Integration Tests", func() {
657708
}
658709
Expect(authMiddlewareFound).To(BeTrue(), "Auth middleware should be present in middleware_configs")
659710
})
711+
712+
It("Should handle MCPServer with authorization ConfigMap reference", func() {
713+
namespace := "authz-configmap-ns"
714+
mcpServerName := "authz-configmap-server"
715+
configMapName := mcpServerName + "-runconfig"
716+
externalAuthzConfigMapName := "external-authz-config"
717+
718+
// Create namespace
719+
ns := &corev1.Namespace{
720+
ObjectMeta: metav1.ObjectMeta{
721+
Name: namespace,
722+
},
723+
}
724+
_ = k8sClient.Create(ctx, ns)
725+
726+
// Create external authorization ConfigMap
727+
authzConfigMap := &corev1.ConfigMap{
728+
ObjectMeta: metav1.ObjectMeta{
729+
Name: externalAuthzConfigMapName,
730+
Namespace: namespace,
731+
},
732+
Data: map[string]string{
733+
"authz.json": `{
734+
"version": "v1",
735+
"type": "cedarv1",
736+
"cedar": {
737+
"policies": [
738+
"permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
739+
"permit(principal, action == Action::\"get_prompt\", resource == Prompt::\"greeting\");",
740+
"forbid(principal, action == Action::\"call_tool\", resource == Tool::\"sensitive_data\");"
741+
],
742+
"entities_json": "[{\"uid\": {\"type\": \"User\", \"id\": \"user1\"}, \"attrs\": {\"name\": \"Alice\", \"role\": \"developer\"}},{\"uid\": {\"type\": \"User\", \"id\": \"admin\"}, \"attrs\": {\"name\": \"Bob\", \"role\": \"admin\"}}]"
743+
}
744+
}`,
745+
},
746+
}
747+
Expect(k8sClient.Create(ctx, authzConfigMap)).Should(Succeed())
748+
defer k8sClient.Delete(ctx, authzConfigMap)
749+
750+
// Create MCPServer with ConfigMap authorization reference
751+
mcpServer := &mcpv1alpha1.MCPServer{
752+
ObjectMeta: metav1.ObjectMeta{
753+
Name: mcpServerName,
754+
Namespace: namespace,
755+
},
756+
Spec: mcpv1alpha1.MCPServerSpec{
757+
Image: "authz/mcp-server:latest",
758+
Transport: "stdio",
759+
ProxyPort: 8080,
760+
AuthzConfig: &mcpv1alpha1.AuthzConfigRef{
761+
Type: mcpv1alpha1.AuthzConfigTypeConfigMap,
762+
ConfigMap: &mcpv1alpha1.ConfigMapAuthzRef{
763+
Name: externalAuthzConfigMapName,
764+
Key: "authz.json",
765+
},
766+
},
767+
},
768+
}
769+
770+
Expect(k8sClient.Create(ctx, mcpServer)).Should(Succeed())
771+
defer k8sClient.Delete(ctx, mcpServer)
772+
773+
// Wait for RunConfig ConfigMap to be created
774+
configMap := &corev1.ConfigMap{}
775+
Eventually(func() error {
776+
return k8sClient.Get(ctx, types.NamespacedName{
777+
Name: configMapName,
778+
Namespace: namespace,
779+
}, configMap)
780+
}, timeout, interval).Should(Succeed())
781+
782+
// Verify ConfigMap has the expected label
783+
Expect(configMap.Labels).To(HaveKeyWithValue("toolhive.stacklok.io/mcp-server", mcpServerName))
784+
785+
// Verify ConfigMap data contains runconfig.json
786+
Expect(configMap.Data).To(HaveKey("runconfig.json"))
787+
runConfigJSON := configMap.Data["runconfig.json"]
788+
Expect(runConfigJSON).NotTo(BeEmpty())
789+
790+
// Parse and verify RunConfig content
791+
var runConfig runner.RunConfig
792+
err := json.Unmarshal([]byte(runConfigJSON), &runConfig)
793+
Expect(err).NotTo(HaveOccurred())
794+
795+
// Verify authorization configuration was embedded from external ConfigMap
796+
Expect(runConfig.AuthzConfig).NotTo(BeNil())
797+
Expect(runConfig.AuthzConfig.Version).To(Equal("v1"))
798+
Expect(runConfig.AuthzConfig.Type).To(Equal(authz.ConfigTypeCedarV1))
799+
800+
// Verify Cedar configuration
801+
Expect(runConfig.AuthzConfig.Cedar).NotTo(BeNil())
802+
803+
// Check policies are present
804+
Expect(runConfig.AuthzConfig.Cedar.Policies).To(HaveLen(3))
805+
Expect(runConfig.AuthzConfig.Cedar.Policies[0]).To(ContainSubstring("call_tool"))
806+
Expect(runConfig.AuthzConfig.Cedar.Policies[0]).To(ContainSubstring("weather"))
807+
Expect(runConfig.AuthzConfig.Cedar.Policies[1]).To(ContainSubstring("get_prompt"))
808+
Expect(runConfig.AuthzConfig.Cedar.Policies[1]).To(ContainSubstring("greeting"))
809+
Expect(runConfig.AuthzConfig.Cedar.Policies[2]).To(ContainSubstring("forbid"))
810+
Expect(runConfig.AuthzConfig.Cedar.Policies[2]).To(ContainSubstring("sensitive_data"))
811+
812+
// Verify entities are embedded
813+
Expect(runConfig.AuthzConfig.Cedar.EntitiesJSON).NotTo(BeEmpty())
814+
815+
// Parse entities to verify they're correctly embedded
816+
var entities []interface{}
817+
err = json.Unmarshal([]byte(runConfig.AuthzConfig.Cedar.EntitiesJSON), &entities)
818+
Expect(err).NotTo(HaveOccurred())
819+
Expect(entities).To(HaveLen(2))
820+
821+
// Verify entity details
822+
entity1 := entities[0].(map[string]interface{})
823+
uid1 := entity1["uid"].(map[string]interface{})
824+
Expect(uid1["type"]).To(Equal("User"))
825+
Expect(uid1["id"]).To(Equal("user1"))
826+
attrs1 := entity1["attrs"].(map[string]interface{})
827+
Expect(attrs1["name"]).To(Equal("Alice"))
828+
Expect(attrs1["role"]).To(Equal("developer"))
829+
830+
entity2 := entities[1].(map[string]interface{})
831+
uid2 := entity2["uid"].(map[string]interface{})
832+
Expect(uid2["type"]).To(Equal("User"))
833+
Expect(uid2["id"]).To(Equal("admin"))
834+
attrs2 := entity2["attrs"].(map[string]interface{})
835+
Expect(attrs2["name"]).To(Equal("Bob"))
836+
Expect(attrs2["role"]).To(Equal("admin"))
837+
})
660838
})
661839
})

test/e2e/chainsaw/operator/single-tenancy/test-scenarios/authz-configmap-ref/assert-mcpserver-pod-running.yaml

Lines changed: 0 additions & 9 deletions
This file was deleted.

test/e2e/chainsaw/operator/single-tenancy/test-scenarios/authz-configmap-ref/assert-mcpserver-running.yaml

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)