Add initial support for configuring IPv6 with AWS

This commit is contained in:
Ciprian Hacman 2021-05-07 07:49:42 +03:00
parent c08d0e2bdf
commit cedbe1f360
33 changed files with 908 additions and 150 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

58
upup/pkg/fi/utils/cidr.go Normal file
View File

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