@@ -26,6 +26,7 @@ const (
26
26
stdioTransport = "stdio"
27
27
sseProxyMode = "sse"
28
28
streamableHTTPProxyMode = "streamable-http"
29
+ defaultAuthzKey = "authz.json"
29
30
)
30
31
31
32
func createRunConfigTestScheme () * runtime.Scheme {
@@ -456,7 +457,7 @@ func TestCreateRunConfigFromMCPServer(t *testing.T) {
456
457
Type : mcpv1alpha1 .AuthzConfigTypeConfigMap ,
457
458
ConfigMap : & mcpv1alpha1.ConfigMapAuthzRef {
458
459
Name : "test-authz-config" ,
459
- Key : "authz.json" ,
460
+ Key : defaultAuthzKey ,
460
461
},
461
462
},
462
463
},
@@ -465,9 +466,14 @@ func TestCreateRunConfigFromMCPServer(t *testing.T) {
465
466
expected : func (t * testing.T , config * runner.RunConfig ) {
466
467
assert .Equal (t , "authz-configmap-server" , config .Name )
467
468
468
- // For ConfigMap type, authorization config should not be set directly in RunConfig
469
- // since it will be handled by proxyrunner when reading from ConfigMap
470
- assert .Nil (t , config .AuthzConfig )
469
+ // For ConfigMap type, with new feature, authorization config is embedded in RunConfig
470
+ require .NotNil (t , config .AuthzConfig )
471
+ assert .Equal (t , "v1" , config .AuthzConfig .Version )
472
+ assert .Equal (t , authz .ConfigTypeCedarV1 , config .AuthzConfig .Type )
473
+ require .NotNil (t , config .AuthzConfig .Cedar )
474
+ assert .Len (t , config .AuthzConfig .Cedar .Policies , 1 )
475
+ assert .Contains (t , config .AuthzConfig .Cedar .Policies [0 ], "call_tool" )
476
+ assert .Equal (t , "[]" , config .AuthzConfig .Cedar .EntitiesJSON )
471
477
},
472
478
},
473
479
{
@@ -594,7 +600,54 @@ func TestCreateRunConfigFromMCPServer(t *testing.T) {
594
600
for _ , tt := range tests {
595
601
t .Run (tt .name , func (t * testing.T ) {
596
602
t .Parallel ()
597
- r := & MCPServerReconciler {}
603
+
604
+ // Build reconciler; if test uses ConfigMap-based authz, provide a fake client with that ConfigMap
605
+ var r * MCPServerReconciler
606
+ if tt .mcpServer != nil &&
607
+ tt .mcpServer .Spec .AuthzConfig != nil &&
608
+ tt .mcpServer .Spec .AuthzConfig .Type == mcpv1alpha1 .AuthzConfigTypeConfigMap &&
609
+ tt .mcpServer .Spec .AuthzConfig .ConfigMap != nil {
610
+
611
+ scheme := createRunConfigTestScheme ()
612
+
613
+ // Prepare a ConfigMap with authorization configuration content
614
+ cm := & corev1.ConfigMap {
615
+ ObjectMeta : metav1.ObjectMeta {
616
+ Name : tt .mcpServer .Spec .AuthzConfig .ConfigMap .Name ,
617
+ Namespace : tt .mcpServer .Namespace ,
618
+ },
619
+ Data : map [string ]string {
620
+ func () string {
621
+ if k := tt .mcpServer .Spec .AuthzConfig .ConfigMap .Key ; k != "" {
622
+ return k
623
+ }
624
+ return defaultAuthzKey
625
+ }(): `{
626
+ "version": "v1",
627
+ "type": "cedarv1",
628
+ "cedar": {
629
+ "policies": [
630
+ "permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");"
631
+ ],
632
+ "entities_json": "[]"
633
+ }
634
+ }` ,
635
+ },
636
+ }
637
+
638
+ fakeClient := fake .NewClientBuilder ().
639
+ WithScheme (scheme ).
640
+ WithRuntimeObjects (cm ).
641
+ Build ()
642
+
643
+ r = & MCPServerReconciler {
644
+ Client : fakeClient ,
645
+ Scheme : scheme ,
646
+ }
647
+ } else {
648
+ r = & MCPServerReconciler {}
649
+ }
650
+
598
651
result , err := r .createRunConfigFromMCPServer (tt .mcpServer )
599
652
require .NoError (t , err )
600
653
assert .NotNil (t , result )
@@ -1053,7 +1106,7 @@ func TestEnsureRunConfigConfigMap(t *testing.T) {
1053
1106
t .Run (tt .name , func (t * testing.T ) {
1054
1107
t .Parallel ()
1055
1108
testScheme := createRunConfigTestScheme ()
1056
- objects := []runtime.Object {}
1109
+ objects := []runtime.Object {tt . mcpServer }
1057
1110
if tt .existingCM != nil {
1058
1111
objects = append (objects , tt .existingCM )
1059
1112
}
@@ -1100,6 +1153,87 @@ func TestEnsureRunConfigConfigMap(t *testing.T) {
1100
1153
}
1101
1154
})
1102
1155
}
1156
+
1157
+ // Additional test: ConfigMap-based Authz referenced externally should be embedded into runconfig.json
1158
+ t .Run ("configmap with external authorization configuration" , func (t * testing.T ) {
1159
+ t .Parallel ()
1160
+ testScheme := createRunConfigTestScheme ()
1161
+
1162
+ mcpServer := & mcpv1alpha1.MCPServer {
1163
+ ObjectMeta : metav1.ObjectMeta {
1164
+ Name : "authz-cm-ext" ,
1165
+ Namespace : "toolhive-system" ,
1166
+ },
1167
+ Spec : mcpv1alpha1.MCPServerSpec {
1168
+ Image : "ghcr.io/example/server:v1.0.0" ,
1169
+ Transport : "stdio" ,
1170
+ Port : 8080 ,
1171
+ AuthzConfig : & mcpv1alpha1.AuthzConfigRef {
1172
+ Type : mcpv1alpha1 .AuthzConfigTypeConfigMap ,
1173
+ ConfigMap : & mcpv1alpha1.ConfigMapAuthzRef {
1174
+ Name : "ext-authz-config" ,
1175
+ Key : "authz.json" ,
1176
+ },
1177
+ },
1178
+ },
1179
+ }
1180
+
1181
+ authzCM := & corev1.ConfigMap {
1182
+ ObjectMeta : metav1.ObjectMeta {
1183
+ Name : "ext-authz-config" ,
1184
+ Namespace : "toolhive-system" ,
1185
+ },
1186
+ Data : map [string ]string {
1187
+ "authz.json" : `{
1188
+ "version": "v1",
1189
+ "type": "cedarv1",
1190
+ "cedar": {
1191
+ "policies": [
1192
+ "permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
1193
+ "permit(principal, action == Action::\"get_prompt\", resource == Prompt::\"greeting\");"
1194
+ ],
1195
+ "entities_json": "[{\"uid\": {\"type\": \"User\", \"id\": \"user1\"}, \"attrs\": {}}]"
1196
+ }
1197
+ }` ,
1198
+ },
1199
+ }
1200
+
1201
+ fakeClient := fake .NewClientBuilder ().
1202
+ WithScheme (testScheme ).
1203
+ WithRuntimeObjects (mcpServer , authzCM ).
1204
+ Build ()
1205
+
1206
+ reconciler := & MCPServerReconciler {
1207
+ Client : fakeClient ,
1208
+ Scheme : testScheme ,
1209
+ }
1210
+
1211
+ err := reconciler .ensureRunConfigConfigMap (context .TODO (), mcpServer )
1212
+ require .NoError (t , err )
1213
+
1214
+ // Fetch the generated runconfig ConfigMap
1215
+ configMapName := fmt .Sprintf ("%s-runconfig" , mcpServer .Name )
1216
+ configMap := & corev1.ConfigMap {}
1217
+ err = fakeClient .Get (context .TODO (), types.NamespacedName {
1218
+ Name : configMapName ,
1219
+ Namespace : mcpServer .Namespace ,
1220
+ }, configMap )
1221
+ require .NoError (t , err )
1222
+
1223
+ // Validate that authz config is embedded
1224
+ var runConfig runner.RunConfig
1225
+ err = json .Unmarshal ([]byte (configMap .Data ["runconfig.json" ]), & runConfig )
1226
+ require .NoError (t , err )
1227
+
1228
+ require .NotNil (t , runConfig .AuthzConfig )
1229
+ assert .Equal (t , "v1" , runConfig .AuthzConfig .Version )
1230
+ assert .Equal (t , authz .ConfigTypeCedarV1 , runConfig .AuthzConfig .Type )
1231
+ require .NotNil (t , runConfig .AuthzConfig .Cedar )
1232
+ assert .Len (t , runConfig .AuthzConfig .Cedar .Policies , 2 )
1233
+ assert .Contains (t , runConfig .AuthzConfig .Cedar .Policies , `permit(principal, action == Action::"call_tool", resource == Tool::"weather");` )
1234
+ assert .Contains (t , runConfig .AuthzConfig .Cedar .Policies , `permit(principal, action == Action::"get_prompt", resource == Prompt::"greeting");` )
1235
+ assert .Equal (t , `[{"uid": {"type": "User", "id": "user1"}, "attrs": {}}]` , runConfig .AuthzConfig .Cedar .EntitiesJSON )
1236
+ })
1103
1237
}
1104
1238
1105
1239
// TestRunConfigContentEquals tests the content comparison logic
0 commit comments