Skip to content

Commit

Permalink
Fix whitespace, public IP issue
Browse files Browse the repository at this point in the history
  • Loading branch information
bwhaley committed May 9, 2024
1 parent 44e72a9 commit f8c2793
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 90 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ jobs:
with:
go-version-file: test/go.mod
cache-dependency-path: test/go.sum


- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53
# use config in .golangci.yaml to configure the action further

- name: Go Tidy
run: go mod tidy && git diff --exit-code

Expand Down
37 changes: 37 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# https://golangci-lint.run/usage/configuration/#config-file
run:
allow-parallel-runners: true
timeout: 5m
go: '1.20'
skip-dirs-use-default: false

linters:
enable:
- errcheck
- errorlint
- exportloopref
- gocritic
- gofmt
- goimports
- gosec
- govet
- misspell
- revive
- staticcheck
- tenv
- unconvert
- unused
- unparam

issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- text: "G306:"
linters:
- gosec
- text: "G204:"
linters:
- gosec
- text: "G112:"
linters:
- gosec
7 changes: 1 addition & 6 deletions examples/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,13 @@ locals {
module "alternat" {
source = "../modules/terraform-aws-alternat"

create_nat_gateways = var.create_nat_gateways
#nat_gateway_id = aws_nat_gateway.main[0].id
create_nat_gateways = var.create_nat_gateways
ingress_security_group_cidr_blocks = var.private_subnets
vpc_az_maps = local.vpc_az_maps
vpc_id = module.vpc.vpc_id

lambda_package_type = "Zip"
#lambda_has_ipv6 = false
#alternat_image_uri = "188238883601.dkr.ecr.us-west-2.amazonaws.com/alternat"
#alternat_image_tag = "v0.4.9"

nat_instance_type = var.alternat_instance_type
nat_instance_key_name = var.nat_instance_key_name
#connectivity_test_check_urls = ["https://www.google.com", "https://www.example.com"]
}
171 changes: 88 additions & 83 deletions test/alternat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,40 +26,42 @@ import (
)

func TestAlternat(t *testing.T) {
// os.Setenv("SKIP_setup", "true")
// os.Setenv("SKIP_apply_vpc", "true")
// os.Setenv("SKIP_apply_alternat_basic", "true")
// os.Setenv("SKIP_validate_alternat_basic", "true")
// os.Setenv("SKIP_validate_alternat_setup", "true")
// os.Setenv("SKIP_validate_alternat_replace_route", "true")
// os.Setenv("SKIP_cleanup", "true")
// os.Setenv("SKIP_setup", "true")
// os.Setenv("SKIP_apply_vpc", "true")
// os.Setenv("SKIP_apply_alternat_basic", "true")
// os.Setenv("SKIP_validate_alternat_basic", "true")
// os.Setenv("SKIP_validate_alternat_setup", "true")
// os.Setenv("SKIP_validate_alternat_replace_route", "true")
// os.Setenv("SKIP_cleanup", "true")

exampleFolder := test_structure.CopyTerraformFolderToTemp(t, "..", "examples/")

// logger := logger.Logger{}

defer test_structure.RunTestStage(t, "cleanup", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, exampleFolder)
awsKeyPair := test_structure.LoadEc2KeyPair(t, exampleFolder)
terraws.DeleteEC2KeyPair(t, awsKeyPair)
terraform.Destroy(t, terraformOptions)
})

test_structure.RunTestStage(t, "setup", func() {
//Use a random region if the SCP allows, otherwise hardcode.
//awsRegion := terraws.GetRandomStableRegion(t, nil, nil)
awsRegion := "us-east-1"

uniqueID := random.UniqueId()
keyPair := ssh.GenerateRSAKeyPair(t, 2048)
awsKeyPair := terraws.ImportEC2KeyPair(t, awsRegion, uniqueID, keyPair)

terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: exampleFolder,
Vars: map[string]interface{}{
"aws_region": awsRegion,
"aws_region": awsRegion,
"nat_instance_key_name": awsKeyPair.Name,
},
})

test_structure.SaveString(t, exampleFolder, "awsRegion", awsRegion)
test_structure.SaveEc2KeyPair(t, exampleFolder, awsKeyPair)
test_structure.SaveTerraformOptions(t, exampleFolder, terraformOptions)
Expand All @@ -73,7 +75,7 @@ func TestAlternat(t *testing.T) {
}
terraformOptionsVpcOnly.Targets = []string{"module.vpc"}
terraform.InitAndApply(t, terraformOptionsVpcOnly)

vpcID := terraform.Output(t, terraformOptions, "vpc_id")
test_structure.SaveString(t, exampleFolder, "vpcID", vpcID)
})
Expand All @@ -96,35 +98,36 @@ func TestAlternat(t *testing.T) {
// Validate that private route tables have routes to the Internet via ENI
for _, rt := range routeTables {
for _, r := range rt.Routes {
// If the route has a gateway ID, it must be a public route table.
// If the route has a gateway ID, it must be a public route table.
// Otherwise, it must be a private route table, and it must route to the Internet via ENI.
if aws.ToString(r.DestinationCidrBlock) == "0.0.0.0/0" && r.GatewayId == nil && r.NetworkInterfaceId == nil {
t.Fatalf("Private route table %v does not have a default route via ENI", rt.RouteTableId)
}
}
}
}
})

test_structure.RunTestStage(t, "validate_alternat_setup", func() {
sgId := aws.String(test_structure.LoadString(t, exampleFolder, "sgId"))
ec2Client := getEc2Client(t, test_structure.LoadString(t, exampleFolder, "awsRegion"))
authorizeSshIngress(t, ec2Client, sgId)
ip, err := getNatInstancePublicIp(ec2Client)
require.NoError(t, err)
awsRegion := test_structure.LoadString(t, exampleFolder, "awsRegion")
ec2Client := getEc2Client(t, awsRegion)
awsKeyPair := test_structure.LoadEc2KeyPair(t, exampleFolder)


authorizeSshIngress(t, ec2Client, sgId)
ip, err := getNatInstancePublicIp(ec2Client)
require.NoError(t, err)

natInstance := ssh.Host{
Hostname: ip,
SshUserName: "ec2-user",
SshKeyPair: awsKeyPair.KeyPair,
}

maxRetries := 6
waitTime := 10 * time.Second
retry.DoWithRetry(t, fmt.Sprintf("Check SSH connection to %s", ip), maxRetries, waitTime, func() (string, error) {
return "", ssh.CheckSshConnectionE(t, natInstance)
},
)
return "", ssh.CheckSshConnectionE(t, natInstance)
})
command := "/usr/sbin/sysctl net.ipv4.ip_forward net.ipv4.conf.eth0.send_redirects net.ipv4.ip_local_port_range"

expectedText := `net.ipv4.ip_forward = 1
Expand All @@ -139,31 +142,25 @@ net.ipv4.ip_local_port_range = 1024 65535
require.NoError(t, err)
if actualText != expectedText {
return "", fmt.Errorf("Expected SSH command to return '%s' but got '%s'", expectedText, actualText)
}
}
return "", nil
})

userdataLogFile := "/var/log/user-data.log"
output := retry.DoWithRetry(
t,
fmt.Sprintf("Check contents of file %s", userdataLogFile),
10,
30*time.Second,
func() (string, error) {
return ssh.FetchContentsOfFileE(t, natInstance, false, userdataLogFile)
},
)
userdataLogFile := "/var/log/user-data.log"
output := retry.DoWithRetry(t, fmt.Sprintf("Check contents of file %s", userdataLogFile), maxRetries, waitTime, func() (string, error) {
return ssh.FetchContentsOfFileE(t, natInstance, false, userdataLogFile)
})
assert.Contains(t, output, "Configuration completed successfully!", "Success string not found in user-data log: %s", output)
})
// Delete the egress rules that allow access to the Internet from the instance, then

// Delete the egress rules that allow access to the Internet from the instance, then
// validate that Alternat has updated the route to use the NAT Gateway.
test_structure.RunTestStage(t, "validate_alternat_replace_route", func() {
sgId := aws.String(test_structure.LoadString(t, exampleFolder, "sgId"))
vpcID := test_structure.LoadString(t, exampleFolder, "vpcID")
awsRegion := test_structure.LoadString(t, exampleFolder, "awsRegion")
ec2Client := getEc2Client(t, awsRegion)

updateEgress(t, ec2Client, sgId, true)

// Validate that private route tables have routes to the Internet via NAT Gateway
Expand All @@ -178,7 +175,7 @@ net.ipv4.ip_local_port_range = 1024 65535
return "", fmt.Errorf("Private route table %v does not have a route via NAT Gateway", *rt.RouteTableId)
}
}
}
}
return "All private route tables route through NAT Gateway", nil
})
updateEgress(t, ec2Client, sgId, false)
Expand All @@ -203,78 +200,86 @@ func updateEgress(t *testing.T, ec2Client *ec2.Client, sgId *string, revoke bool
CidrIpv6: aws.String("::/0"),
},
}
allPermissions := []ec2types.IpPermission{ipv4Permission, ipv6Permission}
allPermissions := []ec2types.IpPermission{ipv4Permission, ipv6Permission}

var err error
if revoke {
_, err = ec2Client.RevokeSecurityGroupEgress(context.TODO(), &ec2.RevokeSecurityGroupEgressInput{
GroupId: sgId,
GroupId: sgId,
IpPermissions: allPermissions,
},
},
)
require.NoError(t, err)
} else {
_, err = ec2Client.AuthorizeSecurityGroupEgress(context.TODO(), &ec2.AuthorizeSecurityGroupEgressInput{
GroupId: sgId,
GroupId: sgId,
IpPermissions: allPermissions,
},
},
)
require.NoError(t, err)
}
}

func getRouteTables(t *testing.T, client *ec2.Client, vpcID string) ([]ec2types.RouteTable, error) {
input := &ec2.DescribeRouteTablesInput{
Filters: []ec2types.Filter{
{
Name: aws.String("vpc-id"),
Values: []string{vpcID},
},
},
}

result, err := client.DescribeRouteTables(context.TODO(), input)
if err != nil {
return nil, err
}
require.Greaterf(t, len(result.RouteTables), 0, "Could not find a route table for vpc %s", vpcID)

return result.RouteTables, nil
input := &ec2.DescribeRouteTablesInput{
Filters: []ec2types.Filter{
{
Name: aws.String("vpc-id"),
Values: []string{vpcID},
},
},
}

result, err := client.DescribeRouteTables(context.TODO(), input)
if err != nil {
return nil, err
}
require.Greaterf(t, len(result.RouteTables), 0, "Could not find a route table for vpc %s", vpcID)

return result.RouteTables, nil
}

func getNatInstancePublicIp(ec2Client *ec2.Client) (string, error) {
namePrefix := "alternat-"
input := &ec2.DescribeInstancesInput{
Filters: []ec2types.Filter{
{
Name: aws.String("tag:Name"),
Values: []string{namePrefix + "*"},
},
Filters: []ec2types.Filter{
{
Name: aws.String("tag:Name"),
Values: []string{namePrefix + "*"},
},
},
}

result, err := ec2Client.DescribeInstances(context.TODO(), input)
if err != nil {
maxRetries := 6
waitTime := 10 * time.Second
ip := retry.DoWithRetry(t, "Get NAT Instance public IP", maxRetries, waitTime, func() (string, error) {
result, err := ec2Client.DescribeInstances(context.TODO(), input)
if err != nil {
return "", err
}
}
ip := aws.ToString(result.Reservations[0].Instances[0].PublicIpAddress)
if ip == "" {
return "", fmt.Errorf("Public IP not found")
}
return ip, nil
})

return aws.ToString(result.Reservations[0].Instances[0].PublicIpAddress), nil
return ip, nil
}

func getThisPublicIp() (string, error) {
url := "https://api.ipify.org"
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("Error fetching IP: %v\n", err)
}
defer resp.Body.Close()
if err != nil {
return "", fmt.Errorf("Error fetching IP: %v\n", err)
}
defer resp.Body.Close()

ip, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("Error reading response: %v", err)
}
ip, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("Error reading response: %v", err)
}

return string(ip), nil
return string(ip), nil
}

func authorizeSshIngress(t *testing.T, ec2Client *ec2.Client, sgId *string) {
Expand All @@ -295,17 +300,17 @@ func authorizeSshIngress(t *testing.T, ec2Client *ec2.Client, sgId *string) {
}

_, err = ec2Client.AuthorizeSecurityGroupIngress(context.TODO(), &ec2.AuthorizeSecurityGroupIngressInput{
GroupId: sgId,
GroupId: sgId,
IpPermissions: ipPermission,
},
},
)
require.NoError(t, err)
}

func getEc2Client(t *testing.T, awsRegion string) *ec2.Client {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(awsRegion))
if err != nil {
t.Fatalf("Unable to load SDK config, %v", err)
t.Fatalf("Unable to load SDK config, %v", err)
}
return ec2.NewFromConfig(cfg)
}
}

0 comments on commit f8c2793

Please sign in to comment.