mirror of https://github.com/kubernetes/kops.git
399 lines
10 KiB
Go
399 lines
10 KiB
Go
/*
|
|
Copyright 2019 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"
|
|
"strings"
|
|
|
|
"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"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter"
|
|
"k8s.io/kops/upup/pkg/fi/utils"
|
|
)
|
|
|
|
// +kops:fitask
|
|
type SecurityGroupRule struct {
|
|
ID *string
|
|
Name *string
|
|
Lifecycle fi.Lifecycle
|
|
|
|
SecurityGroup *SecurityGroup
|
|
CIDR *string
|
|
IPv6CIDR *string
|
|
PrefixList *string
|
|
Protocol *string
|
|
|
|
// FromPort is the lower-bound (inclusive) of the port-range
|
|
FromPort *int64
|
|
// ToPort is the upper-bound (inclusive) of the port-range
|
|
ToPort *int64
|
|
SourceGroup *SecurityGroup
|
|
|
|
Egress *bool
|
|
|
|
Tags map[string]string
|
|
}
|
|
|
|
func (e *SecurityGroupRule) Find(c *fi.CloudupContext) (*SecurityGroupRule, error) {
|
|
cloud := c.T.Cloud.(awsup.AWSCloud)
|
|
|
|
if e.SecurityGroup == nil || e.SecurityGroup.ID == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if e.SourceGroup != nil && e.SourceGroup.ID == nil {
|
|
klog.V(4).Infof("Skipping find of SecurityGroupRule %s, because SourceGroup was not found", fi.ValueOf(e.Name))
|
|
return nil, nil
|
|
}
|
|
|
|
request := &ec2.DescribeSecurityGroupRulesInput{
|
|
Filters: []*ec2.Filter{
|
|
awsup.NewEC2Filter("group-id", *e.SecurityGroup.ID),
|
|
},
|
|
}
|
|
|
|
response, err := cloud.EC2().DescribeSecurityGroupRules(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing SecurityGroup: %v", err)
|
|
}
|
|
|
|
if response == nil || len(response.SecurityGroupRules) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var foundRule *ec2.SecurityGroupRule
|
|
|
|
for _, rule := range response.SecurityGroupRules {
|
|
if e.matches(rule) {
|
|
foundRule = rule
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundRule != nil {
|
|
actual := &SecurityGroupRule{
|
|
ID: foundRule.SecurityGroupRuleId,
|
|
Name: e.Name,
|
|
SecurityGroup: &SecurityGroup{ID: e.SecurityGroup.ID},
|
|
FromPort: foundRule.FromPort,
|
|
ToPort: foundRule.ToPort,
|
|
Protocol: foundRule.IpProtocol,
|
|
Egress: e.Egress,
|
|
|
|
Tags: intersectTags(foundRule.Tags, e.Tags),
|
|
}
|
|
|
|
if aws.StringValue(actual.Protocol) == "-1" {
|
|
actual.Protocol = nil
|
|
}
|
|
|
|
if fi.ValueOf(actual.Protocol) != "icmpv6" {
|
|
if fi.ValueOf(actual.FromPort) == int64(-1) {
|
|
actual.FromPort = nil
|
|
}
|
|
if fi.ValueOf(actual.ToPort) == int64(-1) {
|
|
actual.ToPort = nil
|
|
}
|
|
}
|
|
|
|
if e.CIDR != nil {
|
|
actual.CIDR = e.CIDR
|
|
}
|
|
if e.IPv6CIDR != nil {
|
|
actual.IPv6CIDR = e.IPv6CIDR
|
|
}
|
|
if e.PrefixList != nil {
|
|
actual.PrefixList = e.PrefixList
|
|
}
|
|
if e.SourceGroup != nil {
|
|
actual.SourceGroup = &SecurityGroup{ID: e.SourceGroup.ID}
|
|
}
|
|
|
|
// Avoid spurious changes
|
|
actual.Lifecycle = e.Lifecycle
|
|
|
|
e.ID = actual.ID
|
|
|
|
return actual, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (e *SecurityGroupRule) SetCidrOrPrefix(cidr string) {
|
|
if strings.HasPrefix(cidr, "pl-") {
|
|
e.PrefixList = &cidr
|
|
} else if utils.IsIPv6CIDR(cidr) {
|
|
e.IPv6CIDR = &cidr
|
|
} else {
|
|
e.CIDR = &cidr
|
|
}
|
|
}
|
|
|
|
func (e *SecurityGroupRule) matches(rule *ec2.SecurityGroupRule) bool {
|
|
matchFromPort := int64(-1)
|
|
if e.FromPort != nil {
|
|
matchFromPort = *e.FromPort
|
|
}
|
|
if aws.Int64Value(rule.FromPort) != matchFromPort {
|
|
return false
|
|
}
|
|
|
|
matchToPort := int64(-1)
|
|
if e.ToPort != nil {
|
|
matchToPort = *e.ToPort
|
|
}
|
|
if aws.Int64Value(rule.ToPort) != matchToPort {
|
|
return false
|
|
}
|
|
|
|
matchProtocol := "-1" // Wildcard
|
|
if e.Protocol != nil {
|
|
matchProtocol = *e.Protocol
|
|
}
|
|
if aws.StringValue(rule.IpProtocol) != matchProtocol {
|
|
return false
|
|
}
|
|
|
|
if fi.ValueOf(e.CIDR) != fi.ValueOf(rule.CidrIpv4) {
|
|
return false
|
|
}
|
|
|
|
if fi.ValueOf(e.IPv6CIDR) != fi.ValueOf(rule.CidrIpv6) {
|
|
return false
|
|
}
|
|
|
|
if fi.ValueOf(e.PrefixList) != fi.ValueOf(rule.PrefixListId) {
|
|
return false
|
|
}
|
|
|
|
if e.SourceGroup != nil || rule.ReferencedGroupInfo != nil {
|
|
if e.SourceGroup == nil || rule.ReferencedGroupInfo == nil {
|
|
return false
|
|
}
|
|
if fi.ValueOf(e.SourceGroup.ID) != fi.ValueOf(rule.ReferencedGroupInfo.GroupId) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (e *SecurityGroupRule) Run(c *fi.CloudupContext) error {
|
|
return fi.CloudupDefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (_ *SecurityGroupRule) CheckChanges(a, e, changes *SecurityGroupRule) error {
|
|
if a == nil {
|
|
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.PrefixList != nil && (e.CIDR != nil || e.IPv6CIDR != nil) {
|
|
return field.Forbidden(field.NewPath("PrefixList"), "Cannot set PrefixList when CIDR or IPv6CIDR is set")
|
|
}
|
|
}
|
|
|
|
if e.FromPort != nil && e.Protocol == nil {
|
|
return field.Required(field.NewPath("Protocol"), "Protocol must be specified with FromPort")
|
|
}
|
|
if e.ToPort != nil && e.Protocol == nil {
|
|
return field.Required(field.NewPath("Protocol"), "Protocol must be specified with ToPort")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Description returns a human readable summary of the security group rule
|
|
func (e *SecurityGroupRule) Description() string {
|
|
var description []string
|
|
|
|
if e.Protocol != nil {
|
|
description = append(description, fmt.Sprintf("protocol=%s", *e.Protocol))
|
|
}
|
|
|
|
if e.FromPort != nil {
|
|
description = append(description, fmt.Sprintf("fromPort=%d", *e.FromPort))
|
|
}
|
|
|
|
if e.ToPort != nil {
|
|
description = append(description, fmt.Sprintf("toPort=%d", *e.ToPort))
|
|
}
|
|
|
|
if e.SourceGroup != nil {
|
|
description = append(description, fmt.Sprintf("sourceGroup=%s", fi.ValueOf(e.SourceGroup.ID)))
|
|
}
|
|
|
|
if e.CIDR != nil {
|
|
description = append(description, fmt.Sprintf("cidr=%s", *e.CIDR))
|
|
}
|
|
|
|
if e.IPv6CIDR != nil {
|
|
description = append(description, fmt.Sprintf("ipv6cidr=%s", *e.IPv6CIDR))
|
|
}
|
|
|
|
if e.PrefixList != nil {
|
|
description = append(description, fmt.Sprintf("prefixList=%s", *e.PrefixList))
|
|
}
|
|
|
|
return strings.Join(description, " ")
|
|
}
|
|
|
|
func (_ *SecurityGroupRule) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *SecurityGroupRule) error {
|
|
name := fi.ValueOf(e.Name)
|
|
|
|
if a == nil {
|
|
protocol := e.Protocol
|
|
if protocol == nil {
|
|
protocol = aws.String("-1")
|
|
}
|
|
|
|
ipPermission := &ec2.IpPermission{
|
|
IpProtocol: protocol,
|
|
FromPort: e.FromPort,
|
|
ToPort: e.ToPort,
|
|
}
|
|
|
|
if e.SourceGroup != nil {
|
|
ipPermission.UserIdGroupPairs = []*ec2.UserIdGroupPair{
|
|
{
|
|
GroupId: e.SourceGroup.ID,
|
|
},
|
|
}
|
|
} else if e.IPv6CIDR != nil {
|
|
IPv6CIDR := e.IPv6CIDR
|
|
ipPermission.Ipv6Ranges = []*ec2.Ipv6Range{
|
|
{CidrIpv6: IPv6CIDR},
|
|
}
|
|
} else if e.CIDR != nil {
|
|
CIDR := e.CIDR
|
|
ipPermission.IpRanges = []*ec2.IpRange{
|
|
{CidrIp: CIDR},
|
|
}
|
|
} else if e.PrefixList != nil {
|
|
PrefixList := e.PrefixList
|
|
ipPermission.PrefixListIds = []*ec2.PrefixListId{
|
|
{PrefixListId: PrefixList},
|
|
}
|
|
} else {
|
|
ipPermission.IpRanges = []*ec2.IpRange{
|
|
{CidrIp: aws.String("0.0.0.0/0")},
|
|
}
|
|
}
|
|
|
|
description := e.Description()
|
|
|
|
if fi.ValueOf(e.Egress) {
|
|
request := &ec2.AuthorizeSecurityGroupEgressInput{
|
|
GroupId: e.SecurityGroup.ID,
|
|
}
|
|
request.IpPermissions = []*ec2.IpPermission{ipPermission}
|
|
request.TagSpecifications = awsup.EC2TagSpecification(ec2.ResourceTypeSecurityGroupRule, e.Tags)
|
|
|
|
klog.V(2).Infof("%s: Calling EC2 AuthorizeSecurityGroupEgress (%s)", name, description)
|
|
_, err := t.Cloud.EC2().AuthorizeSecurityGroupEgress(request)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating SecurityGroupEgress: %v", err)
|
|
}
|
|
} else {
|
|
request := &ec2.AuthorizeSecurityGroupIngressInput{
|
|
GroupId: e.SecurityGroup.ID,
|
|
}
|
|
request.IpPermissions = []*ec2.IpPermission{ipPermission}
|
|
request.TagSpecifications = awsup.EC2TagSpecification(ec2.ResourceTypeSecurityGroupRule, e.Tags)
|
|
|
|
klog.V(2).Infof("%s: Calling EC2 AuthorizeSecurityGroupIngress (%s)", name, description)
|
|
_, err := t.Cloud.EC2().AuthorizeSecurityGroupIngress(request)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating SecurityGroupIngress: %v", err)
|
|
}
|
|
}
|
|
|
|
} else if changes.Tags != nil {
|
|
return t.AddAWSTags(*a.ID, e.Tags)
|
|
}
|
|
|
|
// No tags on security group rules (there are tags on the group though)
|
|
|
|
return nil
|
|
}
|
|
|
|
type terraformSecurityGroupIngress struct {
|
|
Type *string `cty:"type"`
|
|
|
|
SecurityGroup *terraformWriter.Literal `cty:"security_group_id"`
|
|
SourceGroup *terraformWriter.Literal `cty:"source_security_group_id"`
|
|
|
|
FromPort *int64 `cty:"from_port"`
|
|
ToPort *int64 `cty:"to_port"`
|
|
|
|
Protocol *string `cty:"protocol"`
|
|
CIDRBlocks []string `cty:"cidr_blocks"`
|
|
IPv6CIDRBlocks []string `cty:"ipv6_cidr_blocks"`
|
|
PrefixListIDs []string `cty:"prefix_list_ids"`
|
|
}
|
|
|
|
func (_ *SecurityGroupRule) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *SecurityGroupRule) error {
|
|
tf := &terraformSecurityGroupIngress{
|
|
Type: fi.PtrTo("ingress"),
|
|
SecurityGroup: e.SecurityGroup.TerraformLink(),
|
|
FromPort: e.FromPort,
|
|
ToPort: e.ToPort,
|
|
Protocol: e.Protocol,
|
|
}
|
|
if fi.ValueOf(e.Egress) {
|
|
tf.Type = fi.PtrTo("egress")
|
|
}
|
|
|
|
if e.Protocol == nil {
|
|
tf.Protocol = fi.PtrTo("-1")
|
|
tf.FromPort = fi.PtrTo(int64(0))
|
|
tf.ToPort = fi.PtrTo(int64(0))
|
|
}
|
|
|
|
if tf.FromPort == nil {
|
|
// FromPort is required by tf
|
|
tf.FromPort = fi.PtrTo(int64(0))
|
|
}
|
|
if tf.ToPort == nil {
|
|
// ToPort is required by tf
|
|
tf.ToPort = fi.PtrTo(int64(65535))
|
|
}
|
|
|
|
if e.SourceGroup != nil {
|
|
tf.SourceGroup = e.SourceGroup.TerraformLink()
|
|
}
|
|
|
|
if e.CIDR != nil {
|
|
tf.CIDRBlocks = append(tf.CIDRBlocks, *e.CIDR)
|
|
}
|
|
if e.IPv6CIDR != nil {
|
|
tf.IPv6CIDRBlocks = append(tf.IPv6CIDRBlocks, *e.IPv6CIDR)
|
|
}
|
|
if e.PrefixList != nil {
|
|
tf.PrefixListIDs = append(tf.PrefixListIDs, *e.PrefixList)
|
|
}
|
|
|
|
return t.RenderResource("aws_security_group_rule", *e.Name, tf)
|
|
}
|