Skip to content

Commit 20c5bc8

Browse files
pedjakclaude
andcommitted
Fix ClusterObjectSet ref resolution for Secrets outside system namespace
The controller cache only watches the system namespace, causing ref resolution to fail when Secrets are stored in other namespaces. Fix by introducing a client wrapper that falls back to direct API calls for Secret reads outside the system namespace, and grant cluster-wide Secret get permission when BoxcutterRuntime is enabled. Adds an e2e scenario covering this path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 735b41e commit 20c5bc8

7 files changed

Lines changed: 160 additions & 5 deletions

File tree

cmd/operator-controller/main.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/spf13/cobra"
3232
"go.podman.io/image/v5/types"
33+
corev1 "k8s.io/api/core/v1"
3334
rbacv1 "k8s.io/api/rbac/v1"
3435
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3536
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
@@ -697,8 +698,13 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl
697698
return fmt.Errorf("unable to create revision engine factory: %w", err)
698699
}
699700

701+
cosClient := &secretFallbackClient{
702+
Client: c.mgr.GetClient(),
703+
apiReader: c.mgr.GetAPIReader(),
704+
systemNamespace: cfg.systemNamespace,
705+
}
700706
if err = (&controllers.ClusterObjectSetReconciler{
701-
Client: c.mgr.GetClient(),
707+
Client: cosClient,
702708
RevisionEngineFactory: revisionEngineFactory,
703709
TrackingCache: trackingCache,
704710
}).SetupWithManager(c.mgr); err != nil {
@@ -791,3 +797,18 @@ func main() {
791797
os.Exit(1)
792798
}
793799
}
800+
801+
// secretFallbackClient wraps a cached client.Client and falls back to direct
802+
// API reads for Secrets outside the system namespace, where the cache does not watch.
803+
type secretFallbackClient struct {
804+
client.Client
805+
apiReader client.Reader
806+
systemNamespace string
807+
}
808+
809+
func (c *secretFallbackClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
810+
if _, isSecret := obj.(*corev1.Secret); isSecret && key.Namespace != c.systemNamespace {
811+
return c.apiReader.Get(ctx, key, obj, opts...)
812+
}
813+
return c.Client.Get(ctx, key, obj, opts...)
814+
}

helm/olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ rules:
8686
verbs:
8787
- list
8888
- watch
89+
- apiGroups:
90+
- ""
91+
resources:
92+
- secrets
93+
verbs:
94+
- get
8995
- apiGroups:
9096
- olm.operatorframework.io
9197
resources:

manifests/experimental-e2e.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,12 @@ rules:
21672167
verbs:
21682168
- list
21692169
- watch
2170+
- apiGroups:
2171+
- ""
2172+
resources:
2173+
- secrets
2174+
verbs:
2175+
- get
21702176
- apiGroups:
21712177
- olm.operatorframework.io
21722178
resources:

manifests/experimental.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,12 @@ rules:
21282128
verbs:
21292129
- list
21302130
- watch
2131+
- apiGroups:
2132+
- ""
2133+
resources:
2134+
- secrets
2135+
verbs:
2136+
- get
21312137
- apiGroups:
21322138
- olm.operatorframework.io
21332139
resources:

test/e2e/features/revision.feature

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,109 @@ Feature: Install ClusterObjectSet
338338
And resource "pod/test-pod" is installed
339339
And resource "configmap/test-configmap-3" is installed
340340
And ClusterObjectSet "${COS_NAME}" reports Progressing as True with Reason Succeeded
341-
And ClusterObjectSet "${COS_NAME}" reports Available as True with Reason ProbesSucceeded
341+
And ClusterObjectSet "${COS_NAME}" reports Available as True with Reason ProbesSucceeded
342+
343+
@wip
344+
Scenario: User can install a ClusterObjectSet with objects stored in Secrets
345+
Given ServiceAccount "olm-sa" with needed permissions is available in test namespace
346+
When resource is applied
347+
"""
348+
apiVersion: v1
349+
kind: Secret
350+
metadata:
351+
name: ${COS_NAME}-ref-secret
352+
namespace: ${TEST_NAMESPACE}
353+
immutable: true
354+
type: olm.operatorframework.io/object-data
355+
stringData:
356+
configmap: |
357+
{
358+
"apiVersion": "v1",
359+
"kind": "ConfigMap",
360+
"metadata": {
361+
"name": "test-configmap-ref",
362+
"namespace": "${TEST_NAMESPACE}"
363+
},
364+
"data": {
365+
"key": "value"
366+
}
367+
}
368+
deployment: |
369+
{
370+
"apiVersion": "apps/v1",
371+
"kind": "Deployment",
372+
"metadata": {
373+
"name": "test-httpd",
374+
"namespace": "${TEST_NAMESPACE}",
375+
"labels": {
376+
"app": "test-httpd"
377+
}
378+
},
379+
"spec": {
380+
"replicas": 1,
381+
"selector": {
382+
"matchLabels": {
383+
"app": "test-httpd"
384+
}
385+
},
386+
"template": {
387+
"metadata": {
388+
"labels": {
389+
"app": "test-httpd"
390+
}
391+
},
392+
"spec": {
393+
"containers": [
394+
{
395+
"name": "httpd",
396+
"image": "busybox:1.36",
397+
"imagePullPolicy": "IfNotPresent",
398+
"command": ["httpd"],
399+
"args": ["-f", "-p", "8080"],
400+
"securityContext": {
401+
"runAsNonRoot": true,
402+
"runAsUser": 1000,
403+
"allowPrivilegeEscalation": false,
404+
"capabilities": {
405+
"drop": ["ALL"]
406+
},
407+
"seccompProfile": {
408+
"type": "RuntimeDefault"
409+
}
410+
}
411+
}
412+
]
413+
}
414+
}
415+
}
416+
}
417+
"""
418+
And ClusterObjectSet is applied
419+
"""
420+
apiVersion: olm.operatorframework.io/v1
421+
kind: ClusterObjectSet
422+
metadata:
423+
annotations:
424+
olm.operatorframework.io/service-account-name: olm-sa
425+
olm.operatorframework.io/service-account-namespace: ${TEST_NAMESPACE}
426+
name: ${COS_NAME}
427+
spec:
428+
lifecycleState: Active
429+
collisionProtection: Prevent
430+
phases:
431+
- name: resources
432+
objects:
433+
- ref:
434+
name: ${COS_NAME}-ref-secret
435+
namespace: ${TEST_NAMESPACE}
436+
key: configmap
437+
- ref:
438+
name: ${COS_NAME}-ref-secret
439+
namespace: ${TEST_NAMESPACE}
440+
key: deployment
441+
revision: 1
442+
"""
443+
Then ClusterObjectSet "${COS_NAME}" reports Progressing as True with Reason Succeeded
444+
And ClusterObjectSet "${COS_NAME}" reports Available as True with Reason ProbesSucceeded
445+
And resource "configmap/test-configmap-ref" is installed
446+
And resource "deployment/test-httpd" is installed

test/e2e/steps/hooks.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import (
2222
)
2323

2424
type resource struct {
25-
name string
26-
kind string
25+
name string
26+
kind string
27+
namespace string
2728
}
2829

2930
type scenarioContext struct {
@@ -195,7 +196,11 @@ func ScenarioCleanup(ctx context.Context, _ *godog.Scenario, err error) (context
195196
forDeletion = append(forDeletion, resource{name: sc.namespace, kind: "namespace"})
196197
for _, r := range forDeletion {
197198
go func(res resource) {
198-
if _, err := k8sClient("delete", res.kind, res.name, "--ignore-not-found=true"); err != nil {
199+
args := []string{"delete", res.kind, res.name, "--ignore-not-found=true"}
200+
if res.namespace != "" {
201+
args = append(args, "-n", res.namespace)
202+
}
203+
if _, err := k8sClient(args...); err != nil {
199204
logger.Info("Error deleting resource", "name", res.name, "namespace", sc.namespace, "stderr", stderrOutput(err))
200205
}
201206
}(r)

test/e2e/steps/steps.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,12 @@ func ResourceIsApplied(ctx context.Context, yamlTemplate *godog.DocString) error
310310
sc.clusterExtensionName = res.GetName()
311311
} else if res.GetKind() == "ClusterObjectSet" {
312312
sc.clusterObjectSetName = res.GetName()
313+
} else {
314+
sc.addedResources = append(sc.addedResources, resource{
315+
name: res.GetName(),
316+
kind: strings.ToLower(res.GetKind()),
317+
namespace: res.GetNamespace(),
318+
})
313319
}
314320
return nil
315321
}

0 commit comments

Comments
 (0)