Skip to content

Commit 6148c0c

Browse files
Merge pull request #391 from mtulio/SPLAT-2253
SPLAT-2253: CCM-AWS config enforce to provision Service NLB with SG under gate
2 parents 8259332 + 259a2dd commit 6148c0c

7 files changed

Lines changed: 180 additions & 12 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ require (
2222
k8s.io/apiextensions-apiserver v0.34.1
2323
k8s.io/apimachinery v0.34.1
2424
k8s.io/client-go v0.34.1
25-
k8s.io/cloud-provider-aws v1.34.1
25+
k8s.io/cloud-provider-aws v1.34.1-0.20250912204608-8a0025b4efb1
2626
k8s.io/cloud-provider-vsphere v1.34.0
2727
k8s.io/component-base v0.34.1
2828
k8s.io/controller-manager v0.34.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,8 +753,8 @@ k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA=
753753
k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0=
754754
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
755755
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
756-
k8s.io/cloud-provider-aws v1.34.1 h1:IbVH3Yg5QUrB6Uz0x/pZIP6GcmUB58FbZXPFUzfki6c=
757-
k8s.io/cloud-provider-aws v1.34.1/go.mod h1:a8p1e6RHviJmZ/ZJK9S26CpZ07uv/jCZa93opvKSDA8=
756+
k8s.io/cloud-provider-aws v1.34.1-0.20250912204608-8a0025b4efb1 h1:yXrYREaxoMix9I+xfgZxwgvRhr7vs0iZJGrvELXYpik=
757+
k8s.io/cloud-provider-aws v1.34.1-0.20250912204608-8a0025b4efb1/go.mod h1:a8p1e6RHviJmZ/ZJK9S26CpZ07uv/jCZa93opvKSDA8=
758758
k8s.io/cloud-provider-vsphere v1.34.0 h1:XVn67iOmwld6pQI6DGCxwiA515VsHofIOUIwaVN8VLo=
759759
k8s.io/cloud-provider-vsphere v1.34.0/go.mod h1:W+o/i/LjkiXU3yNt8fXcoY97zroeOUfCk0vWtXfUJAQ=
760760
k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A=

pkg/cloud/aws/aws_config_transformer.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"gopkg.in/ini.v1"
1212

1313
awsconfig "k8s.io/cloud-provider-aws/pkg/providers/v1/config"
14+
"k8s.io/klog/v2"
1415

1516
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
1617
)
@@ -28,7 +29,7 @@ func CloudConfigTransformer(source string, infra *configv1.Infrastructure, netwo
2829
return "", fmt.Errorf("failed to read the cloud.conf: %w", err)
2930
}
3031

31-
setOpenShiftDefaults(cfg)
32+
setOpenShiftDefaults(cfg, features)
3233

3334
return marshalAWSConfig(cfg)
3435
}
@@ -76,24 +77,65 @@ func marshalAWSConfig(cfg *awsconfig.CloudConfig) (string, error) {
7677
}
7778

7879
// Ensure service override sections are last and ordered numerically.
79-
sort.Slice(file.Sections(), func(i, j int) bool {
80-
return file.Sections()[i].Name() < file.Sections()[j].Name()
80+
// We need to create a new file with sorted sections because file.Sections()
81+
// returns a new slice each time, so sorting it doesn't modify the original.
82+
sections := file.Sections()
83+
sort.Slice(sections, func(i, j int) bool {
84+
return sections[i].Name() < sections[j].Name()
8185
})
8286

87+
// Create a new INI file with sections in sorted order
88+
sortedFile := ini.Empty()
89+
for _, section := range sections {
90+
newSection, err := sortedFile.NewSection(section.Name())
91+
if err != nil {
92+
return "", fmt.Errorf("failed to create section: %w", err)
93+
}
94+
for _, key := range section.Keys() {
95+
newSection.Key(key.Name()).SetValue(key.Value())
96+
}
97+
}
98+
8399
buf := &bytes.Buffer{}
84100

85-
if _, err := file.WriteTo(buf); err != nil {
101+
if _, err := sortedFile.WriteTo(buf); err != nil {
86102
return "", fmt.Errorf("failed to write INI file: %w", err)
87103
}
88104

89105
return buf.String(), nil
90106
}
91107

92-
func setOpenShiftDefaults(cfg *awsconfig.CloudConfig) {
108+
// isFeatureGateEnabled safely checks if a feature gate is enabled without panicking
109+
// if the feature is not registered. Returns false if features is nil or if the
110+
// feature is not in the known features list.
111+
func isFeatureGateEnabled(features featuregates.FeatureGate, featureName string) bool {
112+
// features.Enabled returns panic if the feature is not registered in FeatureGates,
113+
// this functions prevents the panic by returning false if the feature is not registered in FeatureGates.
114+
if features == nil || len(featureName) == 0 {
115+
return false
116+
}
117+
for _, known := range features.KnownFeatures() {
118+
if string(known) == featureName {
119+
return features.Enabled(known)
120+
}
121+
}
122+
return false
123+
}
124+
125+
func setOpenShiftDefaults(cfg *awsconfig.CloudConfig, features featuregates.FeatureGate) {
93126
if cfg.Global.ClusterServiceLoadBalancerHealthProbeMode == "" {
94127
// OpenShift uses Shared mode by default.
95128
// This attaches the health check for Cluster scope services to the "kube-proxy"
96129
// health check endpoint served by OVN.
97130
cfg.Global.ClusterServiceLoadBalancerHealthProbeMode = "Shared"
98131
}
132+
if isFeatureGateEnabled(features, "AWSServiceLBNetworkSecurityGroup") {
133+
if cfg.Global.NLBSecurityGroupMode != awsconfig.NLBSecurityGroupModeManaged {
134+
// When the feature gate AWSServiceLBNetworkSecurityGroup is enabled,
135+
// OpenShift configures the AWS CCM to manage security groups for
136+
// Network Load Balancer (NLB) Services.
137+
klog.Infof("Enforcing cloud provider AWS configuration NLBSecurityGroupMode to Managed")
138+
cfg.Global.NLBSecurityGroupMode = awsconfig.NLBSecurityGroupModeManaged
139+
}
140+
}
99141
}

pkg/cloud/aws/aws_config_transformer_test.go

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,81 @@ import (
55

66
. "github.com/onsi/gomega"
77

8+
configv1 "github.com/openshift/api/config/v1"
9+
810
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
911
)
1012

13+
var mockEmptyFeatureGates = featuregates.NewFeatureGate([]configv1.FeatureGateName{}, []configv1.FeatureGateName{})
14+
var mockEnabledFeatureGates = featuregates.NewFeatureGate([]configv1.FeatureGateName{"AWSServiceLBNetworkSecurityGroup"}, []configv1.FeatureGateName{})
15+
var mockDisabledFeatureGates = featuregates.NewFeatureGate([]configv1.FeatureGateName{}, []configv1.FeatureGateName{"AWSServiceLBNetworkSecurityGroup"})
16+
17+
func TestIsFeatureGateEnabled(t *testing.T) {
18+
testCases := []struct {
19+
name string
20+
features featuregates.FeatureGate
21+
featureName string
22+
expected bool
23+
}{
24+
{
25+
name: "returns false when features is nil",
26+
features: nil,
27+
featureName: "AWSServiceLBNetworkSecurityGroup",
28+
expected: false,
29+
},
30+
{
31+
name: "returns false when feature name is empty string",
32+
features: mockEnabledFeatureGates,
33+
featureName: "",
34+
expected: false,
35+
},
36+
{
37+
name: "returns false when feature is not registered (empty feature gate)",
38+
features: mockEmptyFeatureGates,
39+
featureName: "AWSServiceLBNetworkSecurityGroup",
40+
expected: false,
41+
},
42+
{
43+
name: "returns true when feature is enabled",
44+
features: mockEnabledFeatureGates,
45+
featureName: "AWSServiceLBNetworkSecurityGroup",
46+
expected: true,
47+
},
48+
{
49+
name: "returns false when feature is explicitly disabled",
50+
features: mockDisabledFeatureGates,
51+
featureName: "AWSServiceLBNetworkSecurityGroup",
52+
expected: false,
53+
},
54+
{
55+
name: "returns false when feature is not in known features list",
56+
features: featuregates.NewFeatureGate([]configv1.FeatureGateName{"SomeOtherFeature"}, []configv1.FeatureGateName{}),
57+
featureName: "AWSServiceLBNetworkSecurityGroup",
58+
expected: false,
59+
},
60+
{
61+
name: "returns false for unknown feature with disabled features registered",
62+
features: featuregates.NewFeatureGate([]configv1.FeatureGateName{}, []configv1.FeatureGateName{"SomeOtherFeature"}),
63+
featureName: "AWSServiceLBNetworkSecurityGroup",
64+
expected: false,
65+
},
66+
}
67+
68+
for _, tc := range testCases {
69+
t.Run(tc.name, func(t *testing.T) {
70+
g := NewWithT(t)
71+
result := isFeatureGateEnabled(tc.features, tc.featureName)
72+
g.Expect(result).To(Equal(tc.expected), "Expected isFeatureGateEnabled to return %v for feature '%s'", tc.expected, tc.featureName)
73+
})
74+
}
75+
}
76+
1177
func TestCloudConfigTransformer(t *testing.T) {
1278
testCases := []struct {
1379
name string
1480
source string
1581
expected string
82+
features featuregates.FeatureGate
1683
}{
1784
{
1885
name: "default source",
@@ -23,6 +90,7 @@ DisableSecurityGroupIngress = false
2390
ClusterServiceLoadBalancerHealthProbeMode = Shared
2491
ClusterServiceSharedLoadBalancerHealthProbePort = 0
2592
`,
93+
features: mockEmptyFeatureGates,
2694
},
2795
{
2896
name: "completely empty source",
@@ -32,6 +100,7 @@ DisableSecurityGroupIngress = false
32100
ClusterServiceLoadBalancerHealthProbeMode = Shared
33101
ClusterServiceSharedLoadBalancerHealthProbePort = 0
34102
`,
103+
features: mockEmptyFeatureGates,
35104
},
36105
{
37106
name: "with existing configuration",
@@ -45,6 +114,7 @@ DisableSecurityGroupIngress = true
45114
ClusterServiceLoadBalancerHealthProbeMode = Shared
46115
ClusterServiceSharedLoadBalancerHealthProbePort = 0
47116
`, // Ordered based on the order of fields in the AWS CloudConfig struct.
117+
features: mockEmptyFeatureGates,
48118
},
49119
{
50120
name: "with existing configuration and overrides",
@@ -82,14 +152,44 @@ Region = us-west-1
82152
URL = https://s3.foo.bar
83153
SigningRegion = signing_region
84154
`, // Ordered based on the order of fields in the AWS CloudConfig struct.
155+
features: mockEmptyFeatureGates,
156+
},
157+
{
158+
name: "with AWSServiceLBNetworkSecurityGroup feature gate enabled",
159+
source: `[Global]
160+
DisableSecurityGroupIngress = true
161+
Zone = Foo
162+
`,
163+
expected: `[Global]
164+
Zone = Foo
165+
DisableSecurityGroupIngress = true
166+
ClusterServiceLoadBalancerHealthProbeMode = Shared
167+
ClusterServiceSharedLoadBalancerHealthProbePort = 0
168+
NLBSecurityGroupMode = Managed
169+
`,
170+
features: mockEnabledFeatureGates,
171+
},
172+
{
173+
name: "with AWSServiceLBNetworkSecurityGroup feature gate disabled",
174+
source: `[Global]
175+
DisableSecurityGroupIngress = true
176+
Zone = Foo
177+
`,
178+
expected: `[Global]
179+
Zone = Foo
180+
DisableSecurityGroupIngress = true
181+
ClusterServiceLoadBalancerHealthProbeMode = Shared
182+
ClusterServiceSharedLoadBalancerHealthProbePort = 0
183+
`,
184+
features: mockDisabledFeatureGates,
85185
},
86186
}
87187

88188
for _, tc := range testCases {
89189
t.Run(tc.name, func(t *testing.T) {
90190
g := NewWithT(t)
91191

92-
gotConfig, err := CloudConfigTransformer(tc.source, nil, nil, featuregates.NewFeatureGate(nil, nil)) // No Infra or Network are required for the current functionality.
192+
gotConfig, err := CloudConfigTransformer(tc.source, nil, nil, tc.features) // No Infra or Network are required for the current functionality.
93193
g.Expect(err).ToNot(HaveOccurred())
94194

95195
g.Expect(gotConfig).To(Equal(tc.expected))

pkg/controllers/cloud_config_sync_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ var _ = Describe("Cloud config sync controller", func() {
206206
ManagedNamespace: targetNamespaceName,
207207
},
208208
Scheme: scheme.Scheme,
209-
FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, nil, nil),
209+
FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, []configv1.FeatureGateName{"AWSServiceLBNetworkSecurityGroup"}, nil, nil),
210210
}
211211
Expect(reconciler.SetupWithManager(mgr)).To(Succeed())
212212

@@ -408,7 +408,7 @@ var _ = Describe("Cloud config sync reconciler", func() {
408408
ManagedNamespace: targetNamespaceName,
409409
},
410410
Scheme: scheme.Scheme,
411-
FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, nil, nil),
411+
FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, []configv1.FeatureGateName{"AWSServiceLBNetworkSecurityGroup"}, nil, nil),
412412
}
413413

414414
networkResource := makeNetworkResource()

vendor/k8s.io/cloud-provider-aws/pkg/providers/v1/config/config.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2097,7 +2097,7 @@ k8s.io/client-go/util/homedir
20972097
k8s.io/client-go/util/keyutil
20982098
k8s.io/client-go/util/retry
20992099
k8s.io/client-go/util/workqueue
2100-
# k8s.io/cloud-provider-aws v1.34.1
2100+
# k8s.io/cloud-provider-aws v1.34.1-0.20250912204608-8a0025b4efb1
21012101
## explicit; go 1.24.4
21022102
k8s.io/cloud-provider-aws/pkg/providers/v1/config
21032103
# k8s.io/cloud-provider-vsphere v1.34.0

0 commit comments

Comments
 (0)