-
Notifications
You must be signed in to change notification settings - Fork 460
✨ Allow more permissive extensibility for securityRules and additional CP LoadBalancers #5525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8812ec2
cf4c84b
cb32d14
b408363
51e7c19
04c4b55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,6 +266,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter { | |
| BackendPoolName: s.APIServerLB().BackendPool.Name, | ||
| IdleTimeoutInMinutes: s.APIServerLB().IdleTimeoutInMinutes, | ||
| AdditionalTags: s.AdditionalTags(), | ||
| AdditionalPorts: s.AdditionalAPIServerLBPorts(), | ||
| } | ||
|
|
||
| if s.APIServerLB().FrontendIPs != nil { | ||
|
|
@@ -299,6 +300,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter { | |
| BackendPoolName: s.APIServerLB().BackendPool.Name + "-internal", | ||
| IdleTimeoutInMinutes: s.APIServerLB().IdleTimeoutInMinutes, | ||
| AdditionalTags: s.AdditionalTags(), | ||
| AdditionalPorts: s.AdditionalAPIServerLBPorts(), | ||
| } | ||
|
|
||
| privateIPFound := false | ||
|
|
@@ -771,6 +773,11 @@ func (s *ClusterScope) ControlPlaneOutboundLB() *infrav1.LoadBalancerSpec { | |
| return s.AzureCluster.Spec.NetworkSpec.ControlPlaneOutboundLB | ||
| } | ||
|
|
||
| // AdditionalAPIServerLBPorts returns the additional API server ports list. | ||
| func (s *ClusterScope) AdditionalAPIServerLBPorts() []infrav1.LoadBalancerPort { | ||
| return s.AzureCluster.Spec.NetworkSpec.AdditionalAPIServerLBPorts | ||
| } | ||
|
|
||
| // APIServerLBName returns the API Server LB name. | ||
| func (s *ClusterScope) APIServerLBName() string { | ||
| apiServerLB := s.APIServerLB() | ||
|
|
@@ -1020,9 +1027,12 @@ func (s *ClusterScope) SetControlPlaneSecurityRules() { | |
| if !s.ControlPlaneEnabled() { | ||
| return | ||
| } | ||
| if s.ControlPlaneSubnet().SecurityGroup.SecurityRules == nil { | ||
| subnet := s.ControlPlaneSubnet() | ||
| subnet.SecurityGroup.SecurityRules = infrav1.SecurityRules{ | ||
|
|
||
| subnet := s.ControlPlaneSubnet() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in this flow where are we adding any user-provided security rules? (for example if a user specifies TCP or is that elsewhere and the purpose of this change is to filter out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flow performs only defaulting on top of the user-provided set of rules, which may not be empty. This way having a field populated does not always need to specify all allowed ports, only additional ones or overrides. I’m currently validating how this works in tandem with ClusterClass definitions we have. The desired goal is to permit specifying If it doesn’t work, maybe it would still require additional LB rules. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems reasonable to have the additional ports of the APIServer LB opened-up/have an opinion about. Could you please elaborate on why the security rules should not be updated too ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, maybe I’m not fully understanding. I will try to give my thoughts on this: I was attempting to describe the flow of how the rules are processed. We only apply defaults on top of what the user provides, so the user can still specify ports like TCP 9345 explicitly in security rules. We’re not overriding or stripping anything out, just adding fallback values when needed. LB rules were needed as well, since CP endpoint is external from the cluster perspective. Current set of changes is what was required to make cluster healthy, and added e2e test displays that. My previous comment was a set of assumptions, or “paths to explore later” if it doesn’t work. |
||
|
|
||
| missingSSH := subnet.GetSecurityRuleByDestination("22") == nil | ||
| if missingSSH { | ||
| subnet.SecurityGroup.SecurityRules = append(subnet.SecurityGroup.SecurityRules, | ||
| infrav1.SecurityRule{ | ||
| Name: "allow_ssh", | ||
| Description: "Allow SSH", | ||
|
|
@@ -1034,20 +1044,28 @@ func (s *ClusterScope) SetControlPlaneSecurityRules() { | |
| Destination: ptr.To("*"), | ||
| DestinationPorts: ptr.To("22"), | ||
| Action: infrav1.SecurityRuleActionAllow, | ||
| }, | ||
| infrav1.SecurityRule{ | ||
| Name: "allow_apiserver", | ||
| Description: "Allow K8s API Server", | ||
| Priority: 2201, | ||
| Protocol: infrav1.SecurityGroupProtocolTCP, | ||
| Direction: infrav1.SecurityRuleDirectionInbound, | ||
| Source: ptr.To("*"), | ||
| SourcePorts: ptr.To("*"), | ||
| Destination: ptr.To("*"), | ||
| DestinationPorts: ptr.To(strconv.Itoa(int(s.APIServerPort()))), | ||
| Action: infrav1.SecurityRuleActionAllow, | ||
| }, | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| port := strconv.Itoa(int(s.APIServerPort())) | ||
|
|
||
| missingAPIPort := subnet.GetSecurityRuleByDestination(port) == nil | ||
| if missingAPIPort { | ||
| subnet.SecurityGroup.SecurityRules = append(subnet.SecurityGroup.SecurityRules, infrav1.SecurityRule{ | ||
| Name: "allow_apiserver", | ||
| Description: "Allow K8s API Server", | ||
| Priority: 2201, | ||
| Protocol: infrav1.SecurityGroupProtocolTCP, | ||
| Direction: infrav1.SecurityRuleDirectionInbound, | ||
| Source: ptr.To("*"), | ||
| SourcePorts: ptr.To("*"), | ||
| Destination: ptr.To("*"), | ||
| DestinationPorts: ptr.To(port), | ||
| Action: infrav1.SecurityRuleActionAllow, | ||
| }) | ||
| } | ||
|
|
||
| if missingSSH || missingAPIPort { | ||
| s.AzureCluster.Spec.NetworkSpec.UpdateControlPlaneSubnet(subnet) | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -242,50 +242,165 @@ func TestAPIServerHost(t *testing.T) { | |
| } | ||
|
|
||
| func TestGettingSecurityRules(t *testing.T) { | ||
| g := NewWithT(t) | ||
|
|
||
| cluster := &clusterv1.Cluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-cluster", | ||
| Namespace: "default", | ||
| tests := []struct { | ||
| name string | ||
| cluster *clusterv1.Cluster | ||
| azureCluster *infrav1.AzureCluster | ||
| expectedRuleCount int | ||
|
Comment on lines
+246
to
+249
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for reformatting this! 🙌🏼 |
||
| }{ | ||
| { | ||
| name: "default control plane subnet with no rules should have 2 security rules defaulted", | ||
| cluster: &clusterv1.Cluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-cluster", | ||
| Namespace: "default", | ||
| }, | ||
| }, | ||
| azureCluster: &infrav1.AzureCluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-azure-cluster", | ||
| }, | ||
| Spec: infrav1.AzureClusterSpec{ | ||
| AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ | ||
| SubscriptionID: "123", | ||
| IdentityRef: &corev1.ObjectReference{ | ||
| Kind: infrav1.AzureClusterIdentityKind, | ||
| }, | ||
| }, | ||
| ControlPlaneEnabled: true, | ||
| NetworkSpec: infrav1.NetworkSpec{ | ||
| Subnets: infrav1.Subnets{ | ||
| { | ||
| SubnetClassSpec: infrav1.SubnetClassSpec{ | ||
| Role: infrav1.SubnetNode, | ||
| Name: "node", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| expectedRuleCount: 2, | ||
| }, | ||
| } | ||
|
|
||
| azureCluster := &infrav1.AzureCluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-azure-cluster", | ||
| { | ||
| name: "additional rules are preserved", | ||
| cluster: &clusterv1.Cluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-cluster", | ||
| Namespace: "default", | ||
| }, | ||
| }, | ||
| azureCluster: &infrav1.AzureCluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-azure-cluster", | ||
| }, | ||
| Spec: infrav1.AzureClusterSpec{ | ||
| AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ | ||
| SubscriptionID: "123", | ||
| IdentityRef: &corev1.ObjectReference{ | ||
| Kind: infrav1.AzureClusterIdentityKind, | ||
| }, | ||
| }, | ||
| ControlPlaneEnabled: true, | ||
| NetworkSpec: infrav1.NetworkSpec{ | ||
| Subnets: infrav1.Subnets{ | ||
| { | ||
| SecurityGroup: infrav1.SecurityGroup{ | ||
| SecurityGroupClass: infrav1.SecurityGroupClass{ | ||
| SecurityRules: []infrav1.SecurityRule{{ | ||
| Name: "allow_9345", | ||
| Description: "Allow port 9345", | ||
| Priority: 2200, | ||
| Protocol: infrav1.SecurityGroupProtocolTCP, | ||
| Direction: infrav1.SecurityRuleDirectionInbound, | ||
| Source: ptr.To("*"), | ||
| SourcePorts: ptr.To("*"), | ||
| Destination: ptr.To("*"), | ||
| DestinationPorts: ptr.To("9345"), | ||
| Action: infrav1.SecurityRuleActionAllow, | ||
| }}, | ||
| }, | ||
| }, | ||
| SubnetClassSpec: infrav1.SubnetClassSpec{ | ||
| Role: infrav1.SubnetControlPlane, | ||
| Name: string(infrav1.SubnetControlPlane), | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| expectedRuleCount: 3, | ||
| }, | ||
| Spec: infrav1.AzureClusterSpec{ | ||
| AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ | ||
| SubscriptionID: "123", | ||
| IdentityRef: &corev1.ObjectReference{ | ||
| Kind: infrav1.AzureClusterIdentityKind, | ||
| { | ||
| name: "override rules are accepted", | ||
| cluster: &clusterv1.Cluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-cluster", | ||
| Namespace: "default", | ||
| }, | ||
| }, | ||
| ControlPlaneEnabled: true, | ||
| NetworkSpec: infrav1.NetworkSpec{ | ||
| Subnets: infrav1.Subnets{ | ||
| { | ||
| SubnetClassSpec: infrav1.SubnetClassSpec{ | ||
| Role: infrav1.SubnetNode, | ||
| Name: "node", | ||
| azureCluster: &infrav1.AzureCluster{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-azure-cluster", | ||
| }, | ||
| Spec: infrav1.AzureClusterSpec{ | ||
| AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ | ||
| SubscriptionID: "123", | ||
| IdentityRef: &corev1.ObjectReference{ | ||
| Kind: infrav1.AzureClusterIdentityKind, | ||
| }, | ||
| }, | ||
| ControlPlaneEnabled: true, | ||
| NetworkSpec: infrav1.NetworkSpec{ | ||
| Subnets: infrav1.Subnets{ | ||
| { | ||
| SecurityGroup: infrav1.SecurityGroup{ | ||
| SecurityGroupClass: infrav1.SecurityGroupClass{ | ||
| SecurityRules: []infrav1.SecurityRule{{ | ||
| Name: "deny_ssh", | ||
| Description: "Deny SSH", | ||
| Priority: 2200, | ||
| Protocol: infrav1.SecurityGroupProtocolTCP, | ||
| Direction: infrav1.SecurityRuleDirectionInbound, | ||
| Source: ptr.To("*"), | ||
| SourcePorts: ptr.To("*"), | ||
| Destination: ptr.To("*"), | ||
| DestinationPorts: ptr.To("22"), | ||
| Action: infrav1.SecurityRuleActionDeny, | ||
| }}, | ||
| }, | ||
| }, | ||
| SubnetClassSpec: infrav1.SubnetClassSpec{ | ||
| Role: infrav1.SubnetControlPlane, | ||
| Name: string(infrav1.SubnetControlPlane), | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| expectedRuleCount: 2, | ||
| }, | ||
| } | ||
| azureCluster.Default() | ||
|
|
||
| clusterScope := &ClusterScope{ | ||
| Cluster: cluster, | ||
| AzureCluster: azureCluster, | ||
| } | ||
| clusterScope.SetControlPlaneSecurityRules() | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| g := NewWithT(t) | ||
|
|
||
| subnet, err := clusterScope.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet() | ||
| g.Expect(err).NotTo(HaveOccurred()) | ||
| g.Expect(subnet.SecurityGroup.SecurityRules).To(HaveLen(2)) | ||
| tt.azureCluster.Default() | ||
|
|
||
| clusterScope := &ClusterScope{ | ||
| Cluster: tt.cluster, | ||
| AzureCluster: tt.azureCluster, | ||
| } | ||
| clusterScope.SetControlPlaneSecurityRules() | ||
|
|
||
| subnet, err := clusterScope.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet() | ||
| g.Expect(err).NotTo(HaveOccurred()) | ||
| g.Expect(subnet.SecurityGroup.SecurityRules).To(HaveLen(tt.expectedRuleCount)) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestPublicIPSpecs(t *testing.T) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably for the long term, we should change
s.SecurityGroup.SecurityRulesto a dictionary type for faster processing.