Skip to content

Commit 3e2f8e8

Browse files
committed
fix: allow cluster-scoped resources to bypass allowedNamepsaces checks
1 parent 3f6ef66 commit 3e2f8e8

10 files changed

Lines changed: 99 additions & 5 deletions

pkg/cloud/interfaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,6 @@ type SessionMetadata interface {
106106
IdentityRef() *infrav1.AWSIdentityReference
107107
// ControllerName returns the controller name
108108
ControllerName() string
109+
// IsClusterScoped returns true if the resource is cluster-scoped, false if namespace-scoped.
110+
IsClusterScoped() bool
109111
}

pkg/cloud/scope/cluster.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,8 @@ func (s *ClusterScope) UnstructuredControlPlane() (*unstructured.Unstructured, e
445445
func (s *ClusterScope) NodePortIngressRuleCidrBlocks() infrav1.CidrBlocks {
446446
return s.AWSCluster.Spec.NetworkSpec.DeepCopy().NodePortIngressRuleCidrBlocks
447447
}
448+
449+
// IsClusterScoped returns false as Cluster is namespace-scoped.
450+
func (s *ClusterScope) IsClusterScoped() bool {
451+
return false
452+
}

pkg/cloud/scope/managedcontrolplane.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,8 @@ func (s *ManagedControlPlaneScope) NodePortIngressRuleCidrBlocks() infrav1.CidrB
500500
func (s *ManagedControlPlaneScope) MaxWaitDuration() time.Duration {
501501
return s.MaxWaitActiveUpdateDelete
502502
}
503+
504+
// IsClusterScoped returns false as ManagedControlPlane is namespace-scoped.
505+
func (s *ManagedControlPlaneScope) IsClusterScoped() bool {
506+
return false
507+
}

pkg/cloud/scope/managednodegroup.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,8 @@ func (s *ManagedMachinePoolScope) GetRuntimeObject() runtime.Object {
432432
func (s *ManagedMachinePoolScope) GetLifecycleHooks() []expinfrav1.AWSLifecycleHook {
433433
return s.ManagedMachinePool.Spec.AWSLifecycleHooks
434434
}
435+
436+
// IsClusterScoped returns false as ManagedMachinePool is namespace-scoped.
437+
func (s *ManagedMachinePoolScope) IsClusterScoped() bool {
438+
return false
439+
}

pkg/cloud/scope/rosacontrolplane.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,8 @@ func (s *ROSAControlPlaneScope) PatchObject() error {
226226
func (s *ROSAControlPlaneScope) Close() error {
227227
return s.PatchObject()
228228
}
229+
230+
// IsClusterScoped returns false as ROSAControlPlane is namespace-scoped.
231+
func (s *ROSAControlPlaneScope) IsClusterScoped() bool {
232+
return false
233+
}

pkg/cloud/scope/rosamachinepool.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,8 @@ func (s *RosaMachinePoolScope) PatchCAPIMachinePoolObject(ctx context.Context) e
230230
func (s *RosaMachinePoolScope) Close() error {
231231
return s.PatchObject()
232232
}
233+
234+
// IsClusterScoped returns false as ROSAMachinePool is namespace-scoped.
235+
func (s *RosaMachinePoolScope) IsClusterScoped() bool {
236+
return false
237+
}

pkg/cloud/scope/rosanetwork.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,8 @@ func (s *ROSANetworkScope) PatchObject() error {
134134
expinfrav1.ROSANetworkReadyCondition,
135135
}})
136136
}
137+
138+
// IsClusterScoped returns false as ROSANetwork is namespace-scoped.
139+
func (s *ROSANetworkScope) IsClusterScoped() bool {
140+
return false
141+
}

pkg/cloud/scope/rosaroleconfig.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,8 @@ func (s *RosaRoleConfigScope) CredentialsSecret() *corev1.Secret {
162162
func (s *RosaRoleConfigScope) IAMClient() *iam.Client {
163163
return s.iamClient
164164
}
165+
166+
// IsClusterScoped returns false as ROSARoleConfig is namespace-scoped.
167+
func (s *RosaRoleConfigScope) IsClusterScoped() bool {
168+
return false
169+
}

pkg/cloud/scope/session.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ func buildProvidersForRef(
260260
return providers, err
261261
}
262262
log.Trace("Principal retrieved")
263-
canUse, err := isClusterPermittedToUsePrincipal(k8sClient, roleIdentity.Spec.AllowedNamespaces, clusterScoper.Namespace())
263+
264+
canUse, err := isClusterPermittedToUsePrincipal(k8sClient, roleIdentity.Spec.AllowedNamespaces, clusterScoper.Namespace(), clusterScoper.IsClusterScoped())
264265
if err != nil {
265266
return providers, err
266267
}
@@ -337,7 +338,7 @@ func buildAWSClusterStaticIdentity(ctx context.Context, identityObjectKey client
337338
return nil, errors.Wrapf(err, "failed to patch secret name:%s namespace:%s", secret.Name, secret.Namespace)
338339
}
339340

340-
canUse, err := isClusterPermittedToUsePrincipal(k8sClient, staticPrincipal.Spec.AllowedNamespaces, clusterScoper.Namespace())
341+
canUse, err := isClusterPermittedToUsePrincipal(k8sClient, staticPrincipal.Spec.AllowedNamespaces, clusterScoper.Namespace(), clusterScoper.IsClusterScoped())
341342
if err != nil {
342343
return nil, err
343344
}
@@ -364,7 +365,7 @@ func buildAWSClusterControllerIdentity(ctx context.Context, identityObjectKey cl
364365
return err
365366
}
366367

367-
canUse, err := isClusterPermittedToUsePrincipal(k8sClient, controllerIdentity.Spec.AllowedNamespaces, clusterScoper.Namespace())
368+
canUse, err := isClusterPermittedToUsePrincipal(k8sClient, controllerIdentity.Spec.AllowedNamespaces, clusterScoper.Namespace(), clusterScoper.IsClusterScoped())
368369
if err != nil {
369370
return err
370371
}
@@ -386,7 +387,12 @@ func getProvidersForCluster(ctx context.Context, k8sClient client.Client, cluste
386387
return providers, nil
387388
}
388389

389-
func isClusterPermittedToUsePrincipal(k8sClient client.Client, allowedNs *infrav1.AllowedNamespaces, clusterNamespace string) (bool, error) {
390+
func isClusterPermittedToUsePrincipal(k8sClient client.Client, allowedNs *infrav1.AllowedNamespaces, clusterNamespace string, isClusterScoped bool) (bool, error) {
391+
// Cluster-scoped resources bypass allowedNamespaces checks.
392+
if isClusterScoped {
393+
return true, nil
394+
}
395+
390396
// nil value does not match with any namespaces
391397
if allowedNs == nil {
392398
return false, nil

pkg/cloud/scope/session_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
4141
testCases := []struct {
4242
name string
4343
clusterNamespace string
44+
isClusterScoped bool
4445
allowedNs *infrav1.AllowedNamespaces
4546
setup func(*testing.T, client.Client)
4647
expectedResult bool
@@ -49,20 +50,23 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
4950
{
5051
name: "All clusters are permitted to use identity if allowedNamespaces is empty",
5152
clusterNamespace: "default",
53+
isClusterScoped: false,
5254
allowedNs: &infrav1.AllowedNamespaces{},
5355
expectedResult: true,
5456
expectErr: false,
5557
},
5658
{
5759
name: "No clusters are permitted to use identity if allowedNamespaces is nil",
5860
clusterNamespace: "default",
61+
isClusterScoped: false,
5962
allowedNs: nil,
6063
expectedResult: false,
6164
expectErr: false,
6265
},
6366
{
6467
name: "A namespace is permitted if allowedNamespaces list has it",
6568
clusterNamespace: "match",
69+
isClusterScoped: false,
6670
allowedNs: &infrav1.AllowedNamespaces{
6771
NamespaceList: []string{"match"},
6872
Selector: metav1.LabelSelector{},
@@ -87,6 +91,7 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
8791
{
8892
name: "A namespace is not permitted if allowedNamespaces list does not have it",
8993
clusterNamespace: "default",
94+
isClusterScoped: false,
9095
allowedNs: &infrav1.AllowedNamespaces{
9196
NamespaceList: []string{"nomatch"},
9297
Selector: metav1.LabelSelector{},
@@ -111,6 +116,7 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
111116
{
112117
name: "A namespace is not permitted if allowedNamespaces list and selector do not have it",
113118
clusterNamespace: "default",
119+
isClusterScoped: false,
114120
allowedNs: &infrav1.AllowedNamespaces{
115121
NamespaceList: []string{"nomatch"},
116122
Selector: metav1.LabelSelector{
@@ -137,6 +143,7 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
137143
{
138144
name: "A namespace is not permitted if allowedNamespaces list and selector do not have it",
139145
clusterNamespace: "default",
146+
isClusterScoped: false,
140147
allowedNs: &infrav1.AllowedNamespaces{
141148
NamespaceList: nil,
142149
Selector: metav1.LabelSelector{
@@ -163,6 +170,7 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
163170
{
164171
name: "A namespace is permitted if allowedNamespaces list does not have it but selector matches its label",
165172
clusterNamespace: "default",
173+
isClusterScoped: false,
166174
allowedNs: &infrav1.AllowedNamespaces{
167175
NamespaceList: []string{"noMatch"},
168176
Selector: metav1.LabelSelector{
@@ -189,6 +197,49 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
189197
},
190198
}
191199

200+
// Add test cases for cluster-scoped resources
201+
clusterScopedTestCases := []struct {
202+
name string
203+
allowedNs *infrav1.AllowedNamespaces
204+
expectedResult bool
205+
expectErr bool
206+
}{
207+
{
208+
name: "Cluster-scoped resource bypasses allowedNamespaces check when allowedNamespaces is nil",
209+
allowedNs: nil,
210+
expectedResult: true,
211+
expectErr: false,
212+
},
213+
{
214+
name: "Cluster-scoped resource bypasses allowedNamespaces check when allowedNamespaces restricts to specific namespace",
215+
allowedNs: &infrav1.AllowedNamespaces{
216+
NamespaceList: []string{"specific-namespace"},
217+
},
218+
expectedResult: true,
219+
expectErr: false,
220+
},
221+
}
222+
223+
for _, tc := range clusterScopedTestCases {
224+
testCases = append(testCases, struct {
225+
name string
226+
clusterNamespace string
227+
isClusterScoped bool
228+
allowedNs *infrav1.AllowedNamespaces
229+
setup func(*testing.T, client.Client)
230+
expectedResult bool
231+
expectErr bool
232+
}{
233+
name: tc.name,
234+
clusterNamespace: "", // cluster-scoped resources return empty namespace
235+
isClusterScoped: true,
236+
allowedNs: tc.allowedNs,
237+
setup: nil,
238+
expectedResult: tc.expectedResult,
239+
expectErr: tc.expectErr,
240+
})
241+
}
242+
192243
for _, tc := range testCases {
193244
t.Run(tc.name, func(t *testing.T) {
194245
g := NewWithT(t)
@@ -201,7 +252,7 @@ func TestIsClusterPermittedToUsePrincipal(t *testing.T) {
201252
if tc.setup != nil {
202253
tc.setup(t, k8sClient)
203254
}
204-
result, err := isClusterPermittedToUsePrincipal(k8sClient, tc.allowedNs, tc.clusterNamespace)
255+
result, err := isClusterPermittedToUsePrincipal(k8sClient, tc.allowedNs, tc.clusterNamespace, tc.isClusterScoped)
205256
if tc.expectErr {
206257
g.Expect(err).ToNot(BeNil())
207258
} else {

0 commit comments

Comments
 (0)