mirror of https://github.com/kubernetes/kops.git
Add initial support for configuring IPv6 with AWS
This commit is contained in:
parent
c08d0e2bdf
commit
cedbe1f360
|
|
@ -74,6 +74,18 @@ func (m *MockEC2) CreateSubnetWithId(request *ec2.CreateSubnetInput, id string)
|
|||
AvailabilityZone: request.AvailabilityZone,
|
||||
}
|
||||
|
||||
if request.Ipv6CidrBlock != nil {
|
||||
subnet.Ipv6CidrBlockAssociationSet = []*ec2.SubnetIpv6CidrBlockAssociation{
|
||||
{
|
||||
AssociationId: aws.String("subnet-cidr-assoc-ipv6-" + id),
|
||||
Ipv6CidrBlock: request.Ipv6CidrBlock,
|
||||
Ipv6CidrBlockState: &ec2.SubnetCidrBlockState{
|
||||
State: aws.String(ec2.SubnetCidrBlockStateCodeAssociated),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if m.subnets == nil {
|
||||
m.subnets = make(map[string]*subnetInfo)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,18 +243,33 @@ func (m *MockEC2) AssociateVpcCidrBlock(request *ec2.AssociateVpcCidrBlockInput)
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("VPC %q not found", id)
|
||||
}
|
||||
association := &ec2.VpcCidrBlockAssociation{
|
||||
CidrBlock: request.CidrBlock,
|
||||
AssociationId: aws.String(fmt.Sprintf("%v-%v", id, len(vpc.main.CidrBlockAssociationSet))),
|
||||
CidrBlockState: &ec2.VpcCidrBlockState{
|
||||
State: aws.String(ec2.VpcCidrBlockStateCodeAssociated),
|
||||
},
|
||||
var ipv4association *ec2.VpcCidrBlockAssociation
|
||||
var ipv6association *ec2.VpcIpv6CidrBlockAssociation
|
||||
if aws.BoolValue(request.AmazonProvidedIpv6CidrBlock) {
|
||||
ipv6association = &ec2.VpcIpv6CidrBlockAssociation{
|
||||
Ipv6Pool: aws.String("Amazon"),
|
||||
Ipv6CidrBlock: aws.String("2001:db8::/56"),
|
||||
AssociationId: aws.String(fmt.Sprintf("%v-%v", id, len(vpc.main.Ipv6CidrBlockAssociationSet))),
|
||||
Ipv6CidrBlockState: &ec2.VpcCidrBlockState{
|
||||
State: aws.String(ec2.VpcCidrBlockStateCodeAssociated),
|
||||
},
|
||||
}
|
||||
vpc.main.Ipv6CidrBlockAssociationSet = append(vpc.main.Ipv6CidrBlockAssociationSet, ipv6association)
|
||||
} else {
|
||||
ipv4association = &ec2.VpcCidrBlockAssociation{
|
||||
CidrBlock: request.CidrBlock,
|
||||
AssociationId: aws.String(fmt.Sprintf("%v-%v", id, len(vpc.main.CidrBlockAssociationSet))),
|
||||
CidrBlockState: &ec2.VpcCidrBlockState{
|
||||
State: aws.String(ec2.VpcCidrBlockStateCodeAssociated),
|
||||
},
|
||||
}
|
||||
vpc.main.CidrBlockAssociationSet = append(vpc.main.CidrBlockAssociationSet, ipv4association)
|
||||
}
|
||||
vpc.main.CidrBlockAssociationSet = append(vpc.main.CidrBlockAssociationSet, association)
|
||||
|
||||
return &ec2.AssociateVpcCidrBlockOutput{
|
||||
CidrBlockAssociation: association,
|
||||
VpcId: request.VpcId,
|
||||
CidrBlockAssociation: ipv4association,
|
||||
Ipv6CidrBlockAssociation: ipv6association,
|
||||
VpcId: request.VpcId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4103,6 +4103,7 @@ spec:
|
|||
items:
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is the IPv4 CIDR block assigned to the subnet.
|
||||
type: string
|
||||
egress:
|
||||
description: Egress defines the method of traffic egress for
|
||||
|
|
@ -4112,6 +4113,10 @@ spec:
|
|||
description: ProviderID is the cloud provider id for the objects
|
||||
associated with the zone (the subnet on AWS)
|
||||
type: string
|
||||
ipv6CIDR:
|
||||
description: IPv6CIDR is the IPv6 CIDR block assigned to the
|
||||
subnet.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
publicIP:
|
||||
|
|
|
|||
|
|
@ -621,8 +621,10 @@ const (
|
|||
type ClusterSubnetSpec struct {
|
||||
// Name is the name of the subnet
|
||||
Name string `json:"name,omitempty"`
|
||||
// CIDR is the network cidr of the subnet
|
||||
// CIDR is the IPv4 CIDR block assigned to the subnet.
|
||||
CIDR string `json:"cidr,omitempty"`
|
||||
// IPv6CIDR is the IPv6 CIDR block assigned to the subnet.
|
||||
IPv6CIDR string `json:"ipv6CIDR,omitempty"`
|
||||
// Zone is the zone the subnet is in, set for subnets that are zonally scoped
|
||||
Zone string `json:"zone,omitempty"`
|
||||
// Region is the region the subnet is in, set for subnets that are regionally scoped
|
||||
|
|
|
|||
|
|
@ -606,7 +606,10 @@ type ClusterSubnetSpec struct {
|
|||
// Region is the region the subnet is in, set for subnets that are regionally scoped
|
||||
Region string `json:"region,omitempty"`
|
||||
|
||||
// CIDR is the IPv4 CIDR block assigned to the subnet.
|
||||
CIDR string `json:"cidr,omitempty"`
|
||||
// IPv6CIDR is the IPv6 CIDR block assigned to the subnet.
|
||||
IPv6CIDR string `json:"ipv6CIDR,omitempty"`
|
||||
|
||||
// ProviderID is the cloud provider id for the objects associated with the zone (the subnet on AWS)
|
||||
ProviderID string `json:"id,omitempty"`
|
||||
|
|
|
|||
|
|
@ -2983,6 +2983,7 @@ func autoConvert_v1alpha2_ClusterSubnetSpec_To_kops_ClusterSubnetSpec(in *Cluste
|
|||
out.Zone = in.Zone
|
||||
out.Region = in.Region
|
||||
out.CIDR = in.CIDR
|
||||
out.IPv6CIDR = in.IPv6CIDR
|
||||
out.ProviderID = in.ProviderID
|
||||
out.Egress = in.Egress
|
||||
out.Type = kops.SubnetType(in.Type)
|
||||
|
|
@ -2998,6 +2999,7 @@ func Convert_v1alpha2_ClusterSubnetSpec_To_kops_ClusterSubnetSpec(in *ClusterSub
|
|||
func autoConvert_kops_ClusterSubnetSpec_To_v1alpha2_ClusterSubnetSpec(in *kops.ClusterSubnetSpec, out *ClusterSubnetSpec, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.CIDR = in.CIDR
|
||||
out.IPv6CIDR = in.IPv6CIDR
|
||||
out.Zone = in.Zone
|
||||
out.Region = in.Region
|
||||
out.ProviderID = in.ProviderID
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ go_library(
|
|||
"//pkg/util/subnet:go_default_library",
|
||||
"//upup/pkg/fi:go_default_library",
|
||||
"//upup/pkg/fi/cloudup/awsup:go_default_library",
|
||||
"//upup/pkg/fi/utils:go_default_library",
|
||||
"//util/pkg/vfs:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/arn:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import (
|
|||
"k8s.io/kops/pkg/model/components"
|
||||
"k8s.io/kops/pkg/model/iam"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/utils"
|
||||
)
|
||||
|
||||
func newValidateCluster(cluster *kops.Cluster) field.ErrorList {
|
||||
|
|
@ -78,7 +79,7 @@ func newValidateCluster(cluster *kops.Cluster) field.ErrorList {
|
|||
func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validateSubnets(spec.Subnets, fieldPath.Child("subnets"))...)
|
||||
allErrs = append(allErrs, validateSubnets(spec, fieldPath.Child("subnets"))...)
|
||||
|
||||
// SSHAccess
|
||||
for i, cidr := range spec.SSHAccess {
|
||||
|
|
@ -312,7 +313,11 @@ func validateCIDR(cidr string, fieldPath *field.Path) field.ErrorList {
|
|||
if !strings.Contains(cidr, "/") {
|
||||
ip := net.ParseIP(cidr)
|
||||
if ip != nil {
|
||||
detail += fmt.Sprintf(" (did you mean \"%s/32\")", cidr)
|
||||
if ip.To4() != nil && !strings.Contains(cidr, ":") {
|
||||
detail += fmt.Sprintf(" (did you mean \"%s/32\")", cidr)
|
||||
} else {
|
||||
detail += fmt.Sprintf(" (did you mean \"%s/64\")", cidr)
|
||||
}
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, cidr, detail))
|
||||
|
|
@ -324,6 +329,16 @@ func validateCIDR(cidr string, fieldPath *field.Path) field.ErrorList {
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateIPv6CIDR(cidr string, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := validateCIDR(cidr, fieldPath)
|
||||
|
||||
if !utils.IsIPv6CIDR(cidr) {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath, cidr, "Network is not an IPv6 CIDR"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
|
|
@ -360,9 +375,11 @@ func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field.
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateSubnets(subnets []kops.ClusterSubnetSpec, fieldPath *field.Path) field.ErrorList {
|
||||
func validateSubnets(cluster *kops.ClusterSpec, fieldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
subnets := cluster.Subnets
|
||||
|
||||
// cannot be empty
|
||||
if len(subnets) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fieldPath, ""))
|
||||
|
|
@ -395,6 +412,14 @@ func validateSubnets(subnets []kops.ClusterSubnetSpec, fieldPath *field.Path) fi
|
|||
}
|
||||
}
|
||||
|
||||
if kops.CloudProviderID(cluster.CloudProvider) != kops.CloudProviderAWS {
|
||||
for i := range subnets {
|
||||
if subnets[i].IPv6CIDR != "" {
|
||||
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("ipv6CIDR"), "ipv6CIDR can only be specified for AWS"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
@ -410,6 +435,10 @@ func validateSubnet(subnet *kops.ClusterSubnetSpec, fieldPath *field.Path) field
|
|||
if subnet.CIDR != "" {
|
||||
allErrs = append(allErrs, validateCIDR(subnet.CIDR, fieldPath.Child("cidr"))...)
|
||||
}
|
||||
// IPv6CIDR
|
||||
if subnet.IPv6CIDR != "" {
|
||||
allErrs = append(allErrs, validateIPv6CIDR(subnet.IPv6CIDR, fieldPath.Child("ipv6CIDR"))...)
|
||||
}
|
||||
|
||||
if subnet.Egress != "" {
|
||||
egressType := strings.Split(subnet.Egress, "-")[0]
|
||||
|
|
|
|||
|
|
@ -155,9 +155,42 @@ func TestValidateSubnets(t *testing.T) {
|
|||
},
|
||||
ExpectedErrors: []string{"Invalid value::subnets[0].cidr"},
|
||||
},
|
||||
{
|
||||
Input: []kops.ClusterSubnetSpec{
|
||||
{Name: "a", IPv6CIDR: "2001:db8::/56"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Input: []kops.ClusterSubnetSpec{
|
||||
{Name: "a", IPv6CIDR: "10.0.0.0/8"},
|
||||
},
|
||||
ExpectedErrors: []string{"Invalid value::subnets[0].ipv6CIDR"},
|
||||
},
|
||||
{
|
||||
Input: []kops.ClusterSubnetSpec{
|
||||
{Name: "a", IPv6CIDR: "::ffff:10.128.0.0"},
|
||||
},
|
||||
ExpectedErrors: []string{"Invalid value::subnets[0].ipv6CIDR"},
|
||||
},
|
||||
{
|
||||
Input: []kops.ClusterSubnetSpec{
|
||||
{Name: "a", IPv6CIDR: "::ffff:10.128.0.0/8"},
|
||||
},
|
||||
ExpectedErrors: []string{"Invalid value::subnets[0].ipv6CIDR"},
|
||||
},
|
||||
{
|
||||
Input: []kops.ClusterSubnetSpec{
|
||||
{Name: "a", CIDR: "::ffff:10.128.0.0/8"},
|
||||
},
|
||||
ExpectedErrors: []string{"Invalid value::subnets[0].cidr"},
|
||||
},
|
||||
}
|
||||
for _, g := range grid {
|
||||
errs := validateSubnets(g.Input, field.NewPath("subnets"))
|
||||
cluster := &kops.ClusterSpec{
|
||||
CloudProvider: "aws",
|
||||
Subnets: g.Input,
|
||||
}
|
||||
errs := validateSubnets(cluster, field.NewPath("subnets"))
|
||||
|
||||
testErrors(t, g.Input, errs, g.ExpectedErrors)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ go_library(
|
|||
"//upup/pkg/fi/cloudup/awstasks:go_default_library",
|
||||
"//upup/pkg/fi/cloudup/awsup:go_default_library",
|
||||
"//upup/pkg/fi/cloudup/spotinsttasks:go_default_library",
|
||||
"//upup/pkg/fi/utils:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/endpoints:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"k8s.io/kops/pkg/dns"
|
||||
"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
|
||||
|
|
@ -301,40 +302,70 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
|
||||
// Allow traffic from ELB to egress freely
|
||||
if b.APILoadBalancerClass() == kops.LoadBalancerClassClassic {
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("api-elb-egress"),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
CIDR: fi.String("0.0.0.0/0"),
|
||||
Egress: fi.Bool(true),
|
||||
SecurityGroup: lbSG,
|
||||
{
|
||||
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)
|
||||
}
|
||||
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,
|
||||
CIDR: fi.String(cidr),
|
||||
FromPort: fi.Int64(443),
|
||||
Protocol: fi.String("tcp"),
|
||||
SecurityGroup: lbSG,
|
||||
ToPort: fi.Int64(443),
|
||||
{
|
||||
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)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
|
||||
// Allow ICMP traffic required for PMTU discovery
|
||||
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),
|
||||
})
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -347,39 +378,62 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
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,
|
||||
CIDR: fi.String(cidr),
|
||||
FromPort: fi.Int64(443),
|
||||
Protocol: fi.String("tcp"),
|
||||
SecurityGroup: masterGroup.Task,
|
||||
ToPort: fi.Int64(443),
|
||||
{
|
||||
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)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
|
||||
// Allow ICMP traffic required for PMTU discovery
|
||||
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 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
|
||||
c.AddTask(&awstasks.SecurityGroupRule{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String(fmt.Sprintf("tcp-api-%s", cidr)),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
CIDR: fi.String(cidr),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ func (b *AutoscalingGroupModelBuilder) buildLaunchTemplateTask(c *fi.ModelBuilde
|
|||
InstanceInterruptionBehavior: ig.Spec.InstanceInterruptionBehavior,
|
||||
InstanceMonitoring: ig.Spec.DetailedInstanceMonitoring,
|
||||
InstanceType: fi.String(strings.Split(ig.Spec.MachineType, ",")[0]),
|
||||
IPv6AddressCount: fi.Int64(0),
|
||||
RootVolumeIops: fi.Int64(int64(fi.Int32Value(ig.Spec.RootVolumeIops))),
|
||||
RootVolumeOptimization: ig.Spec.RootVolumeOptimization,
|
||||
RootVolumeSize: fi.Int64(int64(rootVolumeSize)),
|
||||
|
|
@ -211,6 +212,14 @@ func (b *AutoscalingGroupModelBuilder) buildLaunchTemplateTask(c *fi.ModelBuilde
|
|||
case kops.SubnetTypePrivate:
|
||||
lt.AssociatePublicIP = fi.Bool(false)
|
||||
}
|
||||
|
||||
// @step: add an IPv6 address
|
||||
for _, subnet := range subnets {
|
||||
if subnet.IPv6CIDR != "" {
|
||||
lt.IPv6AddressCount = fi.Int64(1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @step: add any additional block devices
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -77,14 +78,26 @@ func (b *BastionModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
|
||||
for _, src := range bastionGroups {
|
||||
// Allow traffic from bastion instances to egress freely
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("bastion-egress" + src.Suffix),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
CIDR: fi.String("0.0.0.0/0"),
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("ipv4-bastion-egress" + src.Suffix),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
CIDR: fi.String("0.0.0.0/0"),
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("ipv6-bastion-egress" + src.Suffix),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
IPv6CIDR: fi.String("::/0"),
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
||||
// Allow incoming SSH traffic to bastions, through the ELB
|
||||
|
|
@ -137,9 +150,8 @@ func (b *BastionModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
// Create security group for bastion ELB
|
||||
{
|
||||
t := &awstasks.SecurityGroup{
|
||||
Name: fi.String(b.ELBSecurityGroupName(BastionELBSecurityGroupPrefix)),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
|
||||
Name: fi.String(b.ELBSecurityGroupName(BastionELBSecurityGroupPrefix)),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
VPC: b.LinkToVPC(),
|
||||
Description: fi.String("Security group for bastion ELB"),
|
||||
RemoveExtraRules: []string{"port=22"},
|
||||
|
|
@ -151,29 +163,41 @@ func (b *BastionModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
// Allow traffic from ELB to egress freely
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("bastion-elb-egress"),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
|
||||
Name: fi.String("ipv4-bastion-elb-egress"),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
SecurityGroup: b.LinkToELBSecurityGroup(BastionELBSecurityGroupPrefix),
|
||||
Egress: fi.Bool(true),
|
||||
CIDR: fi.String("0.0.0.0/0"),
|
||||
}
|
||||
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("ipv6-bastion-elb-egress"),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
SecurityGroup: b.LinkToELBSecurityGroup(BastionELBSecurityGroupPrefix),
|
||||
Egress: fi.Bool(true),
|
||||
IPv6CIDR: fi.String("::/0"),
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
||||
// Allow external access to ELB
|
||||
for _, sshAccess := range b.Cluster.Spec.SSHAccess {
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("ssh-external-to-bastion-elb-" + sshAccess),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
|
||||
Name: fi.String("ssh-external-to-bastion-elb-" + sshAccess),
|
||||
Lifecycle: b.SecurityLifecycle,
|
||||
SecurityGroup: b.LinkToELBSecurityGroup(BastionELBSecurityGroupPrefix),
|
||||
Protocol: fi.String("tcp"),
|
||||
FromPort: fi.Int64(22),
|
||||
ToPort: fi.Int64(22),
|
||||
CIDR: fi.String(sshAccess),
|
||||
}
|
||||
if utils.IsIPv6CIDR(sshAccess) {
|
||||
t.IPv6CIDR = fi.String(sshAccess)
|
||||
} else {
|
||||
t.CIDR = fi.String(sshAccess)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
// ExternalAccessModelBuilder configures security group rules for external access
|
||||
|
|
@ -69,7 +70,11 @@ func (b *ExternalAccessModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
Protocol: fi.String("tcp"),
|
||||
FromPort: fi.Int64(22),
|
||||
ToPort: fi.Int64(22),
|
||||
CIDR: fi.String(sshAccess),
|
||||
}
|
||||
if utils.IsIPv6CIDR(sshAccess) {
|
||||
t.IPv6CIDR = fi.String(sshAccess)
|
||||
} else {
|
||||
t.CIDR = fi.String(sshAccess)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
|
@ -83,7 +88,11 @@ func (b *ExternalAccessModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
Protocol: fi.String("tcp"),
|
||||
FromPort: fi.Int64(22),
|
||||
ToPort: fi.Int64(22),
|
||||
CIDR: fi.String(sshAccess),
|
||||
}
|
||||
if utils.IsIPv6CIDR(sshAccess) {
|
||||
t.IPv6CIDR = fi.String(sshAccess)
|
||||
} else {
|
||||
t.CIDR = fi.String(sshAccess)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
|
@ -98,27 +107,38 @@ func (b *ExternalAccessModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
|
||||
for _, nodeGroup := range nodeGroups {
|
||||
suffix := nodeGroup.Suffix
|
||||
t1 := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String(fmt.Sprintf("nodeport-tcp-external-to-node-%s%s", nodePortAccess, suffix)),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: nodeGroup.Task,
|
||||
Protocol: fi.String("tcp"),
|
||||
FromPort: fi.Int64(int64(nodePortRange.Base)),
|
||||
ToPort: fi.Int64(int64(nodePortRange.Base + nodePortRange.Size - 1)),
|
||||
CIDR: fi.String(nodePortAccess),
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String(fmt.Sprintf("nodeport-tcp-external-to-node-%s%s", nodePortAccess, suffix)),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: nodeGroup.Task,
|
||||
Protocol: fi.String("tcp"),
|
||||
FromPort: fi.Int64(int64(nodePortRange.Base)),
|
||||
ToPort: fi.Int64(int64(nodePortRange.Base + nodePortRange.Size - 1)),
|
||||
}
|
||||
if utils.IsIPv6CIDR(nodePortAccess) {
|
||||
t.IPv6CIDR = fi.String(nodePortAccess)
|
||||
} else {
|
||||
t.CIDR = fi.String(nodePortAccess)
|
||||
}
|
||||
c.AddTask(t)
|
||||
}
|
||||
c.AddTask(t1)
|
||||
|
||||
t2 := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String(fmt.Sprintf("nodeport-udp-external-to-node-%s%s", nodePortAccess, suffix)),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: nodeGroup.Task,
|
||||
Protocol: fi.String("udp"),
|
||||
FromPort: fi.Int64(int64(nodePortRange.Base)),
|
||||
ToPort: fi.Int64(int64(nodePortRange.Base + nodePortRange.Size - 1)),
|
||||
CIDR: fi.String(nodePortAccess),
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String(fmt.Sprintf("nodeport-udp-external-to-node-%s%s", nodePortAccess, suffix)),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: nodeGroup.Task,
|
||||
Protocol: fi.String("udp"),
|
||||
FromPort: fi.Int64(int64(nodePortRange.Base)),
|
||||
ToPort: fi.Int64(int64(nodePortRange.Base + nodePortRange.Size - 1)),
|
||||
}
|
||||
if utils.IsIPv6CIDR(nodePortAccess) {
|
||||
t.IPv6CIDR = fi.String(nodePortAccess)
|
||||
} else {
|
||||
t.CIDR = fi.String(nodePortAccess)
|
||||
}
|
||||
c.AddTask(t)
|
||||
}
|
||||
c.AddTask(t2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +158,11 @@ func (b *ExternalAccessModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
Protocol: fi.String("tcp"),
|
||||
FromPort: fi.Int64(443),
|
||||
ToPort: fi.Int64(443),
|
||||
CIDR: fi.String(apiAccess),
|
||||
}
|
||||
if utils.IsIPv6CIDR(apiAccess) {
|
||||
t.IPv6CIDR = fi.String(apiAccess)
|
||||
} else {
|
||||
t.CIDR = fi.String(apiAccess)
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func (b *FirewallModelBuilder) buildNodeRules(c *fi.ModelBuilderContext) ([]Secu
|
|||
// Allow full egress
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("node-egress" + src.Suffix),
|
||||
Name: fi.String("ipv4-node-egress" + src.Suffix),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
|
|
@ -86,6 +86,16 @@ func (b *FirewallModelBuilder) buildNodeRules(c *fi.ModelBuilderContext) ([]Secu
|
|||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("ipv6-node-egress" + src.Suffix),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
IPv6CIDR: fi.String("::/0"),
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
||||
// Nodes can talk to nodes
|
||||
for _, dest := range nodeGroups {
|
||||
|
|
@ -238,7 +248,7 @@ func (b *FirewallModelBuilder) buildMasterRules(c *fi.ModelBuilderContext, nodeG
|
|||
// Allow full egress
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("master-egress" + src.Suffix),
|
||||
Name: fi.String("ipv4-master-egress" + src.Suffix),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
|
|
@ -246,6 +256,16 @@ func (b *FirewallModelBuilder) buildMasterRules(c *fi.ModelBuilderContext, nodeG
|
|||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
{
|
||||
t := &awstasks.SecurityGroupRule{
|
||||
Name: fi.String("ipv6-master-egress" + src.Suffix),
|
||||
Lifecycle: b.Lifecycle,
|
||||
SecurityGroup: src.Task,
|
||||
Egress: fi.Bool(true),
|
||||
IPv6CIDR: fi.String("::/0"),
|
||||
}
|
||||
AddDirectionalGroupRule(c, t)
|
||||
}
|
||||
|
||||
// Masters can talk to masters
|
||||
for _, dest := range masterGroups {
|
||||
|
|
@ -420,8 +440,10 @@ func generateName(o *awstasks.SecurityGroupRule) string {
|
|||
var target, dst, src, direction, proto string
|
||||
if o.SourceGroup != nil {
|
||||
target = fi.StringValue(o.SourceGroup.Name)
|
||||
} else if o.CIDR != nil && fi.StringValue(o.CIDR) != "" {
|
||||
} else if o.CIDR != nil {
|
||||
target = fi.StringValue(o.CIDR)
|
||||
} else if o.IPv6CIDR != nil {
|
||||
target = fi.StringValue(o.IPv6CIDR)
|
||||
} else {
|
||||
target = "0.0.0.0/0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,11 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
} else {
|
||||
// In theory we don't need to enable it for >= 1.5,
|
||||
// but seems safer to stick with existing behaviour
|
||||
|
||||
t.EnableDNSHostnames = fi.Bool(true)
|
||||
|
||||
// Used only for Terraform rendering.
|
||||
// Direct and CloudFormation rendering is handled via the VPCAmazonIPv6CIDRBlock task
|
||||
t.AmazonIPv6 = fi.Bool(true)
|
||||
t.AssociateExtraCIDRBlocks = b.Cluster.Spec.AdditionalNetworkCIDRs
|
||||
}
|
||||
|
||||
|
|
@ -88,12 +91,21 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
}
|
||||
|
||||
if !sharedVPC {
|
||||
// Associate an Amazon-provided IPv6 CIDR block with the VPC
|
||||
c.AddTask(&awstasks.VPCAmazonIPv6CIDRBlock{
|
||||
Name: fi.String("AmazonIPv6"),
|
||||
Lifecycle: b.Lifecycle,
|
||||
VPC: b.LinkToVPC(),
|
||||
Shared: fi.Bool(false),
|
||||
})
|
||||
|
||||
// Associate additional CIDR blocks with the VPC
|
||||
for _, cidr := range b.Cluster.Spec.AdditionalNetworkCIDRs {
|
||||
c.AddTask(&awstasks.VPCCIDRBlock{
|
||||
Name: fi.String(cidr),
|
||||
Lifecycle: b.Lifecycle,
|
||||
VPC: b.LinkToVPC(),
|
||||
Shared: fi.Bool(sharedVPC),
|
||||
Shared: fi.Bool(false),
|
||||
CIDRBlock: fi.String(cidr),
|
||||
})
|
||||
}
|
||||
|
|
@ -184,6 +196,13 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
RouteTable: publicRouteTable,
|
||||
InternetGateway: igw,
|
||||
})
|
||||
c.AddTask(&awstasks.Route{
|
||||
Name: fi.String("::/0"),
|
||||
Lifecycle: b.Lifecycle,
|
||||
IPv6CIDR: fi.String("::/0"),
|
||||
RouteTable: publicRouteTable,
|
||||
InternetGateway: igw,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,6 +251,9 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
|||
Tags: tags,
|
||||
}
|
||||
|
||||
if subnetSpec.IPv6CIDR != "" {
|
||||
subnet.IPv6CIDR = fi.String(subnetSpec.IPv6CIDR)
|
||||
}
|
||||
if subnetSpec.ProviderID != "" {
|
||||
subnet.ID = fi.String(subnetSpec.ProviderID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ spec:
|
|||
class: Network
|
||||
kubernetesApiAccess:
|
||||
- 0.0.0.0/0
|
||||
- ::/0
|
||||
sshAccess:
|
||||
- 0.0.0.0/0
|
||||
- ::/0
|
||||
channel: stable
|
||||
cloudProvider: aws
|
||||
configBase: memfs://clusters.example.com/minimal-ipv6.example.com
|
||||
|
|
@ -39,6 +41,7 @@ spec:
|
|||
nodes: public
|
||||
subnets:
|
||||
- cidr: 172.20.32.0/19
|
||||
ipv6CIDR: 2001:db8:0:111::/64
|
||||
name: us-test-1a
|
||||
type: Public
|
||||
zone: us-test-1a
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ go_library(
|
|||
"vpc.go",
|
||||
"vpc_dhcpoptions_association.go",
|
||||
"vpc_fitask.go",
|
||||
"vpcamazonipv6cidrblock.go",
|
||||
"vpcamazonipv6cidrblock_fitask.go",
|
||||
"vpccidrblock.go",
|
||||
"vpccidrblock_fitask.go",
|
||||
"vpcdhcpoptionsassociation_fitask.go",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ type LaunchTemplate struct {
|
|||
InstanceMonitoring *bool
|
||||
// InstanceType is the type of instance we are using
|
||||
InstanceType *string
|
||||
// Ipv6AddressCount is the number of IPv6 addresses to assign with the primary network interface.
|
||||
IPv6AddressCount *int64
|
||||
// RootVolumeIops is the provisioned IOPS when the volume type is io1, io2 or gp3
|
||||
RootVolumeIops *int64
|
||||
// RootVolumeOptimization enables EBS optimization for an instance
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, e, changes *LaunchT
|
|||
AssociatePublicIpAddress: t.AssociatePublicIP,
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
DeviceIndex: fi.Int64(0),
|
||||
Ipv6AddressCount: t.IPv6AddressCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -214,6 +215,7 @@ func (t *LaunchTemplate) Find(c *fi.Context) (*LaunchTemplate, error) {
|
|||
for _, id := range x.Groups {
|
||||
actual.SecurityGroups = append(actual.SecurityGroups, &SecurityGroup{ID: id})
|
||||
}
|
||||
actual.IPv6AddressCount = x.Ipv6AddressCount
|
||||
}
|
||||
// In older Kops versions, security groups were added to LaunchTemplateData.SecurityGroupIds
|
||||
for _, id := range lt.LaunchTemplateData.SecurityGroupIds {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ type cloudformationLaunchTemplateNetworkInterface struct {
|
|||
DeleteOnTermination *bool `json:"DeleteOnTermination,omitempty"`
|
||||
// DeviceIndex is the device index for the network interface attachment.
|
||||
DeviceIndex *int `json:"DeviceIndex,omitempty"`
|
||||
// Ipv6AddressCount is the number of IPv6 addresses to assign with the primary network interface.
|
||||
Ipv6AddressCount *int64 `json:"Ipv6AddressCount,omitempty"`
|
||||
// SecurityGroups is a list of security group ids.
|
||||
SecurityGroups []*cloudformation.Literal `json:"Groups,omitempty"`
|
||||
}
|
||||
|
|
@ -200,6 +202,7 @@ func (t *LaunchTemplate) RenderCloudformation(target *cloudformation.Cloudformat
|
|||
AssociatePublicIPAddress: e.AssociatePublicIP,
|
||||
DeleteOnTermination: fi.Bool(true),
|
||||
DeviceIndex: fi.Int(0),
|
||||
Ipv6AddressCount: e.IPv6AddressCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ type terraformLaunchTemplateNetworkInterface struct {
|
|||
AssociatePublicIPAddress *bool `json:"associate_public_ip_address,omitempty" cty:"associate_public_ip_address"`
|
||||
// DeleteOnTermination indicates whether the network interface should be destroyed on instance termination.
|
||||
DeleteOnTermination *bool `json:"delete_on_termination,omitempty" cty:"delete_on_termination"`
|
||||
// Ipv6AddressCount is the number of IPv6 addresses to assign with the primary network interface.
|
||||
Ipv6AddressCount *int64 `json:"ipv6_address_count,omitempty" cty:"ipv6_address_count"`
|
||||
// SecurityGroups is a list of security group ids.
|
||||
SecurityGroups []*terraformWriter.Literal `json:"security_groups,omitempty" cty:"security_groups"`
|
||||
}
|
||||
|
|
@ -205,6 +207,7 @@ func (t *LaunchTemplate) RenderTerraform(target *terraform.TerraformTarget, a, e
|
|||
{
|
||||
AssociatePublicIPAddress: e.AssociatePublicIP,
|
||||
DeleteOnTermination: fi.Bool(true),
|
||||
Ipv6AddressCount: e.IPv6AddressCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ type Route struct {
|
|||
RouteTable *RouteTable
|
||||
Instance *Instance
|
||||
CIDR *string
|
||||
IPv6CIDR *string
|
||||
|
||||
// Exactly one of the below fields
|
||||
// MUST be provided.
|
||||
|
|
@ -48,7 +49,7 @@ type Route struct {
|
|||
func (e *Route) Find(c *fi.Context) (*Route, error) {
|
||||
cloud := c.Cloud.(awsup.AWSCloud)
|
||||
|
||||
if e.RouteTable == nil || e.CIDR == nil {
|
||||
if e.RouteTable == nil || (e.CIDR == nil && e.IPv6CIDR == nil) {
|
||||
// TODO: Move to validate?
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -73,13 +74,15 @@ func (e *Route) Find(c *fi.Context) (*Route, error) {
|
|||
}
|
||||
rt := response.RouteTables[0]
|
||||
for _, r := range rt.Routes {
|
||||
if aws.StringValue(r.DestinationCidrBlock) != *e.CIDR {
|
||||
if (r.DestinationCidrBlock == nil || aws.StringValue(r.DestinationCidrBlock) != aws.StringValue(e.CIDR)) &&
|
||||
(r.DestinationIpv6CidrBlock == nil || aws.StringValue(r.DestinationIpv6CidrBlock) != aws.StringValue(e.IPv6CIDR)) {
|
||||
continue
|
||||
}
|
||||
actual := &Route{
|
||||
Name: e.Name,
|
||||
RouteTable: &RouteTable{ID: rt.RouteTableId},
|
||||
CIDR: r.DestinationCidrBlock,
|
||||
IPv6CIDR: r.DestinationIpv6CidrBlock,
|
||||
}
|
||||
if r.GatewayId != nil {
|
||||
actual.InternetGateway = &InternetGateway{ID: r.GatewayId}
|
||||
|
|
@ -105,7 +108,7 @@ func (e *Route) Find(c *fi.Context) (*Route, error) {
|
|||
// Prevent spurious changes
|
||||
actual.Lifecycle = e.Lifecycle
|
||||
|
||||
klog.V(2).Infof("found route matching cidr %s", *e.CIDR)
|
||||
klog.V(2).Infof("found route matching CIDR=%q IPv6CIDR=%q", aws.StringValue(e.CIDR), aws.StringValue(e.IPv6CIDR))
|
||||
return actual, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -123,8 +126,11 @@ func (s *Route) CheckChanges(a, e, changes *Route) error {
|
|||
if e.RouteTable == nil {
|
||||
return fi.RequiredField("RouteTable")
|
||||
}
|
||||
if e.CIDR == nil {
|
||||
return fi.RequiredField("CIDR")
|
||||
if e.CIDR == nil && e.IPv6CIDR == nil {
|
||||
return fi.RequiredField("CIDR/IPv6CIDR")
|
||||
}
|
||||
if e.CIDR != nil && e.IPv6CIDR != nil {
|
||||
return fmt.Errorf("cannot set more than 1 CIDR or IPv6CIDR")
|
||||
}
|
||||
targetCount := 0
|
||||
if e.InternetGateway != nil {
|
||||
|
|
@ -143,7 +149,7 @@ func (s *Route) CheckChanges(a, e, changes *Route) error {
|
|||
return fmt.Errorf("InternetGateway, Instance, NatGateway, or TransitGateway is required")
|
||||
}
|
||||
if targetCount != 1 {
|
||||
return fmt.Errorf("Cannot set more than 1 InternetGateway, Instance, NatGateway, or TransitGateway")
|
||||
return fmt.Errorf("cannot set more than 1 InternetGateway, Instance, NatGateway, or TransitGateway")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +160,9 @@ func (s *Route) CheckChanges(a, e, changes *Route) error {
|
|||
if changes.CIDR != nil {
|
||||
return fi.CannotChangeField("CIDR")
|
||||
}
|
||||
if changes.IPv6CIDR != nil {
|
||||
return fi.CannotChangeField("IPv6CIDR")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -162,7 +171,13 @@ func (_ *Route) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Route) error {
|
|||
if a == nil {
|
||||
request := &ec2.CreateRouteInput{}
|
||||
request.RouteTableId = checkNotNil(e.RouteTable.ID)
|
||||
request.DestinationCidrBlock = checkNotNil(e.CIDR)
|
||||
|
||||
if e.CIDR != nil || e.IPv6CIDR != nil {
|
||||
request.DestinationCidrBlock = e.CIDR
|
||||
request.DestinationIpv6CidrBlock = e.IPv6CIDR
|
||||
} else {
|
||||
klog.Fatal("both CIDR and IPv6CIDR were unexpectedly nil")
|
||||
}
|
||||
|
||||
if e.InternetGateway == nil && e.NatGateway == nil && e.TransitGatewayID == nil {
|
||||
return fmt.Errorf("missing target for route")
|
||||
|
|
@ -178,7 +193,8 @@ func (_ *Route) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Route) error {
|
|||
request.InstanceId = checkNotNil(e.Instance.ID)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("Creating Route with RouteTable:%q CIDR:%q", *e.RouteTable.ID, *e.CIDR)
|
||||
klog.V(2).Infof("Creating Route with RouteTable:%q CIDR:%q IPv6CIDR:%q",
|
||||
aws.StringValue(e.RouteTable.ID), aws.StringValue(e.CIDR), aws.StringValue(e.IPv6CIDR))
|
||||
|
||||
response, err := t.Cloud.EC2().CreateRoute(request)
|
||||
if err != nil {
|
||||
|
|
@ -197,7 +213,13 @@ func (_ *Route) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Route) error {
|
|||
} else {
|
||||
request := &ec2.ReplaceRouteInput{}
|
||||
request.RouteTableId = checkNotNil(e.RouteTable.ID)
|
||||
request.DestinationCidrBlock = checkNotNil(e.CIDR)
|
||||
|
||||
if e.CIDR != nil || e.IPv6CIDR != nil {
|
||||
request.DestinationCidrBlock = e.CIDR
|
||||
request.DestinationIpv6CidrBlock = e.IPv6CIDR
|
||||
} else {
|
||||
klog.Fatal("both CIDR and IPv6CIDR were unexpectedly nil")
|
||||
}
|
||||
|
||||
if e.InternetGateway == nil && e.NatGateway == nil && e.TransitGatewayID == nil {
|
||||
return fmt.Errorf("missing target for route")
|
||||
|
|
@ -239,6 +261,7 @@ func checkNotNil(s *string) *string {
|
|||
type terraformRoute struct {
|
||||
RouteTableID *terraformWriter.Literal `json:"route_table_id" cty:"route_table_id"`
|
||||
CIDR *string `json:"destination_cidr_block,omitempty" cty:"destination_cidr_block"`
|
||||
IPv6CIDR *string `json:"destination_ipv6_cidr_block,omitempty" cty:"destination_ipv6_cidr_block"`
|
||||
InternetGatewayID *terraformWriter.Literal `json:"gateway_id,omitempty" cty:"gateway_id"`
|
||||
NATGatewayID *terraformWriter.Literal `json:"nat_gateway_id,omitempty" cty:"nat_gateway_id"`
|
||||
TransitGatewayID *string `json:"transit_gateway_id,omitempty" cty:"transit_gateway_id"`
|
||||
|
|
@ -247,8 +270,9 @@ type terraformRoute struct {
|
|||
|
||||
func (_ *Route) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Route) error {
|
||||
tf := &terraformRoute{
|
||||
CIDR: e.CIDR,
|
||||
RouteTableID: e.RouteTable.TerraformLink(),
|
||||
CIDR: e.CIDR,
|
||||
IPv6CIDR: e.IPv6CIDR,
|
||||
}
|
||||
|
||||
if e.InternetGateway == nil && e.NatGateway == nil && e.TransitGatewayID == nil {
|
||||
|
|
@ -274,6 +298,7 @@ func (_ *Route) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Rou
|
|||
type cloudformationRoute struct {
|
||||
RouteTableID *cloudformation.Literal `json:"RouteTableId"`
|
||||
CIDR *string `json:"DestinationCidrBlock,omitempty"`
|
||||
IPv6CIDR *string `json:"DestinationIpv6CidrBlock,omitempty"`
|
||||
InternetGatewayID *cloudformation.Literal `json:"GatewayId,omitempty"`
|
||||
NATGatewayID *cloudformation.Literal `json:"NatGatewayId,omitempty"`
|
||||
TransitGatewayID *string `json:"TransitGatewayId,omitempty"`
|
||||
|
|
@ -282,8 +307,9 @@ type cloudformationRoute struct {
|
|||
|
||||
func (_ *Route) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *Route) error {
|
||||
tf := &cloudformationRoute{
|
||||
CIDR: e.CIDR,
|
||||
RouteTableID: e.RouteTable.CloudformationLink(),
|
||||
CIDR: e.CIDR,
|
||||
IPv6CIDR: e.IPv6CIDR,
|
||||
}
|
||||
|
||||
if e.InternetGateway == nil && e.NatGateway == nil && e.TransitGatewayID == nil {
|
||||
|
|
|
|||
|
|
@ -325,6 +325,9 @@ func (d *deleteSecurityGroupRule) Item() string {
|
|||
for _, r := range p.IpRanges {
|
||||
s += fmt.Sprintf(" ip=%s", aws.StringValue(r.CidrIp))
|
||||
}
|
||||
for _, r := range p.Ipv6Ranges {
|
||||
s += fmt.Sprintf(" ipv6=%s", aws.StringValue(r.CidrIpv6))
|
||||
}
|
||||
//permissionString := fi.DebugAsJsonString(d.permission)
|
||||
//s += permissionString
|
||||
|
||||
|
|
@ -347,6 +350,13 @@ func expandPermissions(sgID *string, permission *ec2.IpPermission, egress bool)
|
|||
rules = append(rules, a)
|
||||
}
|
||||
|
||||
for _, ipv6Range := range permission.Ipv6Ranges {
|
||||
a := &ec2.IpPermission{}
|
||||
*a = *master
|
||||
a.Ipv6Ranges = []*ec2.Ipv6Range{ipv6Range}
|
||||
rules = append(rules, a)
|
||||
}
|
||||
|
||||
for _, ug := range permission.UserIdGroupPairs {
|
||||
a := &ec2.IpPermission{}
|
||||
*a = *master
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ type SecurityGroupRule struct {
|
|||
|
||||
SecurityGroup *SecurityGroup
|
||||
CIDR *string
|
||||
IPv6CIDR *string
|
||||
Protocol *string
|
||||
|
||||
// FromPort is the lower-bound (inclusive) of the port-range
|
||||
|
|
@ -113,6 +114,9 @@ func (e *SecurityGroupRule) Find(c *fi.Context) (*SecurityGroupRule, error) {
|
|||
if e.CIDR != nil {
|
||||
actual.CIDR = e.CIDR
|
||||
}
|
||||
if e.IPv6CIDR != nil {
|
||||
actual.IPv6CIDR = e.IPv6CIDR
|
||||
}
|
||||
if e.SourceGroup != nil {
|
||||
actual.SourceGroup = &SecurityGroup{ID: e.SourceGroup.ID}
|
||||
}
|
||||
|
|
@ -143,7 +147,6 @@ func (e *SecurityGroupRule) matches(rule *ec2.IpPermission) bool {
|
|||
}
|
||||
|
||||
if e.CIDR != nil {
|
||||
// TODO: Only if len 1?
|
||||
match := false
|
||||
for _, ipRange := range rule.IpRanges {
|
||||
if aws.StringValue(ipRange.CidrIp) == *e.CIDR {
|
||||
|
|
@ -156,8 +159,20 @@ func (e *SecurityGroupRule) matches(rule *ec2.IpPermission) bool {
|
|||
}
|
||||
}
|
||||
|
||||
if e.IPv6CIDR != nil {
|
||||
match := false
|
||||
for _, ipv6Range := range rule.Ipv6Ranges {
|
||||
if aws.StringValue(ipv6Range.CidrIpv6) == *e.IPv6CIDR {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if e.SourceGroup != nil {
|
||||
// TODO: Only if len 1?
|
||||
match := false
|
||||
for _, spec := range rule.UserIdGroupPairs {
|
||||
if e.SourceGroup == nil {
|
||||
|
|
@ -190,6 +205,9 @@ func (_ *SecurityGroupRule) CheckChanges(a, e, changes *SecurityGroupRule) error
|
|||
if e.SecurityGroup == nil {
|
||||
return field.Required(field.NewPath("SecurityGroup"), "")
|
||||
}
|
||||
if e.CIDR != nil && e.IPv6CIDR != nil {
|
||||
return field.Forbidden(field.NewPath("CIDR/IPv6CIDR"), "Cannot set more than 1 CIDR or IPv6CIDR")
|
||||
}
|
||||
}
|
||||
|
||||
if e.FromPort != nil && e.Protocol == nil {
|
||||
|
|
@ -226,6 +244,10 @@ func (e *SecurityGroupRule) Description() string {
|
|||
description = append(description, fmt.Sprintf("cidr=%s", *e.CIDR))
|
||||
}
|
||||
|
||||
if e.IPv6CIDR != nil {
|
||||
description = append(description, fmt.Sprintf("ipv6cidr=%s", *e.IPv6CIDR))
|
||||
}
|
||||
|
||||
return strings.Join(description, " ")
|
||||
}
|
||||
|
||||
|
|
@ -250,12 +272,20 @@ func (_ *SecurityGroupRule) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Secu
|
|||
GroupId: e.SourceGroup.ID,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
} else if e.IPv6CIDR != nil {
|
||||
IPv6CIDR := e.IPv6CIDR
|
||||
ipPermission.Ipv6Ranges = []*ec2.Ipv6Range{
|
||||
{CidrIpv6: IPv6CIDR},
|
||||
}
|
||||
} else if e.CIDR != nil {
|
||||
CIDR := e.CIDR
|
||||
// Default to 0.0.0.0/0 ?
|
||||
ipPermission.IpRanges = []*ec2.IpRange{
|
||||
{CidrIp: CIDR},
|
||||
}
|
||||
} else {
|
||||
ipPermission.IpRanges = []*ec2.IpRange{
|
||||
{CidrIp: aws.String("0.0.0.0/0")},
|
||||
}
|
||||
}
|
||||
|
||||
description := e.Description()
|
||||
|
|
@ -300,8 +330,9 @@ type terraformSecurityGroupIngress struct {
|
|||
FromPort *int64 `json:"from_port,omitempty" cty:"from_port"`
|
||||
ToPort *int64 `json:"to_port,omitempty" cty:"to_port"`
|
||||
|
||||
Protocol *string `json:"protocol,omitempty" cty:"protocol"`
|
||||
CIDRBlocks []string `json:"cidr_blocks,omitempty" cty:"cidr_blocks"`
|
||||
Protocol *string `json:"protocol,omitempty" cty:"protocol"`
|
||||
CIDRBlocks []string `json:"cidr_blocks,omitempty" cty:"cidr_blocks"`
|
||||
IPv6CIDRBlocks []string `json:"ipv6_cidr_blocks,omitempty" cty:"ipv6_cidr_blocks"`
|
||||
}
|
||||
|
||||
func (_ *SecurityGroupRule) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *SecurityGroupRule) error {
|
||||
|
|
@ -338,6 +369,10 @@ func (_ *SecurityGroupRule) RenderTerraform(t *terraform.TerraformTarget, a, e,
|
|||
if e.CIDR != nil {
|
||||
tf.CIDRBlocks = append(tf.CIDRBlocks, *e.CIDR)
|
||||
}
|
||||
if e.IPv6CIDR != nil {
|
||||
tf.IPv6CIDRBlocks = append(tf.IPv6CIDRBlocks, *e.IPv6CIDR)
|
||||
}
|
||||
|
||||
return t.RenderResource("aws_security_group_rule", *e.Name, tf)
|
||||
}
|
||||
|
||||
|
|
@ -386,11 +421,10 @@ func (_ *SecurityGroupRule) RenderCloudformation(t *cloudformation.Cloudformatio
|
|||
}
|
||||
|
||||
if e.CIDR != nil {
|
||||
if strings.Contains(fi.StringValue(e.CIDR), ":") {
|
||||
tf.CidrIpv6 = e.CIDR
|
||||
} else {
|
||||
tf.CidrIp = e.CIDR
|
||||
}
|
||||
tf.CidrIp = e.CIDR
|
||||
}
|
||||
if e.IPv6CIDR != nil {
|
||||
tf.CidrIpv6 = e.IPv6CIDR
|
||||
}
|
||||
|
||||
return t.RenderResource(cfType, *e.Name, tf)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package awstasks
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/klog/v2"
|
||||
|
|
@ -45,6 +46,7 @@ type Subnet struct {
|
|||
VPC *VPC
|
||||
AvailabilityZone *string
|
||||
CIDR *string
|
||||
IPv6CIDR *string
|
||||
Shared *bool
|
||||
|
||||
Tags map[string]string
|
||||
|
|
@ -85,6 +87,19 @@ func (e *Subnet) Find(c *fi.Context) (*Subnet, error) {
|
|||
Tags: intersectTags(subnet.Tags, e.Tags),
|
||||
}
|
||||
|
||||
for _, association := range subnet.Ipv6CidrBlockAssociationSet {
|
||||
if association == nil || association.Ipv6CidrBlockState == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
state := aws.StringValue(association.Ipv6CidrBlockState.State)
|
||||
if state != ec2.SubnetCidrBlockStateCodeAssociated && state != ec2.SubnetCidrBlockStateCodeAssociating {
|
||||
continue
|
||||
}
|
||||
|
||||
actual.IPv6CIDR = association.Ipv6CidrBlock
|
||||
}
|
||||
|
||||
klog.V(2).Infof("found matching subnet %q", *actual.ID)
|
||||
e.ID = actual.ID
|
||||
|
||||
|
|
@ -155,10 +170,13 @@ func (s *Subnet) CheckChanges(a, e, changes *Subnet) error {
|
|||
errors = append(errors, fi.FieldIsImmutable(eID, aID, fieldPath.Child("VPC")))
|
||||
}
|
||||
if changes.AvailabilityZone != nil {
|
||||
errors = append(errors, fi.FieldIsImmutable(a.AvailabilityZone, e.AvailabilityZone, fieldPath.Child("AvailabilityZone")))
|
||||
errors = append(errors, fi.FieldIsImmutable(e.AvailabilityZone, a.AvailabilityZone, fieldPath.Child("AvailabilityZone")))
|
||||
}
|
||||
if changes.CIDR != nil {
|
||||
errors = append(errors, fi.FieldIsImmutable(a.CIDR, e.CIDR, fieldPath.Child("CIDR")))
|
||||
errors = append(errors, fi.FieldIsImmutable(e.CIDR, a.CIDR, fieldPath.Child("CIDR")))
|
||||
}
|
||||
if changes.IPv6CIDR != nil && a.IPv6CIDR != nil {
|
||||
errors = append(errors, fi.FieldIsImmutable(e.IPv6CIDR, a.IPv6CIDR, fieldPath.Child("IPv6CIDR")))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +201,7 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error {
|
|||
|
||||
request := &ec2.CreateSubnetInput{
|
||||
CidrBlock: e.CIDR,
|
||||
Ipv6CidrBlock: e.IPv6CIDR,
|
||||
AvailabilityZone: e.AvailabilityZone,
|
||||
VpcId: e.VPC.ID,
|
||||
TagSpecifications: awsup.EC2TagSpecification(ec2.ResourceTypeSubnet, e.Tags),
|
||||
|
|
@ -194,6 +213,18 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error {
|
|||
}
|
||||
|
||||
e.ID = response.Subnet.SubnetId
|
||||
} else {
|
||||
if changes.IPv6CIDR != nil {
|
||||
request := &ec2.AssociateSubnetCidrBlockInput{
|
||||
Ipv6CidrBlock: e.IPv6CIDR,
|
||||
SubnetId: e.ID,
|
||||
}
|
||||
|
||||
_, err := t.Cloud.EC2().AssociateSubnetCidrBlock(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error associating subnet cidr block: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t.AddAWSTags(*e.ID, e.Tags)
|
||||
|
|
@ -218,6 +249,7 @@ func subnetSlicesEqualIgnoreOrder(l, r []*Subnet) bool {
|
|||
type terraformSubnet struct {
|
||||
VPCID *terraformWriter.Literal `json:"vpc_id" cty:"vpc_id"`
|
||||
CIDR *string `json:"cidr_block" cty:"cidr_block"`
|
||||
IPv6CIDR *string `json:"ipv6_cidr_block" cty:"ipv6_cidr_block"`
|
||||
AvailabilityZone *string `json:"availability_zone" cty:"availability_zone"`
|
||||
Tags map[string]string `json:"tags,omitempty" cty:"tags"`
|
||||
}
|
||||
|
|
@ -244,6 +276,7 @@ func (_ *Subnet) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Su
|
|||
tf := &terraformSubnet{
|
||||
VPCID: e.VPC.TerraformLink(),
|
||||
CIDR: e.CIDR,
|
||||
IPv6CIDR: e.IPv6CIDR,
|
||||
AvailabilityZone: e.AvailabilityZone,
|
||||
Tags: e.Tags,
|
||||
}
|
||||
|
|
@ -268,6 +301,7 @@ func (e *Subnet) TerraformLink() *terraformWriter.Literal {
|
|||
type cloudformationSubnet struct {
|
||||
VPCID *cloudformation.Literal `json:"VpcId,omitempty"`
|
||||
CIDR *string `json:"CidrBlock,omitempty"`
|
||||
IPv6CIDR *string `json:"Ipv6CidrBlock,omitempty"`
|
||||
AvailabilityZone *string `json:"AvailabilityZone,omitempty"`
|
||||
Tags []cloudformationTag `json:"Tags,omitempty"`
|
||||
}
|
||||
|
|
@ -283,6 +317,7 @@ func (_ *Subnet) RenderCloudformation(t *cloudformation.CloudformationTarget, a,
|
|||
cf := &cloudformationSubnet{
|
||||
VPCID: e.VPC.CloudformationLink(),
|
||||
CIDR: e.CIDR,
|
||||
IPv6CIDR: e.IPv6CIDR,
|
||||
AvailabilityZone: e.AvailabilityZone,
|
||||
Tags: buildCloudformationTags(e.Tags),
|
||||
}
|
||||
|
|
@ -303,3 +338,74 @@ func (e *Subnet) CloudformationLink() *cloudformation.Literal {
|
|||
|
||||
return cloudformation.Ref("AWS::EC2::Subnet", *e.Name)
|
||||
}
|
||||
|
||||
func (e *Subnet) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
|
||||
if e.ID == nil || aws.BoolValue(e.Shared) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
subnet, err := e.findEc2Subnet(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if subnet == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var removals []fi.Deletion
|
||||
for _, association := range subnet.Ipv6CidrBlockAssociationSet {
|
||||
// Skip when without state
|
||||
if association == nil || association.Ipv6CidrBlockState == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip when already disassociated
|
||||
state := aws.StringValue(association.Ipv6CidrBlockState.State)
|
||||
if state == ec2.SubnetCidrBlockStateCodeDisassociated || state == ec2.SubnetCidrBlockStateCodeDisassociating {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip when current IPv6CIDR
|
||||
if aws.StringValue(e.IPv6CIDR) == aws.StringValue(association.Ipv6CidrBlock) {
|
||||
continue
|
||||
}
|
||||
|
||||
removals = append(removals, &deleteSubnetIPv6CIDRBlock{
|
||||
vpcID: subnet.VpcId,
|
||||
ipv6CidrBlock: association.Ipv6CidrBlock,
|
||||
associationID: association.AssociationId,
|
||||
})
|
||||
}
|
||||
|
||||
return removals, nil
|
||||
}
|
||||
|
||||
type deleteSubnetIPv6CIDRBlock struct {
|
||||
vpcID *string
|
||||
ipv6CidrBlock *string
|
||||
associationID *string
|
||||
}
|
||||
|
||||
var _ fi.Deletion = &deleteSubnetIPv6CIDRBlock{}
|
||||
|
||||
func (d *deleteSubnetIPv6CIDRBlock) Delete(t fi.Target) error {
|
||||
awsTarget, ok := t.(*awsup.AWSAPITarget)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected target type for deletion: %T", t)
|
||||
}
|
||||
|
||||
request := &ec2.DisassociateSubnetCidrBlockInput{
|
||||
AssociationId: d.associationID,
|
||||
}
|
||||
_, err := awsTarget.Cloud.EC2().DisassociateSubnetCidrBlock(request)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *deleteSubnetIPv6CIDRBlock) TaskName() string {
|
||||
return "SubnetIPv6CIDRBlock"
|
||||
}
|
||||
|
||||
func (d *deleteSubnetIPv6CIDRBlock) Item() string {
|
||||
return fmt.Sprintf("%v: ipv6cidr=%v", *d.vpcID, *d.ipv6CidrBlock)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func Test_Subnet_CannotChangeSubnet(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("validation error was expected")
|
||||
}
|
||||
if fmt.Sprintf("%v", err) != "Subnet.CIDR: Forbidden: field is immutable: old=\"192.168.0.1/16\" new=\"192.168.0.0/16\"" {
|
||||
if fmt.Sprintf("%v", err) != "Subnet.CIDR: Forbidden: field is immutable: old=\"192.168.0.0/16\" new=\"192.168.0.1/16\"" {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,14 @@ type VPC struct {
|
|||
Name *string
|
||||
Lifecycle *fi.Lifecycle
|
||||
|
||||
ID *string
|
||||
CIDR *string
|
||||
ID *string
|
||||
CIDR *string
|
||||
|
||||
// AmazonIPv6 is used only for Terraform rendering.
|
||||
// Direct and CloudFormation rendering is handled via the VPCAmazonIPv6CIDRBlock task
|
||||
AmazonIPv6 *bool
|
||||
IPv6CIDR *string
|
||||
|
||||
EnableDNSHostnames *bool
|
||||
EnableDNSSupport *bool
|
||||
|
||||
|
|
@ -83,14 +89,34 @@ func (e *VPC) Find(c *fi.Context) (*VPC, error) {
|
|||
}
|
||||
vpc := response.Vpcs[0]
|
||||
actual := &VPC{
|
||||
ID: vpc.VpcId,
|
||||
CIDR: vpc.CidrBlock,
|
||||
Name: findNameTag(vpc.Tags),
|
||||
Tags: intersectTags(vpc.Tags, e.Tags),
|
||||
ID: vpc.VpcId,
|
||||
CIDR: vpc.CidrBlock,
|
||||
AmazonIPv6: aws.Bool(false),
|
||||
Name: findNameTag(vpc.Tags),
|
||||
Tags: intersectTags(vpc.Tags, e.Tags),
|
||||
}
|
||||
|
||||
klog.V(4).Infof("found matching VPC %v", actual)
|
||||
|
||||
for _, association := range vpc.Ipv6CidrBlockAssociationSet {
|
||||
if association == nil || association.Ipv6CidrBlockState == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
state := aws.StringValue(association.Ipv6CidrBlockState.State)
|
||||
if state != ec2.VpcCidrBlockStateCodeAssociated && state != ec2.VpcCidrBlockStateCodeAssociating {
|
||||
continue
|
||||
}
|
||||
|
||||
pool := aws.StringValue(association.Ipv6Pool)
|
||||
if pool == "Amazon" {
|
||||
actual.AmazonIPv6 = aws.Bool(true)
|
||||
actual.IPv6CIDR = association.Ipv6CidrBlock
|
||||
e.IPv6CIDR = association.Ipv6CidrBlock
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if actual.ID != nil {
|
||||
request := &ec2.DescribeVpcAttributeInput{VpcId: actual.ID, Attribute: aws.String(ec2.VpcAttributeNameEnableDnsSupport)}
|
||||
response, err := cloud.EC2().DescribeVpcAttribute(request)
|
||||
|
|
@ -253,6 +279,7 @@ type terraformVPC struct {
|
|||
CIDR *string `json:"cidr_block,omitempty" cty:"cidr_block"`
|
||||
EnableDNSHostnames *bool `json:"enable_dns_hostnames,omitempty" cty:"enable_dns_hostnames"`
|
||||
EnableDNSSupport *bool `json:"enable_dns_support,omitempty" cty:"enable_dns_support"`
|
||||
AmazonIPv6 *bool `json:"assign_generated_ipv6_cidr_block,omitempty" cty:"assign_generated_ipv6_cidr_block"`
|
||||
Tags map[string]string `json:"tags,omitempty" cty:"tags"`
|
||||
}
|
||||
|
||||
|
|
@ -278,6 +305,7 @@ func (_ *VPC) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *VPC)
|
|||
Tags: e.Tags,
|
||||
EnableDNSHostnames: e.EnableDNSHostnames,
|
||||
EnableDNSSupport: e.EnableDNSSupport,
|
||||
AmazonIPv6: e.AmazonIPv6,
|
||||
}
|
||||
|
||||
return t.RenderResource("aws_vpc", *e.Name, tf)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package awstasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
|
||||
)
|
||||
|
||||
// +kops:fitask
|
||||
type VPCAmazonIPv6CIDRBlock struct {
|
||||
Name *string
|
||||
Lifecycle *fi.Lifecycle
|
||||
|
||||
VPC *VPC
|
||||
CIDRBlock *string
|
||||
|
||||
// Shared is set if this is a shared VPC
|
||||
Shared *bool
|
||||
}
|
||||
|
||||
func (e *VPCAmazonIPv6CIDRBlock) Find(c *fi.Context) (*VPCAmazonIPv6CIDRBlock, error) {
|
||||
cloud := c.Cloud.(awsup.AWSCloud)
|
||||
|
||||
vpc, err := cloud.DescribeVPC(aws.StringValue(e.VPC.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cidr *string
|
||||
for _, association := range vpc.Ipv6CidrBlockAssociationSet {
|
||||
if association == nil || association.Ipv6CidrBlockState == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
state := aws.StringValue(association.Ipv6CidrBlockState.State)
|
||||
if state != ec2.VpcCidrBlockStateCodeAssociated && state != ec2.VpcCidrBlockStateCodeAssociating {
|
||||
continue
|
||||
}
|
||||
|
||||
if aws.StringValue(association.Ipv6Pool) == "Amazon" {
|
||||
cidr = association.Ipv6CidrBlock
|
||||
break
|
||||
}
|
||||
}
|
||||
if cidr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
actual := &VPCAmazonIPv6CIDRBlock{
|
||||
VPC: &VPC{ID: vpc.VpcId},
|
||||
CIDRBlock: cidr,
|
||||
}
|
||||
|
||||
// Expose the Amazon provided IPv6 CIDR block to other tasks
|
||||
e.CIDRBlock = cidr
|
||||
|
||||
// Prevent spurious changes
|
||||
actual.Shared = e.Shared
|
||||
actual.Name = e.Name
|
||||
actual.Lifecycle = e.Lifecycle
|
||||
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
func (e *VPCAmazonIPv6CIDRBlock) Run(c *fi.Context) error {
|
||||
return fi.DefaultDeltaRunMethod(e, c)
|
||||
}
|
||||
|
||||
func (s *VPCAmazonIPv6CIDRBlock) CheckChanges(a, e, changes *VPCAmazonIPv6CIDRBlock) error {
|
||||
if e.VPC == nil {
|
||||
return fi.RequiredField("VPC")
|
||||
}
|
||||
|
||||
if a != nil && changes != nil {
|
||||
if changes.VPC != nil {
|
||||
return fi.CannotChangeField("VPC")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_ *VPCAmazonIPv6CIDRBlock) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *VPCAmazonIPv6CIDRBlock) error {
|
||||
shared := aws.BoolValue(e.Shared)
|
||||
if shared && a == nil {
|
||||
// VPC not owned by kOps, no changes will be applied
|
||||
// Verify that the Amazon IPv6 provided CIDR block was found.
|
||||
return fmt.Errorf("IPv6 CIDR block provided by Amazon not found")
|
||||
}
|
||||
|
||||
request := &ec2.AssociateVpcCidrBlockInput{
|
||||
VpcId: e.VPC.ID,
|
||||
AmazonProvidedIpv6CidrBlock: aws.Bool(true),
|
||||
}
|
||||
|
||||
_, err := t.Cloud.EC2().AssociateVpcCidrBlock(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error associating Amazon IPv6 provided CIDR block to VPC: %v", err)
|
||||
}
|
||||
|
||||
return nil // no tags
|
||||
}
|
||||
|
||||
func (_ *VPCAmazonIPv6CIDRBlock) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *VPCAmazonIPv6CIDRBlock) error {
|
||||
// At the moment, this can only be done via the aws_vpc resource
|
||||
return nil
|
||||
}
|
||||
|
||||
type cloudformationVPCAmazonIPv6CIDRBlock struct {
|
||||
VPCID *cloudformation.Literal `json:"VpcId"`
|
||||
AmazonIPv6 *bool `json:"AmazonProvidedIpv6CidrBlock"`
|
||||
}
|
||||
|
||||
func (_ *VPCAmazonIPv6CIDRBlock) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *VPCAmazonIPv6CIDRBlock) error {
|
||||
shared := aws.BoolValue(e.Shared)
|
||||
if shared && a == nil {
|
||||
// VPC not owned by kOps, no changes will be applied
|
||||
// Verify that the Amazon IPv6 provided CIDR block was found.
|
||||
return fmt.Errorf("IPv6 CIDR block provided by Amazon not found")
|
||||
}
|
||||
|
||||
cf := &cloudformationVPCAmazonIPv6CIDRBlock{
|
||||
VPCID: e.VPC.CloudformationLink(),
|
||||
AmazonIPv6: aws.Bool(true),
|
||||
}
|
||||
|
||||
return t.RenderResource("AWS::EC2::VPCCidrBlock", *e.Name, cf)
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by fitask. DO NOT EDIT.
|
||||
|
||||
package awstasks
|
||||
|
||||
import (
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
// VPCAmazonIPv6CIDRBlock
|
||||
|
||||
var _ fi.HasLifecycle = &VPCAmazonIPv6CIDRBlock{}
|
||||
|
||||
// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle
|
||||
func (o *VPCAmazonIPv6CIDRBlock) GetLifecycle() *fi.Lifecycle {
|
||||
return o.Lifecycle
|
||||
}
|
||||
|
||||
// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle
|
||||
func (o *VPCAmazonIPv6CIDRBlock) SetLifecycle(lifecycle fi.Lifecycle) {
|
||||
o.Lifecycle = &lifecycle
|
||||
}
|
||||
|
||||
var _ fi.HasName = &VPCAmazonIPv6CIDRBlock{}
|
||||
|
||||
// GetName returns the Name of the object, implementing fi.HasName
|
||||
func (o *VPCAmazonIPv6CIDRBlock) GetName() *string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
// String is the stringer function for the task, producing readable output using fi.TaskAsString
|
||||
func (o *VPCAmazonIPv6CIDRBlock) String() string {
|
||||
return fi.TaskAsString(o)
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package awstasks
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
||||
|
|
@ -42,19 +43,28 @@ type VPCCIDRBlock struct {
|
|||
func (e *VPCCIDRBlock) Find(c *fi.Context) (*VPCCIDRBlock, error) {
|
||||
cloud := c.Cloud.(awsup.AWSCloud)
|
||||
|
||||
vpcID := e.VPC.ID
|
||||
|
||||
vpc, err := cloud.DescribeVPC(*vpcID)
|
||||
vpcID := aws.StringValue(e.VPC.ID)
|
||||
vpc, err := cloud.DescribeVPC(vpcID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, cba := range vpc.CidrBlockAssociationSet {
|
||||
if fi.StringValue(cba.CidrBlock) == fi.StringValue(e.CIDRBlock) &&
|
||||
cba.CidrBlockState != nil && fi.StringValue(cba.CidrBlockState.State) == ec2.VpcCidrBlockStateCodeAssociated {
|
||||
found = true
|
||||
break
|
||||
if e.CIDRBlock != nil {
|
||||
for _, cba := range vpc.CidrBlockAssociationSet {
|
||||
if cba == nil || cba.CidrBlockState == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
state := aws.StringValue(cba.CidrBlockState.State)
|
||||
if state != ec2.VpcCidrBlockStateCodeAssociated && state != ec2.VpcCidrBlockStateCodeAssociating {
|
||||
continue
|
||||
}
|
||||
|
||||
if aws.StringValue(cba.CidrBlock) == aws.StringValue(e.CIDRBlock) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
|
|
@ -62,8 +72,8 @@ func (e *VPCCIDRBlock) Find(c *fi.Context) (*VPCCIDRBlock, error) {
|
|||
}
|
||||
|
||||
actual := &VPCCIDRBlock{
|
||||
CIDRBlock: e.CIDRBlock,
|
||||
VPC: &VPC{ID: vpc.VpcId},
|
||||
CIDRBlock: e.CIDRBlock,
|
||||
}
|
||||
|
||||
// Prevent spurious changes
|
||||
|
|
@ -101,12 +111,11 @@ func (s *VPCCIDRBlock) CheckChanges(a, e, changes *VPCCIDRBlock) error {
|
|||
}
|
||||
|
||||
func (_ *VPCCIDRBlock) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *VPCCIDRBlock) error {
|
||||
shared := fi.BoolValue(e.Shared)
|
||||
if shared {
|
||||
// Verify the CIDR block was found.
|
||||
if a == nil {
|
||||
return fmt.Errorf("CIDR block %q not found", fi.StringValue(e.CIDRBlock))
|
||||
}
|
||||
shared := aws.BoolValue(e.Shared)
|
||||
if shared && a == nil {
|
||||
// VPC not owned by kOps, no changes will be applied
|
||||
// Verify that the CIDR block was found.
|
||||
return fmt.Errorf("CIDR block %q not found", aws.StringValue(e.CIDRBlock))
|
||||
}
|
||||
|
||||
if changes.CIDRBlock != nil {
|
||||
|
|
@ -130,6 +139,12 @@ type terraformVPCCIDRBlock struct {
|
|||
}
|
||||
|
||||
func (_ *VPCCIDRBlock) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *VPCCIDRBlock) error {
|
||||
shared := aws.BoolValue(e.Shared)
|
||||
if shared && a == nil {
|
||||
// VPC not owned by kOps, no changes will be applied
|
||||
// Verify that the CIDR block was found.
|
||||
return fmt.Errorf("CIDR block %q not found", aws.StringValue(e.CIDRBlock))
|
||||
}
|
||||
|
||||
// When this has been enabled please fix test TestAdditionalCIDR in integration_test.go to run runTestAWS.
|
||||
tf := &terraformVPCCIDRBlock{
|
||||
|
|
@ -149,6 +164,13 @@ type cloudformationVPCCIDRBlock struct {
|
|||
}
|
||||
|
||||
func (_ *VPCCIDRBlock) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *VPCCIDRBlock) error {
|
||||
shared := aws.BoolValue(e.Shared)
|
||||
if shared && a == nil {
|
||||
// VPC not owned by kOps, no changes will be applied
|
||||
// Verify that the CIDR block was found.
|
||||
return fmt.Errorf("CIDR block %q not found", aws.StringValue(e.CIDRBlock))
|
||||
}
|
||||
|
||||
cf := &cloudformationVPCCIDRBlock{
|
||||
VPCID: e.VPC.CloudformationLink(),
|
||||
CIDRBlock: e.CIDRBlock,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cidr.go",
|
||||
"equals.go",
|
||||
"gzip.go",
|
||||
"hash.go",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IsIPv4CIDR(cidr string) bool {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must convert to IPv4
|
||||
if ip.To4() == nil {
|
||||
return false
|
||||
}
|
||||
// Must NOT contain ":"
|
||||
if strings.Contains(cidr, ":") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsIPv6CIDR(cidr string) bool {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must NOT convert to IPv4
|
||||
if ip.To4() != nil {
|
||||
return false
|
||||
}
|
||||
// Must contain ":"
|
||||
if !strings.Contains(cidr, ":") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Loading…
Reference in New Issue