diff --git a/cloudmock/aws/mockelbv2/loadbalancers.go b/cloudmock/aws/mockelbv2/loadbalancers.go index 5a72384cf9..142c1aadef 100644 --- a/cloudmock/aws/mockelbv2/loadbalancers.go +++ b/cloudmock/aws/mockelbv2/loadbalancers.go @@ -83,6 +83,7 @@ func (m *MockELBV2) CreateLoadBalancer(request *elbv2.CreateLoadBalancerInput) ( lb := elbv2.LoadBalancer{ LoadBalancerName: request.Name, Scheme: request.Scheme, + SecurityGroups: request.SecurityGroups, Type: request.Type, IpAddressType: request.IpAddressType, DNSName: aws.String(fmt.Sprintf("%v.amazonaws.com", aws.StringValue(request.Name))), @@ -194,6 +195,19 @@ func (m *MockELBV2) ModifyLoadBalancerAttributes(request *elbv2.ModifyLoadBalanc return nil, fmt.Errorf("LoadBalancerNotFound: %v", aws.StringValue(request.LoadBalancerArn)) } +func (m *MockELBV2) SetSecurityGroups(request *elbv2.SetSecurityGroupsInput) (*elbv2.SetSecurityGroupsOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + arn := aws.StringValue(request.LoadBalancerArn) + if lb, ok := m.LoadBalancers[arn]; ok { + lb.description.SecurityGroups = request.SecurityGroups + return &elbv2.SetSecurityGroupsOutput{ + SecurityGroupIds: request.SecurityGroups, + }, nil + } + return nil, fmt.Errorf("LoadBalancerNotFound: %v", aws.StringValue(request.LoadBalancerArn)) +} + func (m *MockELBV2) SetSubnets(request *elbv2.SetSubnetsInput) (*elbv2.SetSubnetsOutput, error) { m.mutex.Lock() defer m.mutex.Unlock() diff --git a/docs/releases/1.29-NOTES.md b/docs/releases/1.29-NOTES.md index af92e27ae7..b4b57358a5 100644 --- a/docs/releases/1.29-NOTES.md +++ b/docs/releases/1.29-NOTES.md @@ -8,6 +8,11 @@ This is a document to gather the release notes prior to the release. ## AWS +* Network Load Balancers in front of the Kubernetes API and bastion hosts now +have a security group attached. These security groups are used for security group rules +allowing incoming traffic to the NLBs as well as traffic between the NLBs and their target +instances. + ## GCP ## Openstack diff --git a/pkg/model/awsmodel/api_loadbalancer.go b/pkg/model/awsmodel/api_loadbalancer.go index f4bd617321..844b356cc4 100644 --- a/pkg/model/awsmodel/api_loadbalancer.go +++ b/pkg/model/awsmodel/api_loadbalancer.go @@ -19,7 +19,6 @@ package awsmodel import ( "fmt" "sort" - "strings" "time" "k8s.io/apimachinery/pkg/util/sets" @@ -28,7 +27,6 @@ import ( "k8s.io/kops/pkg/wellknownports" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awstasks" - "k8s.io/kops/upup/pkg/fi/utils" ) // LoadBalancerDefaultIdleTimeout is the default idle time for the ELB @@ -182,9 +180,12 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { LoadBalancerName: fi.PtrTo(loadBalancerName), CLBName: fi.PtrTo("api." + b.ClusterName()), - SubnetMappings: nlbSubnetMappings, - Listeners: nlbListeners, - TargetGroups: make([]*awstasks.TargetGroup, 0), + SecurityGroups: []*awstasks.SecurityGroup{ + b.LinkToELBSecurityGroup("api"), + }, + SubnetMappings: nlbSubnetMappings, + Listeners: nlbListeners, + TargetGroups: make([]*awstasks.TargetGroup, 0), Tags: tags, ForAPIServer: true, @@ -377,7 +378,7 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { } // Allow traffic from ELB to egress freely - if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic { + { { t := &awstasks.SecurityGroupRule{ Name: fi.PtrTo("ipv4-api-elb-egress"), @@ -401,7 +402,7 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { } // Allow traffic into the ELB from KubernetesAPIAccess CIDRs - if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic { + { for _, cidr := range b.Cluster.Spec.API.Access { { t := &awstasks.SecurityGroupRule{ @@ -417,26 +418,33 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { } // Allow ICMP traffic required for PMTU discovery - if utils.IsIPv6CIDR(cidr) { - c.AddTask(&awstasks.SecurityGroupRule{ + { + t := &awstasks.SecurityGroupRule{ Name: fi.PtrTo("icmpv6-pmtu-api-elb-" + cidr), Lifecycle: b.SecurityLifecycle, - IPv6CIDR: fi.PtrTo(cidr), FromPort: fi.PtrTo(int64(-1)), Protocol: fi.PtrTo("icmpv6"), SecurityGroup: lbSG, ToPort: fi.PtrTo(int64(-1)), - }) - } else { - c.AddTask(&awstasks.SecurityGroupRule{ + } + t.SetCidrOrPrefix(cidr) + if t.CIDR == nil { + c.AddTask(t) + } + } + { + t := &awstasks.SecurityGroupRule{ Name: fi.PtrTo("icmp-pmtu-api-elb-" + cidr), Lifecycle: b.SecurityLifecycle, - CIDR: fi.PtrTo(cidr), FromPort: fi.PtrTo(int64(3)), Protocol: fi.PtrTo("icmp"), SecurityGroup: lbSG, ToPort: fi.PtrTo(int64(4)), - }) + } + t.SetCidrOrPrefix(cidr) + if t.IPv6CIDR == nil { + c.AddTask(t) + } } } } @@ -446,69 +454,25 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { return err } - if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork { - for _, cidr := range b.Cluster.Spec.API.Access { - for _, masterGroup := range masterGroups { - { - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("https-api-elb-%s", cidr)), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(443)), - Protocol: fi.PtrTo("tcp"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(443)), - } - t.SetCidrOrPrefix(cidr) - AddDirectionalGroupRule(c, t) - } - - if strings.HasPrefix(cidr, "pl-") { - // In case of a prefix list we do not add a rule for ICMP traffic for PMTU discovery. - // This would require calling out to AWS to check whether the prefix list is IPv4 or IPv6. - } else if utils.IsIPv6CIDR(cidr) { - // Allow ICMP traffic required for PMTU discovery - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo("icmpv6-pmtu-api-elb-" + cidr), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(-1)), - Protocol: fi.PtrTo("icmpv6"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(-1)), - } - t.SetCidrOrPrefix(cidr) - c.AddTask(t) - } else { - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo("icmp-pmtu-api-elb-" + cidr), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(3)), - Protocol: fi.PtrTo("icmp"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(4)), - } - t.SetCidrOrPrefix(cidr) - c.AddTask(t) - } - - if b.Cluster.Spec.API.LoadBalancer != nil && b.Cluster.Spec.API.LoadBalancer.SSLCertificate != "" { - // Allow access to masters on secondary port through NLB - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("tcp-api-%s", cidr)), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(8443)), - Protocol: fi.PtrTo("tcp"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(8443)), - } - t.SetCidrOrPrefix(cidr) - c.AddTask(t) - } + if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork && b.Cluster.Spec.API.LoadBalancer != nil && b.Cluster.Spec.API.LoadBalancer.SSLCertificate != "" { + for _, masterGroup := range masterGroups { + suffix := masterGroup.Suffix + // Allow access to control plane on secondary port through NLB + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo(fmt.Sprintf("tcp-api-cp%s", suffix)), + Lifecycle: b.SecurityLifecycle, + FromPort: fi.PtrTo(int64(8443)), + Protocol: fi.PtrTo("tcp"), + SecurityGroup: masterGroup.Task, + SourceGroup: lbSG, + ToPort: fi.PtrTo(int64(8443)), } + c.AddTask(t) } } // Add precreated additional security groups to the ELB - if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic { + { for _, id := range b.Cluster.Spec.API.LoadBalancer.AdditionalSecurityGroups { t := &awstasks.SecurityGroup{ Name: fi.PtrTo(id), @@ -518,11 +482,12 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { } c.EnsureTask(t) clb.SecurityGroups = append(clb.SecurityGroups, t) + nlb.SecurityGroups = append(nlb.SecurityGroups, t) } } - // Allow HTTPS to the master instances from the ELB - if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic { + // Allow HTTPS to the control-plane instances from the ELB + { for _, masterGroup := range masterGroups { suffix := masterGroup.Suffix c.AddTask(&awstasks.SecurityGroupRule{ @@ -534,57 +499,24 @@ func (b *APILoadBalancerBuilder) Build(c *fi.CloudupModelBuilderContext) error { SourceGroup: lbSG, ToPort: fi.PtrTo(int64(443)), }) - } - } else if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork { - for _, masterGroup := range masterGroups { - suffix := masterGroup.Suffix c.AddTask(&awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("https-elb-to-master%s", suffix)), + Name: fi.PtrTo(fmt.Sprintf("icmp-pmtu-elb-to-cp%s", suffix)), Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(443)), - Protocol: fi.PtrTo("tcp"), + FromPort: fi.PtrTo(int64(3)), + Protocol: fi.PtrTo("icmp"), SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(443)), - CIDR: fi.PtrTo(b.Cluster.Spec.Networking.NetworkCIDR), + SourceGroup: lbSG, + ToPort: fi.PtrTo(int64(4)), }) - for _, cidr := range b.Cluster.Spec.Networking.AdditionalNetworkCIDRs { - c.AddTask(&awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("https-lb-to-master%s-%s", suffix, cidr)), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(443)), - Protocol: fi.PtrTo("tcp"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(443)), - CIDR: fi.PtrTo(cidr), - }) - } - } - } - - // Allow kops-controller to the master instances from the ELB - if b.Cluster.UsesNoneDNS() && b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork { - for _, masterGroup := range masterGroups { - suffix := masterGroup.Suffix c.AddTask(&awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("kops-controller-lb-to-master%s", suffix)), + Name: fi.PtrTo(fmt.Sprintf("icmp-pmtu-cp%s-to-elb", suffix)), Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(wellknownports.KopsControllerPort)), - Protocol: fi.PtrTo("tcp"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(wellknownports.KopsControllerPort)), - CIDR: fi.PtrTo(b.Cluster.Spec.Networking.NetworkCIDR), + FromPort: fi.PtrTo(int64(3)), + Protocol: fi.PtrTo("icmp"), + SecurityGroup: lbSG, + SourceGroup: masterGroup.Task, + ToPort: fi.PtrTo(int64(4)), }) - for _, cidr := range b.Cluster.Spec.Networking.AdditionalNetworkCIDRs { - c.AddTask(&awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("kops-controller-lb-to-master%s-%s", suffix, cidr)), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(wellknownports.KopsControllerPort)), - Protocol: fi.PtrTo("tcp"), - SecurityGroup: masterGroup.Task, - ToPort: fi.PtrTo(int64(wellknownports.KopsControllerPort)), - CIDR: fi.PtrTo(cidr), - }) - } } } diff --git a/pkg/model/awsmodel/bastion.go b/pkg/model/awsmodel/bastion.go index d9472eebff..a21281f18c 100644 --- a/pkg/model/awsmodel/bastion.go +++ b/pkg/model/awsmodel/bastion.go @@ -19,21 +19,19 @@ package awsmodel import ( "fmt" "sort" - "strings" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awstasks" - "k8s.io/kops/upup/pkg/fi/utils" ) // BastionModelBuilder adds model objects to support bastions // // Bastion instances live in the utility subnets created in the private topology. // All traffic goes through an ELB, and the ELB has port 22 open to SSHAccess. -// Bastion instances have access to all internal master and node instances. +// Bastion instances have access to all internal control-plane and node instances. type BastionModelBuilder struct { *AWSModelContext @@ -122,7 +120,7 @@ func (b *BastionModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { } } - // Allow bastion nodes to SSH to masters + // Allow bastion nodes to SSH to control plane for _, src := range bastionGroups { for _, dest := range masterGroups { t := &awstasks.SecurityGroupRule{ @@ -154,6 +152,44 @@ func (b *BastionModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { } } + var lbSG *awstasks.SecurityGroup + { + lbSG = &awstasks.SecurityGroup{ + Name: fi.PtrTo(b.ELBSecurityGroupName("bastion")), + Lifecycle: b.SecurityLifecycle, + Description: fi.PtrTo("Security group for bastion ELB"), + RemoveExtraRules: []string{"port=22"}, + VPC: b.LinkToVPC(), + } + lbSG.Tags = b.CloudTags(*lbSG.Name, false) + + c.AddTask(lbSG) + } + + // Allow traffic from NLB to egress freely + { + { + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo("ipv4-bastion-elb-egress"), + Lifecycle: b.SecurityLifecycle, + CIDR: fi.PtrTo("0.0.0.0/0"), + Egress: fi.PtrTo(true), + SecurityGroup: lbSG, + } + AddDirectionalGroupRule(c, t) + } + { + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo("ipv6-bastion-elb-egress"), + Lifecycle: b.SecurityLifecycle, + IPv6CIDR: fi.PtrTo("::/0"), + Egress: fi.PtrTo(true), + SecurityGroup: lbSG, + } + AddDirectionalGroupRule(c, t) + } + } + var sshAllowedCIDRs []string var nlbSubnetMappings []*awstasks.SubnetMapping { @@ -191,50 +227,93 @@ func (b *BastionModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { sshAllowedCIDRs = append(sshAllowedCIDRs, b.Cluster.Spec.SSHAccess...) for _, cidr := range sshAllowedCIDRs { - // Allow incoming SSH traffic to bastions, through the NLB + // Allow incoming SSH traffic to the NLB // TODO: Could we get away without an NLB here? Tricky to fix if dns-controller breaks though... - for _, bastionGroup := range bastionGroups { - { - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo(fmt.Sprintf("ssh-nlb-%s", cidr)), - Lifecycle: b.SecurityLifecycle, - SecurityGroup: bastionGroup.Task, - Protocol: fi.PtrTo("tcp"), - FromPort: fi.PtrTo(int64(22)), - ToPort: fi.PtrTo(int64(22)), - } - t.SetCidrOrPrefix(cidr) - AddDirectionalGroupRule(c, t) + { + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo(fmt.Sprintf("ssh-nlb-%s", cidr)), + Lifecycle: b.SecurityLifecycle, + SecurityGroup: lbSG, + Protocol: fi.PtrTo("tcp"), + FromPort: fi.PtrTo(int64(22)), + ToPort: fi.PtrTo(int64(22)), } + t.SetCidrOrPrefix(cidr) + AddDirectionalGroupRule(c, t) + } - if strings.HasPrefix(cidr, "pl-") { - // In case of a prefix list we do not add a rule for ICMP traffic for PMTU discovery. - // This would require calling out to AWS to check whether the prefix list is IPv4 or IPv6. - } else if utils.IsIPv6CIDR(cidr) { - // Allow ICMP traffic required for PMTU discovery - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo("icmpv6-pmtu-ssh-nlb-" + cidr), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(-1)), - Protocol: fi.PtrTo("icmpv6"), - SecurityGroup: bastionGroup.Task, - ToPort: fi.PtrTo(int64(-1)), - } - t.SetCidrOrPrefix(cidr) - c.AddTask(t) - } else { - t := &awstasks.SecurityGroupRule{ - Name: fi.PtrTo("icmp-pmtu-ssh-nlb-" + cidr), - Lifecycle: b.SecurityLifecycle, - FromPort: fi.PtrTo(int64(3)), - Protocol: fi.PtrTo("icmp"), - SecurityGroup: bastionGroup.Task, - ToPort: fi.PtrTo(int64(4)), - } - t.SetCidrOrPrefix(cidr) + // Allow ICMP traffic required for PMTU discovery + { + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo("icmpv6-pmtu-ssh-nlb-" + cidr), + Lifecycle: b.SecurityLifecycle, + FromPort: fi.PtrTo(int64(-1)), + Protocol: fi.PtrTo("icmpv6"), + SecurityGroup: lbSG, + ToPort: fi.PtrTo(int64(-1)), + } + t.SetCidrOrPrefix(cidr) + if t.CIDR == nil { c.AddTask(t) } } + { + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo("icmp-pmtu-ssh-nlb-" + cidr), + Lifecycle: b.SecurityLifecycle, + FromPort: fi.PtrTo(int64(3)), + Protocol: fi.PtrTo("icmp"), + SecurityGroup: lbSG, + ToPort: fi.PtrTo(int64(4)), + } + t.SetCidrOrPrefix(cidr) + if t.IPv6CIDR == nil { + c.AddTask(t) + } + } + } + + // Allow SSH to the bastion instances from the NLB + for _, bastionGroup := range bastionGroups { + { + suffix := bastionGroup.Suffix + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo(fmt.Sprintf("ssh-to-bastion%s", suffix)), + Lifecycle: b.SecurityLifecycle, + SecurityGroup: bastionGroup.Task, + SourceGroup: lbSG, + Protocol: fi.PtrTo("tcp"), + FromPort: fi.PtrTo(int64(22)), + ToPort: fi.PtrTo(int64(22)), + } + AddDirectionalGroupRule(c, t) + } + { + suffix := bastionGroup.Suffix + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo(fmt.Sprintf("icmp-to-bastion%s", suffix)), + Lifecycle: b.SecurityLifecycle, + SecurityGroup: bastionGroup.Task, + SourceGroup: lbSG, + Protocol: fi.PtrTo("icmp"), + FromPort: fi.PtrTo(int64(3)), + ToPort: fi.PtrTo(int64(4)), + } + AddDirectionalGroupRule(c, t) + } + { + suffix := bastionGroup.Suffix + t := &awstasks.SecurityGroupRule{ + Name: fi.PtrTo(fmt.Sprintf("icmp-from-bastion%s", suffix)), + Lifecycle: b.SecurityLifecycle, + SecurityGroup: lbSG, + SourceGroup: bastionGroup.Task, + Protocol: fi.PtrTo("icmp"), + FromPort: fi.PtrTo(int64(3)), + ToPort: fi.PtrTo(int64(4)), + } + AddDirectionalGroupRule(c, t) + } } // Create NLB itself @@ -262,8 +341,11 @@ func (b *BastionModelBuilder) Build(c *fi.CloudupModelBuilderContext) error { LoadBalancerName: fi.PtrTo(loadBalancerName), CLBName: fi.PtrTo("bastion." + b.ClusterName()), SubnetMappings: nlbSubnetMappings, - Listeners: nlbListeners, - TargetGroups: make([]*awstasks.TargetGroup, 0), + SecurityGroups: []*awstasks.SecurityGroup{ + b.LinkToELBSecurityGroup("bastion"), + }, + Listeners: nlbListeners, + TargetGroups: make([]*awstasks.TargetGroup, 0), Tags: tags, VPC: b.LinkToVPC(), @@ -366,7 +448,7 @@ func useIPv6ForBastion(b *BastionModelBuilder) bool { // Choose between subnets in a zone. // We have already applied the rules to match internal subnets to internal NLBs and vice-versa for public-facing NLBs. -// For internal NLBs: we prefer the master subnets +// For internal NLBs: we prefer the control-plane subnets // For public facing NLBs: we prefer the utility subnets func (b *BastionModelBuilder) chooseBestSubnetForNLB(zone string, subnets []*kops.ClusterSubnetSpec) *kops.ClusterSubnetSpec { if len(subnets) == 0 { diff --git a/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go b/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go index 9c5863fcbc..6e5ff91fc1 100644 --- a/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go +++ b/upup/pkg/fi/cloudup/awstasks/network_load_balancer.go @@ -54,6 +54,7 @@ type NetworkLoadBalancer struct { HostedZoneId *string SubnetMappings []*SubnetMapping + SecurityGroups []*SecurityGroup Listeners []*NetworkLoadBalancerListener @@ -298,6 +299,10 @@ func (e *NetworkLoadBalancer) Find(c *fi.CloudupContext) (*NetworkLoadBalancer, actual.SubnetMappings = append(actual.SubnetMappings, sm) } + for _, sg := range lb.SecurityGroups { + actual.SecurityGroups = append(actual.SecurityGroups, &SecurityGroup{ID: sg}) + } + { request := &elbv2.DescribeListenersInput{ LoadBalancerArn: loadBalancerArn, @@ -580,6 +585,10 @@ func (_ *NetworkLoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Ne }) } + for _, sg := range e.SecurityGroups { + request.SecurityGroups = append(request.SecurityGroups, sg.ID) + } + klog.V(2).Infof("Creating NLB %q", loadBalancerName) response, err := t.Cloud.ELBV2().CreateLoadBalancer(request) @@ -686,6 +695,20 @@ func (_ *NetworkLoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Ne } } + if changes.SecurityGroups != nil { + request := &elbv2.SetSecurityGroupsInput{ + LoadBalancerArn: lb.LoadBalancerArn, + } + for _, sg := range e.SecurityGroups { + request.SecurityGroups = append(request.SecurityGroups, sg.ID) + } + + klog.V(2).Infof("Updating Load Balancer Security Groups") + if _, err := t.Cloud.ELBV2().SetSecurityGroups(request); err != nil { + return fmt.Errorf("Error updating security groups on Load Balancer: %v", err) + } + } + if changes.Listeners != nil { if lb != nil { @@ -744,6 +767,7 @@ type terraformNetworkLoadBalancer struct { Internal bool `cty:"internal"` Type string `cty:"load_balancer_type"` IPAddressType *string `cty:"ip_address_type"` + SecurityGroups []*terraformWriter.Literal `cty:"security_groups"` SubnetMappings []terraformNetworkLoadBalancerSubnetMapping `cty:"subnet_mapping"` CrossZoneLoadBalancing bool `cty:"enable_cross_zone_load_balancing"` AccessLog *terraformNetworkLoadBalancerAccessLog `cty:"access_logs"` @@ -791,6 +815,11 @@ func (_ *NetworkLoadBalancer) RenderTerraform(t *terraform.TerraformTarget, a, e }) } + for _, sg := range e.SecurityGroups { + nlbTF.SecurityGroups = append(nlbTF.SecurityGroups, sg.TerraformLink()) + } + terraformWriter.SortLiterals(nlbTF.SecurityGroups) + if e.AccessLog != nil && fi.ValueOf(e.AccessLog.Enabled) { nlbTF.AccessLog = &terraformNetworkLoadBalancerAccessLog{ Enabled: e.AccessLog.Enabled,