Skip to content

Commit 31e89ae

Browse files
committed
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).
1 parent 274a6da commit 31e89ae

File tree

3 files changed

+131
-5
lines changed

3 files changed

+131
-5
lines changed

pkg/cloud/aws/aws_config_transformer.go

Lines changed: 28 additions & 2 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
}
@@ -89,11 +90,36 @@ func marshalAWSConfig(cfg *awsconfig.CloudConfig) (string, error) {
8990
return buf.String(), nil
9091
}
9192

92-
func setOpenShiftDefaults(cfg *awsconfig.CloudConfig) {
93+
// isFeatureGateEnabled safely checks if a feature gate is enabled without panicking
94+
// if the feature is not registered. Returns false if features is nil or if the
95+
// feature is not in the known features list.
96+
func isFeatureGateEnabled(features featuregates.FeatureGate, featureName string) bool {
97+
// features.Enabled returns panic if the feature is not registered in FeatureGates,
98+
// this functions prevents the panic by returning false if the feature is not registered in FeatureGates.
99+
if features == nil || len(featureName) == 0 {
100+
return false
101+
}
102+
for _, known := range features.KnownFeatures() {
103+
if string(known) == featureName {
104+
return features.Enabled(known)
105+
}
106+
}
107+
return false
108+
}
109+
110+
func setOpenShiftDefaults(cfg *awsconfig.CloudConfig, features featuregates.FeatureGate) {
93111
if cfg.Global.ClusterServiceLoadBalancerHealthProbeMode == "" {
94112
// OpenShift uses Shared mode by default.
95113
// This attaches the health check for Cluster scope services to the "kube-proxy"
96114
// health check endpoint served by OVN.
97115
cfg.Global.ClusterServiceLoadBalancerHealthProbeMode = "Shared"
98116
}
117+
if isFeatureGateEnabled(features, "AWSServiceLBNetworkSecurityGroup") {
118+
if cfg.Global.NLBSecurityGroupMode != awsconfig.NLBSecurityGroupModeManaged {
119+
// OpenShift enforces to CCM manage security group by default when deploying
120+
// Service type-LoadBalancer Network Load Balancer (NLB).
121+
klog.Infof("Enforcing cloud provider AWS configuration NLBSecurityGroupMode to Managed")
122+
cfg.Global.NLBSecurityGroupMode = awsconfig.NLBSecurityGroupModeManaged
123+
}
124+
}
99125
}

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)