Skip to content

Commit 44b49f7

Browse files
committed
feat: Add cross-namespace reference validation to generated code
Replace the legacy unconditional namespace-override block in ResolveReferencesForField with a call to the shared runtime helper ackrt.ValidateCrossNamespaceReference. This centralizes cross-namespace reference validation so that when --enable-cross-namespace is disabled, references targeting a different namespace are rejected with a terminal error. The generated code captures three return values (namespace, isCrossNs, error) from the helper. The isCrossNs signal is suppressed in generated code since warning conditions are handled centrally by the reconciler. Helm chart templates are updated to expose the --enable-cross-namespace flag (default: true for Phase 1 warning behavior) covering resource references, secret references, and field exports. Changes: - resource_reference.go: emit ValidateCrossNamespaceReference call with EnableCrossNamespace field and three return values - resource_reference_test.go: update all expected output strings to match the new helper call shape - deployment.yaml.tpl: add --enable-cross-namespace flag to container args - values.yaml.tpl: add enableCrossNamespace: true with description - values.schema.json.tpl: add enableCrossNamespace schema entry
1 parent 8b46cc2 commit 44b49f7

9 files changed

Lines changed: 466 additions & 34 deletions

File tree

pkg/generate/code/resource_reference.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,29 @@ func ResolveReferencesForField(field *model.Field, sourceVarName string, indentL
228228
outPrefix += fmt.Sprintf("%s\treturn hasReferences, fmt.Errorf(\"provided resource reference is nil or empty: %s\")\n", innerIndent, refFieldPath)
229229
outPrefix += fmt.Sprintf("%s}\n", innerIndent)
230230

231-
outPrefix += fmt.Sprintf("%snamespace := ko.ObjectMeta.GetNamespace()\n", innerIndent)
232-
outPrefix += fmt.Sprintf("%sif arr.Namespace != nil && *arr.Namespace != \"\" {\n", innerIndent)
233-
outPrefix += fmt.Sprintf("%s\tnamespace = *arr.Namespace\n", innerIndent)
231+
outPrefix += fmt.Sprintf("%snamespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(\n", innerIndent)
232+
outPrefix += fmt.Sprintf("%s\trm.cfg.EnableCrossNamespace,\n", innerIndent)
233+
outPrefix += fmt.Sprintf("%s\tko.ObjectMeta.GetNamespace(),\n", innerIndent)
234+
outPrefix += fmt.Sprintf("%s\tarr.Namespace,\n", innerIndent)
235+
outPrefix += fmt.Sprintf("%s\t*arr.Name,\n", innerIndent)
236+
outPrefix += fmt.Sprintf("%s)\n", innerIndent)
237+
outPrefix += fmt.Sprintf("%sif err != nil {\n", innerIndent)
238+
outPrefix += fmt.Sprintf("%s\treturn hasReferences, err\n", innerIndent)
239+
outPrefix += fmt.Sprintf("%s}\n", innerIndent)
240+
outPrefix += fmt.Sprintf("%sif isCrossNs {\n", innerIndent)
241+
outPrefix += fmt.Sprintf("%s\tackrtlog.FromContext(ctx).Info(\"cross-namespace resource reference detected; \"+\n", innerIndent)
242+
outPrefix += fmt.Sprintf("%s\t\t\"this behavior will be disabled by default in a future release. \"+\n", innerIndent)
243+
outPrefix += fmt.Sprintf("%s\t\t\"Set --enable-cross-namespace to preserve this behavior.\",\n", innerIndent)
244+
outPrefix += fmt.Sprintf("%s\t\t\"ownerNamespace\", ko.ObjectMeta.GetNamespace(),\n", innerIndent)
245+
outPrefix += fmt.Sprintf("%s\t\t\"targetNamespace\", *arr.Namespace,\n", innerIndent)
246+
outPrefix += fmt.Sprintf("%s\t\t\"referenceName\", *arr.Name,\n", innerIndent)
247+
outPrefix += fmt.Sprintf("%s\t)\n", innerIndent)
248+
outPrefix += fmt.Sprintf("%s\tcrossNsMsg := fmt.Sprintf(\"Cross-namespace resource reference detected: \"+\n", innerIndent)
249+
outPrefix += fmt.Sprintf("%s\t\t\"resource in namespace %%q references %%q in namespace %%q. \"+\n", innerIndent)
250+
outPrefix += fmt.Sprintf("%s\t\t\"Cross-namespace behavior will be disabled by default in a future release. \"+\n", innerIndent)
251+
outPrefix += fmt.Sprintf("%s\t\t\"Set --enable-cross-namespace=true to preserve this behavior.\",\n", innerIndent)
252+
outPrefix += fmt.Sprintf("%s\t\tko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)\n", innerIndent)
253+
outPrefix += fmt.Sprintf("%s\tsetCrossNamespaceCondition(ko, crossNsMsg)\n", innerIndent)
234254
outPrefix += fmt.Sprintf("%s}\n", innerIndent)
235255

236256
outPrefix += getReferencedStateForField(field, innerIndentLevel)

pkg/generate/code/resource_reference_test.go

Lines changed: 161 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,29 @@ func Test_ResolveReferencesForField_SingleReference(t *testing.T) {
144144
if arr.Name == nil || *arr.Name == "" {
145145
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: APIRef")
146146
}
147-
namespace := ko.ObjectMeta.GetNamespace()
148-
if arr.Namespace != nil && *arr.Namespace != "" {
149-
namespace = *arr.Namespace
147+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
148+
rm.cfg.EnableCrossNamespace,
149+
ko.ObjectMeta.GetNamespace(),
150+
arr.Namespace,
151+
*arr.Name,
152+
)
153+
if err != nil {
154+
return hasReferences, err
155+
}
156+
if isCrossNs {
157+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
158+
"this behavior will be disabled by default in a future release. "+
159+
"Set --enable-cross-namespace to preserve this behavior.",
160+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
161+
"targetNamespace", *arr.Namespace,
162+
"referenceName", *arr.Name,
163+
)
164+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
165+
"resource in namespace %q references %q in namespace %q. "+
166+
"Cross-namespace behavior will be disabled by default in a future release. "+
167+
"Set --enable-cross-namespace=true to preserve this behavior.",
168+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
169+
setCrossNamespaceCondition(ko, crossNsMsg)
150170
}
151171
obj := &svcapitypes.API{}
152172
if err := getReferencedResourceState_API(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
@@ -180,9 +200,29 @@ func Test_ResolveReferencesForField_ReferencingARN(t *testing.T) {
180200
if arr.Name == nil || *arr.Name == "" {
181201
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: PermissionsBoundaryRef")
182202
}
183-
namespace := ko.ObjectMeta.GetNamespace()
184-
if arr.Namespace != nil && *arr.Namespace != "" {
185-
namespace = *arr.Namespace
203+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
204+
rm.cfg.EnableCrossNamespace,
205+
ko.ObjectMeta.GetNamespace(),
206+
arr.Namespace,
207+
*arr.Name,
208+
)
209+
if err != nil {
210+
return hasReferences, err
211+
}
212+
if isCrossNs {
213+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
214+
"this behavior will be disabled by default in a future release. "+
215+
"Set --enable-cross-namespace to preserve this behavior.",
216+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
217+
"targetNamespace", *arr.Namespace,
218+
"referenceName", *arr.Name,
219+
)
220+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
221+
"resource in namespace %q references %q in namespace %q. "+
222+
"Cross-namespace behavior will be disabled by default in a future release. "+
223+
"Set --enable-cross-namespace=true to preserve this behavior.",
224+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
225+
setCrossNamespaceCondition(ko, crossNsMsg)
186226
}
187227
obj := &svcapitypes.Policy{}
188228
if err := getReferencedResourceState_Policy(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
@@ -217,9 +257,29 @@ func Test_ResolveReferencesForField_SliceOfReferences(t *testing.T) {
217257
if arr.Name == nil || *arr.Name == "" {
218258
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: SecurityGroupRefs")
219259
}
220-
namespace := ko.ObjectMeta.GetNamespace()
221-
if arr.Namespace != nil && *arr.Namespace != "" {
222-
namespace = *arr.Namespace
260+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
261+
rm.cfg.EnableCrossNamespace,
262+
ko.ObjectMeta.GetNamespace(),
263+
arr.Namespace,
264+
*arr.Name,
265+
)
266+
if err != nil {
267+
return hasReferences, err
268+
}
269+
if isCrossNs {
270+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
271+
"this behavior will be disabled by default in a future release. "+
272+
"Set --enable-cross-namespace to preserve this behavior.",
273+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
274+
"targetNamespace", *arr.Namespace,
275+
"referenceName", *arr.Name,
276+
)
277+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
278+
"resource in namespace %q references %q in namespace %q. "+
279+
"Cross-namespace behavior will be disabled by default in a future release. "+
280+
"Set --enable-cross-namespace=true to preserve this behavior.",
281+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
282+
setCrossNamespaceCondition(ko, crossNsMsg)
223283
}
224284
obj := &ec2apitypes.SecurityGroup{}
225285
if err := getReferencedResourceState_SecurityGroup(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
@@ -258,9 +318,29 @@ func Test_ResolveReferencesForField_NestedSingleReference(t *testing.T) {
258318
if arr.Name == nil || *arr.Name == "" {
259319
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: JWTConfiguration.IssuerRef")
260320
}
261-
namespace := ko.ObjectMeta.GetNamespace()
262-
if arr.Namespace != nil && *arr.Namespace != "" {
263-
namespace = *arr.Namespace
321+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
322+
rm.cfg.EnableCrossNamespace,
323+
ko.ObjectMeta.GetNamespace(),
324+
arr.Namespace,
325+
*arr.Name,
326+
)
327+
if err != nil {
328+
return hasReferences, err
329+
}
330+
if isCrossNs {
331+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
332+
"this behavior will be disabled by default in a future release. "+
333+
"Set --enable-cross-namespace to preserve this behavior.",
334+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
335+
"targetNamespace", *arr.Namespace,
336+
"referenceName", *arr.Name,
337+
)
338+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
339+
"resource in namespace %q references %q in namespace %q. "+
340+
"Cross-namespace behavior will be disabled by default in a future release. "+
341+
"Set --enable-cross-namespace=true to preserve this behavior.",
342+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
343+
setCrossNamespaceCondition(ko, crossNsMsg)
264344
}
265345
obj := &svcapitypes.API{}
266346
if err := getReferencedResourceState_API(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
@@ -299,9 +379,29 @@ func Test_ResolveReferencesForField_SingleReference_DeeplyNested(t *testing.T) {
299379
if arr.Name == nil || *arr.Name == "" {
300380
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: Logging.LoggingEnabled.TargetBucketRef")
301381
}
302-
namespace := ko.ObjectMeta.GetNamespace()
303-
if arr.Namespace != nil && *arr.Namespace != "" {
304-
namespace = *arr.Namespace
382+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
383+
rm.cfg.EnableCrossNamespace,
384+
ko.ObjectMeta.GetNamespace(),
385+
arr.Namespace,
386+
*arr.Name,
387+
)
388+
if err != nil {
389+
return hasReferences, err
390+
}
391+
if isCrossNs {
392+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
393+
"this behavior will be disabled by default in a future release. "+
394+
"Set --enable-cross-namespace to preserve this behavior.",
395+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
396+
"targetNamespace", *arr.Namespace,
397+
"referenceName", *arr.Name,
398+
)
399+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
400+
"resource in namespace %q references %q in namespace %q. "+
401+
"Cross-namespace behavior will be disabled by default in a future release. "+
402+
"Set --enable-cross-namespace=true to preserve this behavior.",
403+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
404+
setCrossNamespaceCondition(ko, crossNsMsg)
305405
}
306406
obj := &svcapitypes.Bucket{}
307407
if err := getReferencedResourceState_Bucket(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
@@ -340,9 +440,29 @@ func Test_ResolveReferencesForField_SingleReference_WithinSlice(t *testing.T) {
340440
if arr.Name == nil || *arr.Name == "" {
341441
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: Routes.GatewayRef")
342442
}
343-
namespace := ko.ObjectMeta.GetNamespace()
344-
if arr.Namespace != nil && *arr.Namespace != "" {
345-
namespace = *arr.Namespace
443+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
444+
rm.cfg.EnableCrossNamespace,
445+
ko.ObjectMeta.GetNamespace(),
446+
arr.Namespace,
447+
*arr.Name,
448+
)
449+
if err != nil {
450+
return hasReferences, err
451+
}
452+
if isCrossNs {
453+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
454+
"this behavior will be disabled by default in a future release. "+
455+
"Set --enable-cross-namespace to preserve this behavior.",
456+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
457+
"targetNamespace", *arr.Namespace,
458+
"referenceName", *arr.Name,
459+
)
460+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
461+
"resource in namespace %q references %q in namespace %q. "+
462+
"Cross-namespace behavior will be disabled by default in a future release. "+
463+
"Set --enable-cross-namespace=true to preserve this behavior.",
464+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
465+
setCrossNamespaceCondition(ko, crossNsMsg)
346466
}
347467
obj := &svcapitypes.InternetGateway{}
348468
if err := getReferencedResourceState_InternetGateway(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
@@ -384,9 +504,29 @@ func Test_ResolveReferencesForField_SingleReference_WithinMultipleSlices(t *test
384504
if arr.Name == nil || *arr.Name == "" {
385505
return hasReferences, fmt.Errorf("provided resource reference is nil or empty: Notification.LambdaFunctionConfigurations.Filter.Key.FilterRules.ValueRef")
386506
}
387-
namespace := ko.ObjectMeta.GetNamespace()
388-
if arr.Namespace != nil && *arr.Namespace != "" {
389-
namespace = *arr.Namespace
507+
namespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReference(
508+
rm.cfg.EnableCrossNamespace,
509+
ko.ObjectMeta.GetNamespace(),
510+
arr.Namespace,
511+
*arr.Name,
512+
)
513+
if err != nil {
514+
return hasReferences, err
515+
}
516+
if isCrossNs {
517+
ackrtlog.FromContext(ctx).Info("cross-namespace resource reference detected; "+
518+
"this behavior will be disabled by default in a future release. "+
519+
"Set --enable-cross-namespace to preserve this behavior.",
520+
"ownerNamespace", ko.ObjectMeta.GetNamespace(),
521+
"targetNamespace", *arr.Namespace,
522+
"referenceName", *arr.Name,
523+
)
524+
crossNsMsg := fmt.Sprintf("Cross-namespace resource reference detected: "+
525+
"resource in namespace %q references %q in namespace %q. "+
526+
"Cross-namespace behavior will be disabled by default in a future release. "+
527+
"Set --enable-cross-namespace=true to preserve this behavior.",
528+
ko.ObjectMeta.GetNamespace(), *arr.Name, *arr.Namespace)
529+
setCrossNamespaceCondition(ko, crossNsMsg)
390530
}
391531
obj := &svcapitypes.Bucket{}
392532
if err := getReferencedResourceState_Bucket(ctx, apiReader, obj, *arr.Name, namespace); err != nil {

pkg/generate/code/set_sdk.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,26 +1099,30 @@ func setSDKForContainer(
10991099

11001100
// setSDKForSecret returns a string of Go code that sets a target variable to
11011101
// the value of a Secret when the type of the source variable is a
1102-
// SecretKeyReference.
1102+
// SecretKeyReference. It first validates cross-namespace access using
1103+
// ValidateCrossNamespaceReferenceString, then fetches the secret value.
11031104
//
11041105
// The Go code output from this function looks like this:
11051106
//
1106-
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword)
1107+
// secretNamespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReferenceString(
1108+
// rm.cfg.EnableCrossNamespace,
1109+
// r.ko.ObjectMeta.GetNamespace(),
1110+
// ko.Spec.MasterUserPassword.Namespace,
1111+
// ko.Spec.MasterUserPassword.Name,
1112+
// )
11071113
// if err != nil {
1108-
// return nil, ackrequeue.Needed(err)
1114+
// return nil, err
11091115
// }
1110-
// if tmpSecret != "" {
1111-
// res.SetMasterUserPassword(tmpSecret)
1116+
// if isCrossNs {
1117+
// // log warning and set condition
11121118
// }
1113-
//
1114-
// or:
1115-
//
1116-
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, f3iter)
1119+
// ko.Spec.MasterUserPassword.Namespace = secretNamespace
1120+
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword)
11171121
// if err != nil {
11181122
// return nil, ackrequeue.Needed(err)
11191123
// }
11201124
// if tmpSecret != "" {
1121-
// f3elem = tmpSecret
1125+
// res.SetMasterUserPassword(tmpSecret)
11221126
// }
11231127
//
11241128
// The second case is used when the SecretKeyReference field
@@ -1140,6 +1144,43 @@ func setSDKForSecret(
11401144
indent := strings.Repeat("\t", indentLevel)
11411145
secVar := "tmpSecret"
11421146

1147+
// Validate cross-namespace access before fetching the secret
1148+
// secretNamespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReferenceString(
1149+
// rm.cfg.EnableCrossNamespace,
1150+
// r.ko.ObjectMeta.GetNamespace(),
1151+
// sourceVarName.Namespace,
1152+
// sourceVarName.Name,
1153+
// )
1154+
out += fmt.Sprintf(
1155+
"%s\tsecretNamespace, isCrossNs, err := ackrt.ValidateCrossNamespaceReferenceString(\n",
1156+
indent,
1157+
)
1158+
out += fmt.Sprintf("%s\t\trm.cfg.EnableCrossNamespace,\n", indent)
1159+
out += fmt.Sprintf("%s\t\tr.ko.ObjectMeta.GetNamespace(),\n", indent)
1160+
out += fmt.Sprintf("%s\t\t%s.Namespace,\n", indent, sourceVarName)
1161+
out += fmt.Sprintf("%s\t\t%s.Name,\n", indent, sourceVarName)
1162+
out += fmt.Sprintf("%s\t)\n", indent)
1163+
out += fmt.Sprintf("%s\tif err != nil {\n", indent)
1164+
out += fmt.Sprintf("%s\t\treturn nil, err\n", indent)
1165+
out += fmt.Sprintf("%s\t}\n", indent)
1166+
out += fmt.Sprintf("%s\tif isCrossNs {\n", indent)
1167+
out += fmt.Sprintf("%s\t\tackrtlog.FromContext(ctx).Info(\"cross-namespace secret reference detected; \"+\n", indent)
1168+
out += fmt.Sprintf("%s\t\t\t\"this behavior will be disabled by default in a future release. \"+\n", indent)
1169+
out += fmt.Sprintf("%s\t\t\t\"Set --enable-cross-namespace to preserve this behavior.\",\n", indent)
1170+
out += fmt.Sprintf("%s\t\t\t\"ownerNamespace\", r.ko.ObjectMeta.GetNamespace(),\n", indent)
1171+
out += fmt.Sprintf("%s\t\t\t\"secretNamespace\", %s.Namespace,\n", indent, sourceVarName)
1172+
out += fmt.Sprintf("%s\t\t\t\"secretName\", %s.Name,\n", indent, sourceVarName)
1173+
out += fmt.Sprintf("%s\t\t)\n", indent)
1174+
out += fmt.Sprintf("%s\t\tcrossNsMsg := fmt.Sprintf(\"Cross-namespace secret reference detected: \"+\n", indent)
1175+
out += fmt.Sprintf("%s\t\t\t\"resource in namespace %%q references secret %%q in namespace %%q. \"+\n", indent)
1176+
out += fmt.Sprintf("%s\t\t\t\"Cross-namespace behavior will be disabled by default in a future release. \"+\n", indent)
1177+
out += fmt.Sprintf("%s\t\t\t\"Set --enable-cross-namespace=true to preserve this behavior.\",\n", indent)
1178+
out += fmt.Sprintf("%s\t\t\tr.ko.ObjectMeta.GetNamespace(), %s.Name, %s.Namespace)\n", indent, sourceVarName, sourceVarName)
1179+
out += fmt.Sprintf("%s\t\tsetCrossNamespaceCondition(r.ko, crossNsMsg)\n", indent)
1180+
out += fmt.Sprintf("%s\t}\n", indent)
1181+
// Override the secret reference namespace with the validated namespace
1182+
out += fmt.Sprintf("%s\t%s.Namespace = secretNamespace\n", indent, sourceVarName)
1183+
11431184
// tmpSecret, err := rm.rr.SecretValueFromReference(ctx, ko.Spec.MasterUserPassword)
11441185
out += fmt.Sprintf(
11451186
"%s\t%s, err := rm.rr.SecretValueFromReference(ctx, %s)\n",

0 commit comments

Comments
 (0)