aws: Attach security groups to NLBs

This commit is contained in:
John Gardiner Myers 2023-10-03 22:25:00 -07:00
parent ecda9e8652
commit 2fbc7cf979
5 changed files with 226 additions and 164 deletions

View File

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

View File

@ -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

View File

@ -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),
})
}
}
}

View File

@ -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 {

View File

@ -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,