Skip to content

Commit

Permalink
Merge pull request #2062 from wotolom/feat-securitygroup-observe-rules
Browse files Browse the repository at this point in the history
feat(ec2): Add SecurityGroupRuleIDs to SG status
  • Loading branch information
MisterMX authored May 22, 2024
2 parents 031d774 + d0f56f6 commit 6966f8d
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 13 deletions.
25 changes: 25 additions & 0 deletions apis/ec2/v1beta1/securitygroup_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,31 @@ type SecurityGroupObservation struct {

// SecurityGroupID is the ID of the SecurityGroup.
SecurityGroupID string `json:"securityGroupID"`

// IngressRules of the observed SecurityGroup.
IngressRules []SecurityGroupRuleObservation `json:"ingressRules,omitempty"`

// EgressRules of the observed SecurityGroup.
EgressRules []SecurityGroupRuleObservation `json:"egressRules,omitempty"`
}

type SecurityGroupRuleObservation struct {
// ID of the security group rule.
ID *string `json:"id,omitempty"`

// CidrIpv4 range.
CidrIpv4 *string `json:"cidrIpv4,omitempty"`

// CidrIpv6 range.
CidrIpv6 *string `json:"cidrIpv6,omitempty"`

// The IP protocol name (tcp, udp, icmp, icmpv6) or number (see Protocol Numbers
// (http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml)). Use
// -1 to specify all protocols.
IpProtocol *string `json:"ipProtocol,omitempty"`

// Description of this rule.
Description *string `json:"description,omitempty"`
}

// A SecurityGroupStatus represents the observed state of a SecurityGroup.
Expand Down
56 changes: 55 additions & 1 deletion apis/ec2/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions package/crds/ec2.aws.crossplane.io_securitygroups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,52 @@ spec:
description: SecurityGroupObservation keeps the state for the external
resource
properties:
egressRules:
description: EgressRules of the observed SecurityGroup.
items:
properties:
cidrIpv4:
description: CidrIpv4 range.
type: string
cidrIpv6:
description: CidrIpv6 range.
type: string
description:
description: Description of this rule.
type: string
id:
description: ID of the security group rule.
type: string
ipProtocol:
description: The IP protocol name (tcp, udp, icmp, icmpv6)
or number (see Protocol Numbers (http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml)).
Use -1 to specify all protocols.
type: string
type: object
type: array
ingressRules:
description: IngressRules of the observed SecurityGroup.
items:
properties:
cidrIpv4:
description: CidrIpv4 range.
type: string
cidrIpv6:
description: CidrIpv6 range.
type: string
description:
description: Description of this rule.
type: string
id:
description: ID of the security group rule.
type: string
ipProtocol:
description: The IP protocol name (tcp, udp, icmp, icmpv6)
or number (see Protocol Numbers (http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml)).
Use -1 to specify all protocols.
type: string
type: object
type: array
ownerId:
description: The AWS account ID of the owner of the security group.
type: string
Expand Down
6 changes: 6 additions & 0 deletions pkg/clients/ec2/fake/securitygroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type MockSecurityGroupClient struct {
MockCreate func(ctx context.Context, input *ec2.CreateSecurityGroupInput, opts []func(*ec2.Options)) (*ec2.CreateSecurityGroupOutput, error)
MockDelete func(ctx context.Context, input *ec2.DeleteSecurityGroupInput, opts []func(*ec2.Options)) (*ec2.DeleteSecurityGroupOutput, error)
MockDescribe func(ctx context.Context, input *ec2.DescribeSecurityGroupsInput, opts []func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error)
MockDescribeRules func(ctx context.Context, input *ec2.DescribeSecurityGroupRulesInput, opts []func(*ec2.Options)) (*ec2.DescribeSecurityGroupRulesOutput, error)
MockAuthorizeIngress func(ctx context.Context, input *ec2.AuthorizeSecurityGroupIngressInput, opts []func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupIngressOutput, error)
MockAuthorizeEgress func(ctx context.Context, input *ec2.AuthorizeSecurityGroupEgressInput, opts []func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupEgressOutput, error)
MockRevokeIngress func(ctx context.Context, input *ec2.RevokeSecurityGroupIngressInput, opts []func(*ec2.Options)) (*ec2.RevokeSecurityGroupIngressOutput, error)
Expand All @@ -55,6 +56,11 @@ func (m *MockSecurityGroupClient) DescribeSecurityGroups(ctx context.Context, in
return m.MockDescribe(ctx, input, opts)
}

// DescribeSecurityGroups mocks DescribeSecurityGroups method
func (m *MockSecurityGroupClient) DescribeSecurityGroupRules(ctx context.Context, input *ec2.DescribeSecurityGroupRulesInput, opts ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupRulesOutput, error) {
return m.MockDescribeRules(ctx, input, opts)
}

// AuthorizeSecurityGroupIngress mocks AuthorizeSecurityGroupIngress method
func (m *MockSecurityGroupClient) AuthorizeSecurityGroupIngress(ctx context.Context, input *ec2.AuthorizeSecurityGroupIngressInput, opts ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupIngressOutput, error) {
return m.MockAuthorizeIngress(ctx, input, opts)
Expand Down
23 changes: 22 additions & 1 deletion pkg/clients/ec2/securitygroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/smithy-go"
"k8s.io/utils/ptr"

"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
"github.com/crossplane-contrib/provider-aws/pkg/utils/pointer"
Expand All @@ -26,6 +27,7 @@ type SecurityGroupClient interface {
CreateSecurityGroup(ctx context.Context, input *ec2.CreateSecurityGroupInput, opts ...func(*ec2.Options)) (*ec2.CreateSecurityGroupOutput, error)
DeleteSecurityGroup(ctx context.Context, input *ec2.DeleteSecurityGroupInput, opts ...func(*ec2.Options)) (*ec2.DeleteSecurityGroupOutput, error)
DescribeSecurityGroups(ctx context.Context, input *ec2.DescribeSecurityGroupsInput, opts ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error)
DescribeSecurityGroupRules(ctx context.Context, input *ec2.DescribeSecurityGroupRulesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupRulesOutput, error)
AuthorizeSecurityGroupIngress(ctx context.Context, input *ec2.AuthorizeSecurityGroupIngressInput, opts ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupIngressOutput, error)
AuthorizeSecurityGroupEgress(ctx context.Context, input *ec2.AuthorizeSecurityGroupEgressInput, opts ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupEgressOutput, error)
RevokeSecurityGroupIngress(ctx context.Context, input *ec2.RevokeSecurityGroupIngressInput, opts ...func(*ec2.Options)) (*ec2.RevokeSecurityGroupIngressOutput, error)
Expand Down Expand Up @@ -98,10 +100,29 @@ func GenerateEC2Permissions(objectPerms []v1beta1.IPPermission) []ec2types.IpPer

// GenerateSGObservation is used to produce v1beta1.SecurityGroupExternalStatus from
// ec2types.SecurityGroup.
func GenerateSGObservation(sg ec2types.SecurityGroup) v1beta1.SecurityGroupObservation {
func GenerateSGObservation(sg ec2types.SecurityGroup, rules []ec2types.SecurityGroupRule) v1beta1.SecurityGroupObservation {
ingressRules := []v1beta1.SecurityGroupRuleObservation{}
egressRules := []v1beta1.SecurityGroupRuleObservation{}

for _, r := range rules {
observedRule := v1beta1.SecurityGroupRuleObservation{
ID: r.SecurityGroupRuleId,
CidrIpv4: r.CidrIpv4,
CidrIpv6: r.CidrIpv6,
IpProtocol: r.IpProtocol,
}
if ptr.Deref(r.IsEgress, false) {
egressRules = append(egressRules, observedRule)
} else {
ingressRules = append(ingressRules, observedRule)
}
}

return v1beta1.SecurityGroupObservation{
OwnerID: pointer.StringValue(sg.OwnerId),
SecurityGroupID: pointer.StringValue(sg.GroupId),
IngressRules: ingressRules,
EgressRules: egressRules,
}
}

Expand Down
56 changes: 48 additions & 8 deletions pkg/clients/ec2/securitygroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/google/go-cmp/cmp"
"k8s.io/utils/ptr"

"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
)
Expand Down Expand Up @@ -126,33 +127,72 @@ func TestIsSGUpToDate(t *testing.T) {
}

func TestGenerateSGObservation(t *testing.T) {
type args struct {
sg ec2types.SecurityGroup
rules []ec2types.SecurityGroupRule
}

cases := map[string]struct {
in ec2types.SecurityGroup
in args
out v1beta1.SecurityGroupObservation
}{
"AllFilled": {
in: ec2types.SecurityGroup{
OwnerId: aws.String(sgOwner),
GroupId: aws.String(sgID),
in: args{
sg: ec2types.SecurityGroup{
OwnerId: aws.String(sgOwner),
GroupId: aws.String(sgID),
},
rules: []ec2types.SecurityGroupRule{
{
CidrIpv4: ptr.To("10.0.0.16/32"),
Description: ptr.To("egress rule"),
GroupId: ptr.To("abcd"),
IpProtocol: ptr.To("tcp"),
IsEgress: ptr.To(true),
},
{
CidrIpv4: ptr.To("10.0.100.16/16"),
Description: ptr.To("ingress rule"),
GroupId: ptr.To("efgh"),
IpProtocol: ptr.To("tcp"),
IsEgress: ptr.To(false),
},
},
},
out: v1beta1.SecurityGroupObservation{
OwnerID: sgOwner,
SecurityGroupID: sgID,
EgressRules: []v1beta1.SecurityGroupRuleObservation{
{
CidrIpv4: ptr.To("10.0.0.16/32"),
IpProtocol: ptr.To("tcp"),
},
},
IngressRules: []v1beta1.SecurityGroupRuleObservation{
{
CidrIpv4: ptr.To("10.0.100.16/16"),
IpProtocol: ptr.To("tcp"),
},
},
},
},
"NoIpCount": {
in: ec2types.SecurityGroup{
OwnerId: aws.String(sgOwner),
in: args{
sg: ec2types.SecurityGroup{
OwnerId: aws.String(sgOwner),
},
},
out: v1beta1.SecurityGroupObservation{
OwnerID: sgOwner,
OwnerID: sgOwner,
IngressRules: []v1beta1.SecurityGroupRuleObservation{},
EgressRules: []v1beta1.SecurityGroupRuleObservation{},
},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
r := GenerateSGObservation(tc.in)
r := GenerateSGObservation(tc.in.sg, tc.in.rules)
if diff := cmp.Diff(r, tc.out); diff != "" {
t.Errorf("GenerateNetworkObservation(...): -want, +got:\n%s", diff)
}
Expand Down
16 changes: 15 additions & 1 deletion pkg/controller/ec2/securitygroup/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
errUnexpectedObject = "The managed resource is not an SecurityGroup resource"

errDescribe = "failed to describe SecurityGroup"
errDescribeRules = "failed to describe SecurityGroupRules"
errGetSecurityGroup = "failed to get SecurityGroup based on groupName"
errMultipleItems = "retrieved multiple SecurityGroups for the given securityGroupId"
errCreate = "failed to create the SecurityGroup resource"
Expand Down Expand Up @@ -153,12 +154,25 @@ func (e *external) Observe(ctx context.Context, mgd resource.Managed) (managed.E
return managed.ExternalObservation{}, errors.New(errMultipleItems)
}

securityGroupRulesResponse, err := e.sg.DescribeSecurityGroupRules(ctx, &awsec2.DescribeSecurityGroupRulesInput{
Filters: []awsec2types.Filter{
{
Name: aws.String("group-id"),
Values: []string{meta.GetExternalName(cr)},
},
},
})
if err != nil {
return managed.ExternalObservation{}, errorutils.Wrap(resource.Ignore(ec2.IsSecurityGroupNotFoundErr, err), errDescribeRules)
}

observedRules := securityGroupRulesResponse.SecurityGroupRules
observed := response.SecurityGroups[0]

current := cr.Spec.ForProvider.DeepCopy()
ec2.LateInitializeSG(&cr.Spec.ForProvider, &observed)

cr.Status.AtProvider = ec2.GenerateSGObservation(observed)
cr.Status.AtProvider = ec2.GenerateSGObservation(observed, observedRules)

upToDate := ec2.IsSGUpToDate(cr.Spec.ForProvider, observed)
// this is to make sure that the security group exists with the specified traffic rules.
Expand Down
Loading

0 comments on commit 6966f8d

Please sign in to comment.