Skip to content

Commit da7b49b

Browse files
mtulioclaude
andcommitted
feat/aws/svc-nlb: Enable config to manage security group
Enforce CCM to manage Security Group by default for security compliance and best practices on Service type-loadBalancer when using Network Load Balancer (NLB). Fixes INI files with sections sorted co-authored by Claude. Co-Authored-By: Claude <[email protected]> Signed-off-by: Nolan Brubaker <[email protected]>
1 parent cdbcb81 commit da7b49b

File tree

3 files changed

+149
-8
lines changed

3 files changed

+149
-8
lines changed

pkg/cloud/aws/aws_config_transformer.go

Lines changed: 46 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,64 @@ 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+
// OpenShift enforces to CCM manage security group by default when deploying
135+
// Service type-LoadBalancer Network Load Balancer (NLB).
136+
klog.Infof("Enforcing cloud provider AWS configuration NLBSecurityGroupMode to Managed")
137+
cfg.Global.NLBSecurityGroupMode = awsconfig.NLBSecurityGroupModeManaged
138+
}
139+
}
99140
}

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()

0 commit comments

Comments
 (0)