Skip to content

Commit

Permalink
Merge pull request #145 from anispate/OSD_12357
Browse files Browse the repository at this point in the history
Adding Security group
  • Loading branch information
openshift-merge-robot authored Oct 21, 2022
2 parents c26bdba + 265a4fc commit 94a9287
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 4 deletions.
7 changes: 5 additions & 2 deletions cmd/egress/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ are set correctly before execution.
validateEgressCmd.Flags().StringVar(&config.vpcSubnetID, "subnet-id", "", "source subnet ID")
validateEgressCmd.Flags().StringVar(&config.cloudImageID, "image-id", "", "(optional) cloud image for the compute instance")
validateEgressCmd.Flags().StringVar(&config.instanceType, "instance-type", "", "(optional) compute instance type")
validateEgressCmd.Flags().StringVar(&config.securityGroupId, "security-group-id", "", "(optional) security group id to attach to the created EC2 instance")
validateEgressCmd.Flags().StringVar(&config.securityGroupId, "security-group-id", "", "security group id to attach to the created EC2 instance")
validateEgressCmd.Flags().StringVar(&config.region, "region", "", fmt.Sprintf("(optional) compute instance region. If absent, environment var %[1]v = %[2]v and %[3]v = %[4]v will be used", awsRegionEnvVarStr, awsRegionDefault, gcpRegionEnvVarStr, gcpRegionDefault))
validateEgressCmd.Flags().StringToStringVar(&config.cloudTags, "cloud-tags", map[string]string{}, "(optional) comma-seperated list of tags to assign to cloud resources e.g. --cloud-tags key1=value1,key2=value2")
validateEgressCmd.Flags().BoolVar(&config.debug, "debug", false, "(optional) if true, enable additional debug-level logging")
Expand All @@ -214,7 +214,10 @@ are set correctly before execution.

if err := validateEgressCmd.MarkFlagRequired("subnet-id"); err != nil {
validateEgressCmd.PrintErr(err)
os.Exit(1)
}

if err := validateEgressCmd.MarkFlagRequired("security-group-id"); err != nil {
validateEgressCmd.PrintErr(err)
}

return validateEgressCmd
Expand Down
31 changes: 30 additions & 1 deletion docs/aws/aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,42 @@ Ensure that the AWS credentials being used have the following permissions. (This
"ec2:DescribeInstanceTypes",
"ec2:GetConsoleOutput",
"ec2:TerminateInstances",
"ec2:DescribeVpcAttribute"
"ec2:DescribeVpcAttribute",
"ec2:CreateSecurityGroup",
"ec2:DeleteSecurityGroup",
"ec2:DescribeSecurityGroup",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:RevokeSecurityGroupEgress",
"ec2:DescribeSubnets"
],
"Resource": "*"
}
]
}
```

The SRE only needs below permissions because we should supply Security Group ID by running `./osd-network-verifier egress --security-group-id <SG_ID>`:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateTags",
"ec2:RunInstances",
"ec2:DescribeInstances",
"ec2:DescribeInstanceTypes",
"ec2:GetConsoleOutput",
"ec2:TerminateInstances",
"ec2:DescribeVpcAttribute",
],
"Resource": "*"
}
]
}

```

## Available Tools ##

Expand Down
39 changes: 39 additions & 0 deletions pkg/clients/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package aws

import (
"context"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
Expand Down Expand Up @@ -59,6 +61,12 @@ type EC2Client interface {
GetConsoleOutput(ctx context.Context, input *ec2.GetConsoleOutputInput, optFns ...func(*ec2.Options)) (*ec2.GetConsoleOutputOutput, error)
TerminateInstances(ctx context.Context, input *ec2.TerminateInstancesInput, optFns ...func(*ec2.Options)) (*ec2.TerminateInstancesOutput, error)
DescribeVpcAttribute(ctx context.Context, input *ec2.DescribeVpcAttributeInput, optFns ...func(*ec2.Options)) (*ec2.DescribeVpcAttributeOutput, error)
CreateSecurityGroup(ctx context.Context, params *ec2.CreateSecurityGroupInput, optFns ...func(*ec2.Options)) (*ec2.CreateSecurityGroupOutput, error)
DeleteSecurityGroup(ctx context.Context, params *ec2.DeleteSecurityGroupInput, optFns ...func(*ec2.Options)) (*ec2.DeleteSecurityGroupOutput, error)
DescribeSecurityGroups(ctx context.Context, params *ec2.DescribeSecurityGroupsInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error)
AuthorizeSecurityGroupEgress(ctx context.Context, params *ec2.AuthorizeSecurityGroupEgressInput, optFns ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupEgressOutput, error)
RevokeSecurityGroupEgress(ctx context.Context, params *ec2.RevokeSecurityGroupEgressInput, optFns ...func(*ec2.Options)) (*ec2.RevokeSecurityGroupEgressOutput, error)
DescribeSubnets(ctx context.Context, params *ec2.DescribeSubnetsInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error)
}

func (c *Client) CreateTags(ctx context.Context, params *ec2.CreateTagsInput, optFns ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error) {
Expand All @@ -76,6 +84,7 @@ func (c *Client) DescribeInstances(ctx context.Context, params *ec2.DescribeInst
func (c *Client) RunInstances(ctx context.Context, params *ec2.RunInstancesInput, optFns ...func(*ec2.Options)) (*ec2.RunInstancesOutput, error) {
return c.ec2Client.RunInstances(ctx, params, optFns...)
}

func (c *Client) DescribeInstanceTypes(ctx context.Context, input *ec2.DescribeInstanceTypesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return c.ec2Client.DescribeInstanceTypes(ctx, input, optFns...)
}
Expand All @@ -84,6 +93,30 @@ func (c *Client) GetConsoleOutput(ctx context.Context, input *ec2.GetConsoleOutp
return c.ec2Client.GetConsoleOutput(ctx, input, optFns...)
}

func (c *Client) CreateSecurityGroup(ctx context.Context, params *ec2.CreateSecurityGroupInput, optFns ...func(*ec2.Options)) (*ec2.CreateSecurityGroupOutput, error) {
return c.ec2Client.CreateSecurityGroup(ctx, params, optFns...)
}

func (c *Client) DescribeSubnets(ctx context.Context, params *ec2.DescribeSubnetsInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) {
return c.ec2Client.DescribeSubnets(ctx, params, optFns...)
}

func (c *Client) DeleteSecurityGroup(ctx context.Context, params *ec2.DeleteSecurityGroupInput, optFns ...func(*ec2.Options)) (*ec2.DeleteSecurityGroupOutput, error) {
return c.ec2Client.DeleteSecurityGroup(ctx, params, optFns...)
}

func (c *Client) DescribeSecurityGroups(ctx context.Context, params *ec2.DescribeSecurityGroupsInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) {
return c.ec2Client.DescribeSecurityGroups(ctx, params, optFns...)
}

func (c *Client) AuthorizeSecurityGroupEgress(ctx context.Context, params *ec2.AuthorizeSecurityGroupEgressInput, optFns ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupEgressOutput, error) {
return c.ec2Client.AuthorizeSecurityGroupEgress(ctx, params, optFns...)
}

func (c *Client) RevokeSecurityGroupEgress(ctx context.Context, params *ec2.RevokeSecurityGroupEgressInput, optFns ...func(*ec2.Options)) (*ec2.RevokeSecurityGroupEgressOutput, error) {
return c.ec2Client.RevokeSecurityGroupEgress(ctx, params, optFns...)
}

// TerminateEC2Instance terminates target ec2 instance
func (c *Client) TerminateEC2Instance(ctx context.Context, instanceID string) error {
input := ec2.TerminateInstancesInput{
Expand All @@ -93,5 +126,11 @@ func (c *Client) TerminateEC2Instance(ctx context.Context, instanceID string) er
return handledErrors.NewGenericError(err)
}

// Wait up to 5 minutes for the instance to be terminated
waiter := ec2.NewInstanceTerminatedWaiter(c)
if err := waiter.Wait(ctx, &ec2.DescribeInstancesInput{InstanceIds: []string{instanceID}}, 5*time.Minute); err != nil {
return handledErrors.NewGenericError(err)
}

return nil
}
12 changes: 12 additions & 0 deletions pkg/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ package helpers
import (
_ "embed"
"errors"
"math/rand"
"time"
)

//go:embed config/userdata.yaml
var UserdataTemplate string

// RandSeq generates random string with n characters.
func RandSeq(n int) string {
rand.Seed(time.Now().UnixNano())
b := make([]rune, n)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

// PollImmediate calls the condition function at the specified interval up to the specified timeout
// until the condition function returns true or an error
func PollImmediate(interval time.Duration, timeout time.Duration, condition func() (bool, error)) error {
Expand Down
126 changes: 125 additions & 1 deletion pkg/verifier/aws/aws_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func (a *AwsVerifier) createEC2Instance(input createEC2InstanceInput) (string, e
}

for _, i := range instanceResp.Instances {
a.Logger.Info(context.TODO(), "Created instance with ID: %s", *i.InstanceId)
a.Logger.Info(input.ctx, "Created instance with ID: %s", *i.InstanceId)
}

if len(instanceResp.Instances) == 0 {
Expand Down Expand Up @@ -329,3 +329,127 @@ func (a *AwsVerifier) writeDebugLogs(log string) {
a.Output.AddDebugLogs(log)
a.Logger.Debug(context.TODO(), log)
}

// CreateSecurityGroup creates a security group with the specified name and cluster tag key in a specified VPC
func (a *AwsVerifier) CreateSecurityGroup(ctx context.Context, tags map[string]string, name, vpcId string) (*ec2.CreateSecurityGroupOutput, error) {
input := &ec2.CreateSecurityGroupInput{
GroupName: awsTools.String(name + "-" + helpers.RandSeq(5)),
VpcId: &vpcId,
Description: awsTools.String("osd-network-verifier security group"),
}
a.writeDebugLogs("Creating a Security group")
output, err := a.AwsClient.CreateSecurityGroup(ctx, input)
if err != nil {
return &ec2.CreateSecurityGroupOutput{}, err
}

a.writeDebugLogs(fmt.Sprintf("Waiting for the Security Group to exist: %s", *output.GroupId))
// Wait up to 1 minutes for the security group to exist
waiter := ec2.NewSecurityGroupExistsWaiter(a.AwsClient)
if err := waiter.Wait(ctx, &ec2.DescribeSecurityGroupsInput{GroupIds: []string{*output.GroupId}}, 1*time.Minute); err != nil {
a.writeDebugLogs(fmt.Sprintf("Error waiting for the security group to exist: %s, attempting to delete the Security Group", *output.GroupId))
_, err := a.AwsClient.DeleteSecurityGroup(ctx, &ec2.DeleteSecurityGroupInput{GroupId: output.GroupId})
if err != nil {
return &ec2.CreateSecurityGroupOutput{}, handledErrors.NewGenericError(err)
}
return &ec2.CreateSecurityGroupOutput{}, fmt.Errorf("deleted %s after timing out waiting for security group to exist", *output.GroupId)
}

a.Logger.Info(ctx, "Created security group with ID: %s", *output.GroupId)
if err := a.createTags(tags, *output.GroupId); err != nil {
// Unable to tag the instance
_, err := a.AwsClient.DeleteSecurityGroup(ctx, &ec2.DeleteSecurityGroupInput{GroupId: output.GroupId})
if err != nil {
return &ec2.CreateSecurityGroupOutput{}, handledErrors.NewGenericError(err)
}
return &ec2.CreateSecurityGroupOutput{}, handledErrors.NewGenericError(err)
}

input_rules := &ec2.AuthorizeSecurityGroupEgressInput{
GroupId: output.GroupId,
IpPermissions: []ec2Types.IpPermission{
{
FromPort: awsTools.Int32(80),
ToPort: awsTools.Int32(80),
IpProtocol: awsTools.String("tcp"),
IpRanges: []ec2Types.IpRange{
{
CidrIp: awsTools.String("0.0.0.0/0"),
},
},
},
{
FromPort: awsTools.Int32(443),
ToPort: awsTools.Int32(443),
IpProtocol: awsTools.String("tcp"),
IpRanges: []ec2Types.IpRange{
{
CidrIp: awsTools.String("0.0.0.0/0"),
},
},
},
{
FromPort: awsTools.Int32(9997),
ToPort: awsTools.Int32(9997),
IpProtocol: awsTools.String("tcp"),
IpRanges: []ec2Types.IpRange{
{
CidrIp: awsTools.String("0.0.0.0/0"),
},
},
},
},
}

if _, err := a.AwsClient.AuthorizeSecurityGroupEgress(ctx, input_rules); err != nil {
return &ec2.CreateSecurityGroupOutput{}, err
}

revoke_default_egress := &ec2.RevokeSecurityGroupEgressInput{
GroupId: output.GroupId,
IpPermissions: []ec2Types.IpPermission{
{
FromPort: awsTools.Int32(-1),
ToPort: awsTools.Int32(-1),
IpProtocol: awsTools.String("-1"),
IpRanges: []ec2Types.IpRange{
{
CidrIp: awsTools.String("0.0.0.0/0"),
},
},
},
},
}

if _, err := a.AwsClient.RevokeSecurityGroupEgress(ctx, revoke_default_egress); err != nil {
return &ec2.CreateSecurityGroupOutput{}, err
}

return output, nil
}

// GetVpcIdFromSubnetId takes in a subnet id and returns the associated VPC id
func (a *AwsVerifier) GetVpcIdFromSubnetId(ctx context.Context, vpcSubnetID string) (string, error) {
input := &ec2.DescribeSubnetsInput{

SubnetIds: []string{vpcSubnetID},
}

output, err := a.AwsClient.DescribeSubnets(ctx, input)
if err != nil {
return "", err
}

// What if we get an empty vpc-id for a returned subnet
if len(output.Subnets) == 0 {
return "", fmt.Errorf("no subnets returned for subnet id: %s", vpcSubnetID)
}

// What if the Subnets array has 0 entries
vpcId := *output.Subnets[0].VpcId
if vpcId == "" {
// return "", errors.New("Empty VPCId for the returned subnet")
return "", fmt.Errorf("empty vpc id for the returned subnet: %s", vpcSubnetID)
}
return vpcId, nil
}
24 changes: 24 additions & 0 deletions pkg/verifier/aws/entry_point.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ func (a *AwsVerifier) ValidateEgress(vei verifier.ValidateEgressInput) *output.O
return a.Output.AddError(err) // fatal
}

cleanupSecurityGroup := false
if vei.AWS.SecurityGroupId == "" {
vpcId, err := a.GetVpcIdFromSubnetId(vei.Ctx, vei.SubnetID)
if err != nil {
return a.Output.AddError(err)
}

createSecurityGroupOutput, err := a.CreateSecurityGroup(vei.Ctx, vei.Tags, "osd-network-verifier", vpcId)
if err != nil {
return a.Output.AddError(err)
}

vei.AWS.SecurityGroupId = *createSecurityGroupOutput.GroupId
cleanupSecurityGroup = true
}

// Create EC2 instance
instanceID, err := a.createEC2Instance(createEC2InstanceInput{
amiID: vei.CloudImageID,
SubnetID: vei.SubnetID,
Expand All @@ -84,6 +101,13 @@ func (a *AwsVerifier) ValidateEgress(vei verifier.ValidateEgressInput) *output.O
a.Output.AddError(err)
}

if cleanupSecurityGroup {
_, err := a.AwsClient.DeleteSecurityGroup(vei.Ctx, &ec2.DeleteSecurityGroupInput{GroupId: awsTools.String(vei.AWS.SecurityGroupId)})
if err != nil {
a.Output.AddError(err)
}
}

return &a.Output
}

Expand Down

0 comments on commit 94a9287

Please sign in to comment.