in pkg/model/awsmodel/api_loadbalancer.go [47:537]
func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error {
// Configuration where an ELB fronts the API
if !b.UseLoadBalancerForAPI() {
return nil
}
lbSpec := b.Cluster.Spec.API.LoadBalancer
if lbSpec == nil {
// Skipping API ELB creation; not requested in Spec
return nil
}
switch lbSpec.Type {
case kops.LoadBalancerTypeInternal, kops.LoadBalancerTypePublic:
// OK
default:
return fmt.Errorf("unhandled LoadBalancer type %q", lbSpec.Type)
}
var elbSubnets []*awstasks.Subnet
var nlbSubnetMappings []*awstasks.SubnetMapping
if len(lbSpec.Subnets) != 0 {
// Subnets have been explicitly set
for _, subnet := range lbSpec.Subnets {
for _, clusterSubnet := range b.Cluster.Spec.Subnets {
if subnet.Name == clusterSubnet.Name {
elbSubnet := b.LinkToSubnet(&clusterSubnet)
elbSubnets = append(elbSubnets, elbSubnet)
nlbSubnetMapping := &awstasks.SubnetMapping{
Subnet: elbSubnet,
}
if subnet.PrivateIPv4Address != nil {
nlbSubnetMapping.PrivateIPv4Address = subnet.PrivateIPv4Address
}
if subnet.AllocationID != nil {
nlbSubnetMapping.AllocationID = subnet.AllocationID
}
nlbSubnetMappings = append(nlbSubnetMappings, nlbSubnetMapping)
break
}
}
}
} else {
// Compute the subnets - only one per zone, and then break ties based on chooseBestSubnetForELB
subnetsByZone := make(map[string][]*kops.ClusterSubnetSpec)
for i := range b.Cluster.Spec.Subnets {
subnet := &b.Cluster.Spec.Subnets[i]
switch subnet.Type {
case kops.SubnetTypePublic, kops.SubnetTypeUtility:
if lbSpec.Type != kops.LoadBalancerTypePublic {
continue
}
case kops.SubnetTypeDualStack, kops.SubnetTypePrivate:
if lbSpec.Type != kops.LoadBalancerTypeInternal {
continue
}
default:
return fmt.Errorf("subnet %q had unknown type %q", subnet.Name, subnet.Type)
}
subnetsByZone[subnet.Zone] = append(subnetsByZone[subnet.Zone], subnet)
}
for zone, subnets := range subnetsByZone {
subnet := b.chooseBestSubnetForELB(zone, subnets)
elbSubnet := b.LinkToSubnet(subnet)
elbSubnets = append(elbSubnets, elbSubnet)
nlbSubnetMappings = append(nlbSubnetMappings, &awstasks.SubnetMapping{Subnet: elbSubnet})
}
}
var clb *awstasks.ClassicLoadBalancer
var nlb *awstasks.NetworkLoadBalancer
{
loadBalancerName := b.LBName32("api")
idleTimeout := LoadBalancerDefaultIdleTimeout
if lbSpec.IdleTimeoutSeconds != nil {
idleTimeout = time.Second * time.Duration(*lbSpec.IdleTimeoutSeconds)
}
listeners := map[string]*awstasks.ClassicLoadBalancerListener{
"443": {InstancePort: 443},
}
nlbListeners := []*awstasks.NetworkLoadBalancerListener{
{
Port: 443,
TargetGroupName: b.NLBTargetGroupName("tcp"),
},
}
if lbSpec.SSLCertificate != "" {
listeners["443"].SSLCertificateID = lbSpec.SSLCertificate
nlbListeners[0].Port = 8443
nlbListener := &awstasks.NetworkLoadBalancerListener{
Port: 443,
TargetGroupName: b.NLBTargetGroupName("tls"),
SSLCertificateID: lbSpec.SSLCertificate,
}
if lbSpec.SSLPolicy != nil {
nlbListener.SSLPolicy = *lbSpec.SSLPolicy
}
nlbListeners = append(nlbListeners, nlbListener)
}
if lbSpec.SecurityGroupOverride != nil {
klog.V(1).Infof("WARNING: You are overwriting the Load Balancers, Security Group. When this is done you are responsible for ensure the correct rules!")
}
tags := b.CloudTags(loadBalancerName, false)
for k, v := range b.Cluster.Spec.CloudLabels {
tags[k] = v
}
// Override the returned name to be the expected ELB name
tags["Name"] = "api." + b.ClusterName()
name := b.NLBName("api")
nlb = &awstasks.NetworkLoadBalancer{
Name: &name,
Lifecycle: b.Lifecycle,
LoadBalancerName: fi.String(loadBalancerName),
SubnetMappings: nlbSubnetMappings,
Listeners: nlbListeners,
TargetGroups: make([]*awstasks.TargetGroup, 0),
Tags: tags,
VPC: b.LinkToVPC(),
Type: fi.String("network"),
IpAddressType: fi.String("ipv4"),
}
if b.UseIPv6ForAPI() {
nlb.IpAddressType = fi.String("dualstack")
}
clb = &awstasks.ClassicLoadBalancer{
Name: fi.String("api." + b.ClusterName()),
Lifecycle: b.Lifecycle,
LoadBalancerName: fi.String(loadBalancerName),
SecurityGroups: []*awstasks.SecurityGroup{
b.LinkToELBSecurityGroup("api"),
},
Subnets: elbSubnets,
Listeners: listeners,
// Configure fast-recovery health-checks
HealthCheck: &awstasks.ClassicLoadBalancerHealthCheck{
Target: fi.String("SSL:443"),
Timeout: fi.Int64(5),
Interval: fi.Int64(10),
HealthyThreshold: fi.Int64(2),
UnhealthyThreshold: fi.Int64(2),
},
ConnectionSettings: &awstasks.ClassicLoadBalancerConnectionSettings{
IdleTimeout: fi.Int64(int64(idleTimeout.Seconds())),
},
ConnectionDraining: &awstasks.ClassicLoadBalancerConnectionDraining{
Enabled: fi.Bool(true),
Timeout: fi.Int64(300),
},
Tags: tags,
}
if lbSpec.CrossZoneLoadBalancing == nil {
lbSpec.CrossZoneLoadBalancing = fi.Bool(false)
}
clb.CrossZoneLoadBalancing = &awstasks.ClassicLoadBalancerCrossZoneLoadBalancing{
Enabled: lbSpec.CrossZoneLoadBalancing,
}
nlb.CrossZoneLoadBalancing = lbSpec.CrossZoneLoadBalancing
switch lbSpec.Type {
case kops.LoadBalancerTypeInternal:
clb.Scheme = fi.String("internal")
nlb.Scheme = fi.String("internal")
case kops.LoadBalancerTypePublic:
clb.Scheme = nil
nlb.Scheme = nil
default:
return fmt.Errorf("unknown load balancer Type: %q", lbSpec.Type)
}
if lbSpec.AccessLog != nil {
clb.AccessLog = &awstasks.ClassicLoadBalancerAccessLog{
EmitInterval: fi.Int64(int64(lbSpec.AccessLog.Interval)),
Enabled: fi.Bool(true),
S3BucketName: fi.String(lbSpec.AccessLog.Bucket),
S3BucketPrefix: fi.String(lbSpec.AccessLog.BucketPrefix),
}
nlb.AccessLog = &awstasks.NetworkLoadBalancerAccessLog{
Enabled: fi.Bool(true),
S3BucketName: fi.String(lbSpec.AccessLog.Bucket),
S3BucketPrefix: fi.String(lbSpec.AccessLog.BucketPrefix),
}
} else {
clb.AccessLog = &awstasks.ClassicLoadBalancerAccessLog{
Enabled: fi.Bool(false),
}
nlb.AccessLog = &awstasks.NetworkLoadBalancerAccessLog{
Enabled: fi.Bool(false),
}
}
if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic {
c.AddTask(clb)
} else if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork {
tcpGroupName := b.NLBTargetGroupName("tcp")
tcpGroupTags := b.CloudTags(tcpGroupName, false)
// Override the returned name to be the expected NLB TG name
tcpGroupTags["Name"] = tcpGroupName
tg := &awstasks.TargetGroup{
Name: fi.String(tcpGroupName),
Lifecycle: b.Lifecycle,
VPC: b.LinkToVPC(),
Tags: tcpGroupTags,
Protocol: fi.String("TCP"),
Port: fi.Int64(443),
HealthyThreshold: fi.Int64(2),
UnhealthyThreshold: fi.Int64(2),
Shared: fi.Bool(false),
}
c.AddTask(tg)
nlb.TargetGroups = append(nlb.TargetGroups, tg)
if lbSpec.SSLCertificate != "" {
tlsGroupName := b.NLBTargetGroupName("tls")
tlsGroupTags := b.CloudTags(tlsGroupName, false)
// Override the returned name to be the expected NLB TG name
tlsGroupTags["Name"] = tlsGroupName
secondaryTG := &awstasks.TargetGroup{
Name: fi.String(tlsGroupName),
Lifecycle: b.Lifecycle,
VPC: b.LinkToVPC(),
Tags: tlsGroupTags,
Protocol: fi.String("TLS"),
Port: fi.Int64(443),
HealthyThreshold: fi.Int64(2),
UnhealthyThreshold: fi.Int64(2),
Shared: fi.Bool(false),
}
c.AddTask(secondaryTG)
nlb.TargetGroups = append(nlb.TargetGroups, secondaryTG)
}
sort.Stable(awstasks.OrderTargetGroupsByName(nlb.TargetGroups))
c.AddTask(nlb)
}
}
var lbSG *awstasks.SecurityGroup
{
lbSG = &awstasks.SecurityGroup{
Name: fi.String(b.ELBSecurityGroupName("api")),
Lifecycle: b.SecurityLifecycle,
Description: fi.String("Security group for api ELB"),
RemoveExtraRules: []string{"port=443"},
VPC: b.LinkToVPC(),
}
lbSG.Tags = b.CloudTags(*lbSG.Name, false)
if lbSpec.SecurityGroupOverride != nil {
lbSG.ID = fi.String(*lbSpec.SecurityGroupOverride)
lbSG.Shared = fi.Bool(true)
}
c.AddTask(lbSG)
}
// Allow traffic from ELB to egress freely
if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic {
{
t := &awstasks.SecurityGroupRule{
Name: fi.String("ipv4-api-elb-egress"),
Lifecycle: b.SecurityLifecycle,
CIDR: fi.String("0.0.0.0/0"),
Egress: fi.Bool(true),
SecurityGroup: lbSG,
}
AddDirectionalGroupRule(c, t)
}
{
t := &awstasks.SecurityGroupRule{
Name: fi.String("ipv6-api-elb-egress"),
Lifecycle: b.SecurityLifecycle,
IPv6CIDR: fi.String("::/0"),
Egress: fi.Bool(true),
SecurityGroup: lbSG,
}
AddDirectionalGroupRule(c, t)
}
}
// Allow traffic into the ELB from KubernetesAPIAccess CIDRs
if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic {
for _, cidr := range b.Cluster.Spec.KubernetesAPIAccess {
{
t := &awstasks.SecurityGroupRule{
Name: fi.String("https-api-elb-" + cidr),
Lifecycle: b.SecurityLifecycle,
FromPort: fi.Int64(443),
Protocol: fi.String("tcp"),
SecurityGroup: lbSG,
ToPort: fi.Int64(443),
}
if utils.IsIPv6CIDR(cidr) {
t.IPv6CIDR = fi.String(cidr)
} else {
t.CIDR = fi.String(cidr)
}
AddDirectionalGroupRule(c, t)
}
// Allow ICMP traffic required for PMTU discovery
if utils.IsIPv6CIDR(cidr) {
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String("icmpv6-pmtu-api-elb-" + cidr),
Lifecycle: b.SecurityLifecycle,
IPv6CIDR: fi.String(cidr),
FromPort: fi.Int64(-1),
Protocol: fi.String("icmpv6"),
SecurityGroup: lbSG,
ToPort: fi.Int64(-1),
})
} else {
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String("icmp-pmtu-api-elb-" + cidr),
Lifecycle: b.SecurityLifecycle,
CIDR: fi.String(cidr),
FromPort: fi.Int64(3),
Protocol: fi.String("icmp"),
SecurityGroup: lbSG,
ToPort: fi.Int64(4),
})
}
}
}
masterGroups, err := b.GetSecurityGroups(kops.InstanceGroupRoleMaster)
if err != nil {
return err
}
if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork {
for _, cidr := range b.Cluster.Spec.KubernetesAPIAccess {
for _, masterGroup := range masterGroups {
{
t := &awstasks.SecurityGroupRule{
Name: fi.String(fmt.Sprintf("https-api-elb-%s", cidr)),
Lifecycle: b.SecurityLifecycle,
FromPort: fi.Int64(443),
Protocol: fi.String("tcp"),
SecurityGroup: masterGroup.Task,
ToPort: fi.Int64(443),
}
if utils.IsIPv6CIDR(cidr) {
t.IPv6CIDR = fi.String(cidr)
} else {
t.CIDR = fi.String(cidr)
}
AddDirectionalGroupRule(c, t)
}
// Allow ICMP traffic required for PMTU discovery
if utils.IsIPv6CIDR(cidr) {
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String("icmpv6-pmtu-api-elb-" + cidr),
Lifecycle: b.SecurityLifecycle,
IPv6CIDR: fi.String(cidr),
FromPort: fi.Int64(-1),
Protocol: fi.String("icmpv6"),
SecurityGroup: masterGroup.Task,
ToPort: fi.Int64(-1),
})
} else {
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String("icmp-pmtu-api-elb-" + cidr),
Lifecycle: b.SecurityLifecycle,
CIDR: fi.String(cidr),
FromPort: fi.Int64(3),
Protocol: fi.String("icmp"),
SecurityGroup: masterGroup.Task,
ToPort: fi.Int64(4),
})
}
if b.Cluster.Spec.API != nil && 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.String(fmt.Sprintf("tcp-api-%s", cidr)),
Lifecycle: b.SecurityLifecycle,
FromPort: fi.Int64(8443),
Protocol: fi.String("tcp"),
SecurityGroup: masterGroup.Task,
ToPort: fi.Int64(8443),
}
if utils.IsIPv6CIDR(cidr) {
t.IPv6CIDR = fi.String(cidr)
} else {
t.CIDR = fi.String(cidr)
}
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.String(id),
Lifecycle: b.SecurityLifecycle,
ID: fi.String(id),
Shared: fi.Bool(true),
}
if err := c.EnsureTask(t); err != nil {
return err
}
clb.SecurityGroups = append(clb.SecurityGroups, t)
}
}
// Allow HTTPS to the master instances from the ELB
if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic {
for _, masterGroup := range masterGroups {
suffix := masterGroup.Suffix
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String(fmt.Sprintf("https-elb-to-master%s", suffix)),
Lifecycle: b.SecurityLifecycle,
FromPort: fi.Int64(443),
Protocol: fi.String("tcp"),
SecurityGroup: masterGroup.Task,
SourceGroup: lbSG,
ToPort: fi.Int64(443),
})
}
} else if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork {
for _, masterGroup := range masterGroups {
suffix := masterGroup.Suffix
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String(fmt.Sprintf("https-elb-to-master%s", suffix)),
Lifecycle: b.SecurityLifecycle,
FromPort: fi.Int64(443),
Protocol: fi.String("tcp"),
SecurityGroup: masterGroup.Task,
ToPort: fi.Int64(443),
CIDR: fi.String(b.Cluster.Spec.NetworkCIDR),
})
for _, cidr := range b.Cluster.Spec.AdditionalNetworkCIDRs {
c.AddTask(&awstasks.SecurityGroupRule{
Name: fi.String(fmt.Sprintf("https-lb-to-master%s-%s", suffix, cidr)),
Lifecycle: b.SecurityLifecycle,
FromPort: fi.Int64(443),
Protocol: fi.String("tcp"),
SecurityGroup: masterGroup.Task,
ToPort: fi.Int64(443),
CIDR: fi.String(cidr),
})
}
}
}
if dns.IsGossipHostname(b.Cluster.Name) || b.UsePrivateDNS() {
// Ensure the LB hostname is included in the TLS certificate,
// if we're not going to use an alias for it
clb.ForAPIServer = true
nlb.ForAPIServer = true
}
return nil
}