Use sg rule ids and tags where possible

This commit is contained in:
Ole Markus With 2021-09-11 21:11:14 +02:00
parent fe28b8587c
commit d98994686a
6 changed files with 258 additions and 167 deletions

View File

@ -42,6 +42,7 @@ type MockEC2 struct {
securityGroupNumber int
SecurityGroups map[string]*ec2.SecurityGroup
SecurityGroupRules map[string]*ec2.SecurityGroupRule
subnets map[string]*subnetInfo

View File

@ -213,7 +213,7 @@ func (m *MockEC2) RevokeSecurityGroupIngress(request *ec2.RevokeSecurityGroupIng
return nil, fmt.Errorf("SecurityGroup not found")
}
klog.Warningf("RevokeSecurityGroupIngress not implemented - does not actually revoke permissions")
klog.Warningf("RevokeSecurityGroupIngress mock not implemented - does not actually revoke permissions")
response := &ec2.RevokeSecurityGroupIngressOutput{}
return response, nil
@ -269,6 +269,61 @@ func (m *MockEC2) AuthorizeSecurityGroupEgress(request *ec2.AuthorizeSecurityGro
// TODO: We need to fold permissions
if m.SecurityGroupRules == nil {
m.SecurityGroupRules = make(map[string]*ec2.SecurityGroupRule)
}
for _, permission := range request.IpPermissions {
for _, iprange := range permission.IpRanges {
n := len(m.SecurityGroupRules) + 1
id := fmt.Sprintf("sgr-%d", n)
rule := &ec2.SecurityGroupRule{
SecurityGroupRuleId: &id,
GroupId: sg.GroupId,
FromPort: permission.FromPort,
ToPort: permission.ToPort,
IsEgress: aws.Bool(true),
CidrIpv4: iprange.CidrIp,
IpProtocol: permission.IpProtocol,
Tags: tagSpecificationsToTags(request.TagSpecifications, ec2.ResourceTypeSecurityGroupRule),
}
if permission.FromPort == nil {
rule.FromPort = aws.Int64(int64(-1))
}
if permission.ToPort == nil {
rule.ToPort = aws.Int64(int64(-1))
}
m.SecurityGroupRules[id] = rule
}
for _, iprange := range permission.Ipv6Ranges {
n := len(m.SecurityGroupRules) + 1
id := fmt.Sprintf("sgr-%d", n)
rule := &ec2.SecurityGroupRule{
SecurityGroupRuleId: &id,
GroupId: sg.GroupId,
FromPort: permission.FromPort,
ToPort: permission.ToPort,
IsEgress: aws.Bool(true),
CidrIpv6: iprange.CidrIpv6,
IpProtocol: permission.IpProtocol,
Tags: tagSpecificationsToTags(request.TagSpecifications, ec2.ResourceTypeSecurityGroupRule),
}
if permission.FromPort == nil {
rule.FromPort = aws.Int64(int64(-1))
}
if permission.ToPort == nil {
rule.ToPort = aws.Int64(int64(-1))
}
m.SecurityGroupRules[id] = rule
}
}
response := &ec2.AuthorizeSecurityGroupEgressOutput{}
return response, nil
}
@ -325,6 +380,111 @@ func (m *MockEC2) AuthorizeSecurityGroupIngress(request *ec2.AuthorizeSecurityGr
// TODO: We need to fold permissions
if m.SecurityGroupRules == nil {
m.SecurityGroupRules = make(map[string]*ec2.SecurityGroupRule)
}
for _, permission := range request.IpPermissions {
for _, iprange := range permission.IpRanges {
n := len(m.SecurityGroupRules) + 1
id := fmt.Sprintf("sgr-%d", n)
rule := &ec2.SecurityGroupRule{
SecurityGroupRuleId: &id,
GroupId: sg.GroupId,
FromPort: permission.FromPort,
ToPort: permission.ToPort,
IsEgress: aws.Bool(false),
CidrIpv4: iprange.CidrIp,
IpProtocol: permission.IpProtocol,
Tags: tagSpecificationsToTags(request.TagSpecifications, ec2.ResourceTypeSecurityGroupRule),
}
if permission.FromPort == nil {
rule.FromPort = aws.Int64(int64(-1))
}
if permission.ToPort == nil {
rule.ToPort = aws.Int64(int64(-1))
}
m.SecurityGroupRules[id] = rule
}
for _, iprange := range permission.Ipv6Ranges {
n := len(m.SecurityGroupRules) + 1
id := fmt.Sprintf("sgr-%d", n)
rule := &ec2.SecurityGroupRule{
SecurityGroupRuleId: &id,
GroupId: sg.GroupId,
FromPort: permission.FromPort,
ToPort: permission.ToPort,
IsEgress: aws.Bool(false),
CidrIpv6: iprange.CidrIpv6,
IpProtocol: permission.IpProtocol,
Tags: tagSpecificationsToTags(request.TagSpecifications, ec2.ResourceTypeSecurityGroupRule),
}
if permission.FromPort == nil {
rule.FromPort = aws.Int64(int64(-1))
}
if permission.ToPort == nil {
rule.ToPort = aws.Int64(int64(-1))
}
m.SecurityGroupRules[id] = rule
}
for _, group := range permission.UserIdGroupPairs {
n := len(m.SecurityGroupRules) + 1
id := fmt.Sprintf("sgr-%d", n)
rule := &ec2.SecurityGroupRule{
SecurityGroupRuleId: &id,
GroupId: sg.GroupId,
FromPort: permission.FromPort,
ToPort: permission.ToPort,
IsEgress: aws.Bool(false),
ReferencedGroupInfo: &ec2.ReferencedSecurityGroup{
GroupId: group.GroupId,
},
IpProtocol: permission.IpProtocol,
Tags: tagSpecificationsToTags(request.TagSpecifications, ec2.ResourceTypeSecurityGroupRule),
}
if permission.FromPort == nil {
rule.FromPort = aws.Int64(int64(-1))
}
if permission.ToPort == nil {
rule.ToPort = aws.Int64(int64(-1))
}
m.SecurityGroupRules[id] = rule
}
}
response := &ec2.AuthorizeSecurityGroupIngressOutput{}
return response, nil
}
func (m *MockEC2) DescribeSecurityGroupRules(request *ec2.DescribeSecurityGroupRulesInput) (*ec2.DescribeSecurityGroupRulesOutput, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
rules := []*ec2.SecurityGroupRule{}
sgid := ""
for _, filter := range request.Filters {
if aws.StringValue(filter.Name) == "group-id" {
sgid = aws.StringValue(filter.Values[0])
}
}
for _, rule := range m.SecurityGroupRules {
if aws.StringValue(rule.GroupId) == sgid {
rules = append(rules, rule)
}
}
return &ec2.DescribeSecurityGroupRulesOutput{
SecurityGroupRules: rules,
}, nil
}

View File

@ -429,6 +429,12 @@ func AddDirectionalGroupRule(c *fi.ModelBuilderContext, t *awstasks.SecurityGrou
name := generateName(t)
t.Name = fi.String(name)
tags := make(map[string]string)
for key, value := range t.SecurityGroup.Tags {
tags[key] = value
}
tags["Name"] = *t.Name
t.Tags = tags
klog.V(8).Infof("Adding rule %v", name)
c.AddTask(t)

View File

@ -261,26 +261,24 @@ func (e *SecurityGroup) CloudformationLink() *cloudformation.Literal {
}
type deleteSecurityGroupRule struct {
groupID *string
permission *ec2.IpPermission
egress bool
rule *ec2.SecurityGroupRule
}
var _ fi.Deletion = &deleteSecurityGroupRule{}
func (d *deleteSecurityGroupRule) Delete(t fi.Target) error {
klog.V(2).Infof("deleting security group permission: %v", fi.DebugAsJsonString(d.permission))
klog.V(2).Infof("deleting security group permission: %v", fi.DebugAsJsonString(d.rule))
awsTarget, ok := t.(*awsup.AWSAPITarget)
if !ok {
return fmt.Errorf("unexpected target type for deletion: %T", t)
}
if d.egress {
if fi.BoolValue(d.rule.IsEgress) {
request := &ec2.RevokeSecurityGroupEgressInput{
GroupId: d.groupID,
GroupId: d.rule.GroupId,
SecurityGroupRuleIds: []*string{d.rule.SecurityGroupRuleId},
}
request.IpPermissions = []*ec2.IpPermission{d.permission}
klog.V(2).Infof("Calling EC2 RevokeSecurityGroupEgress")
_, err := awsTarget.Cloud.EC2().RevokeSecurityGroupEgress(request)
@ -289,9 +287,9 @@ func (d *deleteSecurityGroupRule) Delete(t fi.Target) error {
}
} else {
request := &ec2.RevokeSecurityGroupIngressInput{
GroupId: d.groupID,
GroupId: d.rule.GroupId,
SecurityGroupRuleIds: []*string{d.rule.SecurityGroupRuleId},
}
request.IpPermissions = []*ec2.IpPermission{d.permission}
klog.V(2).Infof("Calling EC2 RevokeSecurityGroupIngress")
_, err := awsTarget.Cloud.EC2().RevokeSecurityGroupIngress(request)
@ -308,8 +306,8 @@ func (d *deleteSecurityGroupRule) TaskName() string {
}
func (d *deleteSecurityGroupRule) Item() string {
s := fi.StringValue(d.groupID) + ":"
p := d.permission
s := fi.StringValue(d.rule.GroupId) + ":"
p := d.rule
if aws.Int64Value(p.FromPort) != 0 {
s += fmt.Sprintf(" port=%d", aws.Int64Value(p.FromPort))
if aws.Int64Value(p.ToPort) != aws.Int64Value(p.FromPort) {
@ -319,59 +317,17 @@ func (d *deleteSecurityGroupRule) Item() string {
if aws.StringValue(p.IpProtocol) != "-1" {
s += fmt.Sprintf(" protocol=%s", aws.StringValue(p.IpProtocol))
}
for _, ug := range p.UserIdGroupPairs {
s += fmt.Sprintf(" group=%s", aws.StringValue(ug.GroupId))
}
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))
if p.ReferencedGroupInfo != nil {
s += fmt.Sprintf(" group=%s", aws.StringValue(p.ReferencedGroupInfo.GroupId))
}
s += fmt.Sprintf(" ip=%s", aws.StringValue(p.CidrIpv4))
s += fmt.Sprintf(" ipv6=%s", aws.StringValue(p.CidrIpv6))
//permissionString := fi.DebugAsJsonString(d.permission)
//s += permissionString
return s
}
func expandPermissions(sgID *string, permission *ec2.IpPermission, egress bool) []*ec2.IpPermission {
var rules []*ec2.IpPermission
master := &ec2.IpPermission{
FromPort: permission.FromPort,
ToPort: permission.ToPort,
IpProtocol: permission.IpProtocol,
}
for _, ipRange := range permission.IpRanges {
a := &ec2.IpPermission{}
*a = *master
a.IpRanges = []*ec2.IpRange{ipRange}
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
a.UserIdGroupPairs = []*ec2.UserIdGroupPair{ug}
rules = append(rules, a)
}
if len(rules) == 0 {
// If there are no group or cidr restrictions, it is just a generic rule
rules = append(rules, master)
}
return rules
}
func (e *SecurityGroup) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
var removals []fi.Deletion
@ -396,13 +352,20 @@ func (e *SecurityGroup) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
return nil, nil
}
var ingress []*ec2.IpPermission
for _, permission := range sg.IpPermissions {
rules := expandPermissions(sg.GroupId, permission, false)
ingress = append(ingress, rules...)
cloud := c.Cloud.(awsup.AWSCloud)
request := &ec2.DescribeSecurityGroupRulesInput{
Filters: []*ec2.Filter{
awsup.NewEC2Filter("group-id", *e.ID),
},
}
for _, permission := range ingress {
response, err := cloud.EC2().DescribeSecurityGroupRules(request)
if err != nil {
return nil, err
}
for _, permission := range response.SecurityGroupRules {
// Because of #478, we can't remove all non-matching security groups
// Instead we consider only certain rules to be 'in-scope'
// (in the model, we typically consider only rules on port 22 and 443)
@ -436,34 +399,7 @@ func (e *SecurityGroup) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
}
if !found {
removals = append(removals, &deleteSecurityGroupRule{
groupID: sg.GroupId,
permission: permission,
egress: false,
})
}
}
var egress []*ec2.IpPermission
for _, permission := range sg.IpPermissionsEgress {
rules := expandPermissions(sg.GroupId, permission, true)
egress = append(egress, rules...)
}
for _, permission := range egress {
found := false
for _, t := range c.AllTasks() {
er, ok := t.(*SecurityGroupRule)
if !ok {
continue
}
if er.matches(permission) {
found = true
}
}
if !found {
removals = append(removals, &deleteSecurityGroupRule{
groupID: sg.GroupId,
permission: permission,
egress: true,
rule: permission,
})
}
}
@ -473,7 +409,7 @@ func (e *SecurityGroup) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
// RemovalRule is a rule that filters the permissions we should remove
type RemovalRule interface {
Matches(permission *ec2.IpPermission) bool
Matches(permission *ec2.SecurityGroupRule) bool
}
// ParseRemovalRule parses our removal rule DSL into a RemovalRule
@ -511,7 +447,7 @@ func (r *PortRemovalRule) String() string {
return fi.DebugAsJsonString(r)
}
func (r *PortRemovalRule) Matches(permission *ec2.IpPermission) bool {
func (r *PortRemovalRule) Matches(permission *ec2.SecurityGroupRule) bool {
// Check if port matches
if permission.FromPort == nil || *permission.FromPort != int64(r.Port) {
return false

View File

@ -60,34 +60,34 @@ func testParsesAsPort(t *testing.T, rule string, port int) {
func TestPortRemovalRule(t *testing.T) {
r := &PortRemovalRule{Port: 22}
testMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(22), ToPort: aws.Int64(22)})
testMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(22), ToPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(0), ToPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(23), ToPort: aws.Int64(23)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(20), ToPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(22), ToPort: aws.Int64(23)})
testNotMatches(t, r, &ec2.IpPermission{ToPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.IpPermission{})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(0), ToPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(23), ToPort: aws.Int64(23)})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(20), ToPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(22), ToPort: aws.Int64(23)})
testNotMatches(t, r, &ec2.SecurityGroupRule{ToPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(22)})
testNotMatches(t, r, &ec2.SecurityGroupRule{})
}
func TestPortRemovalRule_Zero(t *testing.T) {
r := &PortRemovalRule{Port: 0}
testMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(0), ToPort: aws.Int64(0)})
testMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(0), ToPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(0), ToPort: aws.Int64(20)})
testNotMatches(t, r, &ec2.IpPermission{ToPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.IpPermission{FromPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.IpPermission{})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(0), ToPort: aws.Int64(20)})
testNotMatches(t, r, &ec2.SecurityGroupRule{ToPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.SecurityGroupRule{FromPort: aws.Int64(0)})
testNotMatches(t, r, &ec2.SecurityGroupRule{})
}
func testMatches(t *testing.T, rule *PortRemovalRule, permission *ec2.IpPermission) {
func testMatches(t *testing.T, rule *PortRemovalRule, permission *ec2.SecurityGroupRule) {
if !rule.Matches(permission) {
t.Fatalf("rule %q failed to match permission %q", rule, permission)
}
}
func testNotMatches(t *testing.T, rule *PortRemovalRule, permission *ec2.IpPermission) {
func testNotMatches(t *testing.T, rule *PortRemovalRule, permission *ec2.SecurityGroupRule) {
if rule.Matches(permission) {
t.Fatalf("rule %q unexpectedly matched permission %q", rule, permission)
}

View File

@ -34,6 +34,7 @@ import (
// +kops:fitask
type SecurityGroupRule struct {
ID *string
Name *string
Lifecycle fi.Lifecycle
@ -49,6 +50,8 @@ type SecurityGroupRule struct {
SourceGroup *SecurityGroup
Egress *bool
Tags map[string]string
}
func (e *SecurityGroupRule) Find(c *fi.Context) (*SecurityGroupRule, error) {
@ -63,35 +66,24 @@ func (e *SecurityGroupRule) Find(c *fi.Context) (*SecurityGroupRule, error) {
return nil, nil
}
request := &ec2.DescribeSecurityGroupsInput{
request := &ec2.DescribeSecurityGroupRulesInput{
Filters: []*ec2.Filter{
awsup.NewEC2Filter("group-id", *e.SecurityGroup.ID),
},
}
response, err := cloud.EC2().DescribeSecurityGroups(request)
response, err := cloud.EC2().DescribeSecurityGroupRules(request)
if err != nil {
return nil, fmt.Errorf("error listing SecurityGroup: %v", err)
}
if response == nil || len(response.SecurityGroups) == 0 {
if response == nil || len(response.SecurityGroupRules) == 0 {
return nil, nil
}
if len(response.SecurityGroups) != 1 {
klog.Fatalf("found multiple security groups for id=%s", *e.SecurityGroup.ID)
}
sg := response.SecurityGroups[0]
//klog.V(2).Info("found existing security group")
var foundRule *ec2.SecurityGroupRule
var foundRule *ec2.IpPermission
ipPermissions := sg.IpPermissions
if fi.BoolValue(e.Egress) {
ipPermissions = sg.IpPermissionsEgress
}
for _, rule := range ipPermissions {
for _, rule := range response.SecurityGroupRules {
if e.matches(rule) {
foundRule = rule
break
@ -100,17 +92,30 @@ func (e *SecurityGroupRule) Find(c *fi.Context) (*SecurityGroupRule, error) {
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.StringValue(actual.Protocol) != "icmpv6" {
if fi.Int64Value(actual.FromPort) == int64(-1) {
actual.FromPort = nil
}
if fi.Int64Value(actual.ToPort) == int64(-1) {
actual.ToPort = nil
}
}
if e.CIDR != nil {
actual.CIDR = e.CIDR
}
@ -124,17 +129,27 @@ func (e *SecurityGroupRule) Find(c *fi.Context) (*SecurityGroupRule, error) {
// Avoid spurious changes
actual.Lifecycle = e.Lifecycle
e.ID = actual.ID
return actual, nil
}
return nil, nil
}
func (e *SecurityGroupRule) matches(rule *ec2.IpPermission) bool {
if aws.Int64Value(rule.FromPort) != aws.Int64Value(e.FromPort) {
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
}
if aws.Int64Value(rule.ToPort) != aws.Int64Value(e.ToPort) {
matchToPort := int64(-1)
if e.ToPort != nil {
matchToPort = *e.ToPort
}
if aws.Int64Value(rule.ToPort) != matchToPort {
return false
}
@ -146,50 +161,19 @@ func (e *SecurityGroupRule) matches(rule *ec2.IpPermission) bool {
return false
}
if e.CIDR != nil {
match := false
for _, ipRange := range rule.IpRanges {
if aws.StringValue(ipRange.CidrIp) == *e.CIDR {
match = true
break
}
}
if !match {
return false
}
if fi.StringValue(e.CIDR) != fi.StringValue(rule.CidrIpv4) {
return false
}
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 fi.StringValue(e.IPv6CIDR) != fi.StringValue(rule.CidrIpv6) {
return false
}
if e.SourceGroup != nil {
match := false
for _, spec := range rule.UserIdGroupPairs {
if e.SourceGroup == nil {
continue
}
if e.SourceGroup.ID == nil {
klog.Warningf("SourceGroup had nil ID: %v", e.SourceGroup)
continue
}
if aws.StringValue(spec.GroupId) == *e.SourceGroup.ID {
match = true
break
}
if e.SourceGroup != nil || rule.ReferencedGroupInfo != nil {
if e.SourceGroup == nil || rule.ReferencedGroupInfo == nil {
return false
}
if !match {
if fi.StringValue(e.SourceGroup.ID) != fi.StringValue(rule.ReferencedGroupInfo.GroupId) {
return false
}
}
@ -295,6 +279,7 @@ func (_ *SecurityGroupRule) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Secu
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)
@ -306,6 +291,7 @@ func (_ *SecurityGroupRule) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Secu
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)
@ -314,6 +300,8 @@ func (_ *SecurityGroupRule) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Secu
}
}
} 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)