feat: switch to NAT instance instead of NAT gateway (#164)

Change code to use NAT instance instead of a NAT gateway.

Update AWS provider to v6.

Signed-off-by: Scott Lowe <scott.lowe@scottlowe.org>
This commit is contained in:
Scott S. Lowe 2024-03-19 13:21:51 -06:00 committed by GitHub
parent 7696da5b2e
commit ea75da13eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 155 additions and 161 deletions

View file

@ -4,66 +4,90 @@ go 1.21
require (
github.com/apparentlymart/go-cidr v1.1.0
github.com/pulumi/pulumi-aws/sdk/v5 v5.42.0
github.com/pulumi/pulumi/sdk/v3 v3.55.0
github.com/pulumi/pulumi-aws/sdk/v6 v6.24.0
github.com/pulumi/pulumi/sdk/v3 v3.105.0
)
require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/charmbracelet/bubbles v0.16.1 // indirect
github.com/charmbracelet/bubbletea v0.24.2 // indirect
github.com/charmbracelet/lipgloss v0.7.1 // indirect
github.com/cheggaaa/pb v1.0.29 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/djherbis/times v1.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.5.1 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.11.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/hashicorp/hcl/v2 v2.17.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/opentracing/basictracer-go v1.1.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pjbgf/sha1cd v0.2.3 // indirect
github.com/pgavlin/fx v0.1.6 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/term v1.1.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect
github.com/pulumi/esc v0.6.2 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/zclconf/go-cty v1.13.2 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 // indirect
google.golang.org/grpc v1.51.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/grpc v1.57.1 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/frand v1.4.2 // indirect
sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 // indirect
)

View file

@ -7,8 +7,8 @@ import (
"net/netip"
"github.com/apparentlymart/go-cidr/cidr"
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/ec2"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
@ -16,28 +16,24 @@ import (
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Get some values from the Pulumi stack configuration
keyPair := config.Require(ctx, "sshKeyPair")
bastionAmiType, err := config.Try(ctx, "bastionType")
if err != nil {
bastionAmiType = "t3a.small"
}
vpcNetworkCidr, err := config.Try(ctx, "networkCidr")
keyPair := config.Require(ctx, "sshkeypair")
vpcNetworkCidr, err := config.Try(ctx, "networkcidr")
if err != nil {
vpcNetworkCidr = "10.0.0.0/16"
}
subnetMask, err := config.TryInt(ctx, "subnetMask")
subnetMask, err := config.TryInt(ctx, "subnetmask")
if err != nil {
subnetMask = 22
}
clusterName, err := config.Try(ctx, "clusterName")
clusterName, err := config.Try(ctx, "clustername")
if err != nil {
clusterName = "test"
}
ownerTagValue, err := config.Try(ctx, "ownerTagValue")
ownerTagValue, err := config.Try(ctx, "ownertagvalue")
if err != nil {
ownerTagValue = "nobody@nowhere.com"
}
teamTagValue, err := config.Try(ctx, "teamTagValue")
teamTagValue, err := config.Try(ctx, "teamtagvalue")
if err != nil {
teamTagValue = "TeamOfOne"
}
@ -110,7 +106,7 @@ func main() {
log.Printf("error calculating subnet CIDR: %s", err.Error())
return err
}
subnet, err := ec2.NewSubnet(ctx, fmt.Sprintf("%s-public-%d", clusterName, idx), &ec2.SubnetArgs{
subnet, err := ec2.NewSubnet(ctx, fmt.Sprintf("public-%d", idx), &ec2.SubnetArgs{
VpcId: vpc.ID(),
AvailabilityZone: pulumi.String(azNames[idx]),
CidrBlock: pulumi.String(subnetCidr.String()),
@ -147,7 +143,7 @@ func main() {
ctx.Export("gatewayId", gw.ID())
// Adopt the default route table in the new VPC
defrt, err := ec2.NewDefaultRouteTable(ctx, "def-rt", &ec2.DefaultRouteTableArgs{
defRt, err := ec2.NewDefaultRouteTable(ctx, "def-rt", &ec2.DefaultRouteTableArgs{
DefaultRouteTableId: vpc.DefaultRouteTableId,
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("%s-def-rt", clusterName),
@ -159,11 +155,11 @@ func main() {
if err != nil {
log.Printf("error adopting default route table: %s", err.Error())
}
ctx.Export("defaultRoute", defrt.ID())
ctx.Export("defaultRoute", defRt.ID())
// Create a route for Internet access in the default route table
route, err := ec2.NewRoute(ctx, "inet-route", &ec2.RouteArgs{
RouteTableId: defrt.ID(),
RouteTableId: defRt.ID(),
DestinationCidrBlock: pulumi.String("0.0.0.0/0"),
GatewayId: gw.ID(),
})
@ -179,7 +175,7 @@ func main() {
if err != nil {
log.Printf("error calculating subnet CIDR: %s", err.Error())
}
subnet, err := ec2.NewSubnet(ctx, fmt.Sprintf("%s-private-%d", clusterName, idx), &ec2.SubnetArgs{
subnet, err := ec2.NewSubnet(ctx, fmt.Sprintf("private-%d", idx), &ec2.SubnetArgs{
VpcId: vpc.ID(),
AvailabilityZone: pulumi.String(azNames[idx]),
CidrBlock: pulumi.String(subnetCidr.String()),
@ -199,75 +195,11 @@ func main() {
}
ctx.Export("privSubnetIds", pulumi.StringArray(privSubnetIds))
// Create/allocate an Elastic IP address for the NAT gateway for private subnets
eip, err := ec2.NewEip(ctx, "natgw-eip", &ec2.EipArgs{
Vpc: pulumi.Bool(true),
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("%s-natgw-eip", clusterName),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
},
})
if err != nil {
log.Printf("error creating EIP: %s", err.Error())
}
ctx.Export("EIP", eip.AllocationId)
// Create a NAT gateway for the private subnets
// All private subnets share one NAT Gateway
natgw, err := ec2.NewNatGateway(ctx, "natgw", &ec2.NatGatewayArgs{
AllocationId: eip.ID(),
SubnetId: pubSubnetIds[0],
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("%s-natgw", clusterName),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
},
}, pulumi.DependsOn([]pulumi.Resource{eip}))
if err != nil {
log.Printf("error creating NAT gateway: %s", err.Error())
}
ctx.Export("natGateway", natgw.ID())
// Create a new route table for Internet access from private subnets
privrt, err := ec2.NewRouteTable(ctx, "priv-rt", &ec2.RouteTableArgs{
VpcId: vpc.ID(),
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("%s-priv-rt", clusterName),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
},
Routes: ec2.RouteTableRouteArray{
&ec2.RouteTableRouteArgs{
CidrBlock: pulumi.String("0.0.0.0/0"),
NatGatewayId: natgw.ID(),
},
},
})
if err != nil {
log.Printf("error creating private route table: %s", err.Error())
}
ctx.Export("privRouteTableId", privrt.ID())
// Associate the private subnets with the NAT gateway route table
for idx := 0; idx < numOfAZs; idx++ {
_, err := ec2.NewRouteTableAssociation(ctx, fmt.Sprintf("priv-rta-%d", idx), &ec2.RouteTableAssociationArgs{
SubnetId: privSubnetIds[idx],
RouteTableId: privrt.ID(),
})
if err != nil {
log.Printf("error associating private subnet with route table: %s", err.Error())
}
}
// Create a security group for traffic to the SSH bastion host
bastionSecGrp, err := ec2.NewSecurityGroup(ctx, "bastion-sg", &ec2.SecurityGroupArgs{
Name: pulumi.Sprintf("%s-bastion-sg", clusterName),
// Create a security group for the NAT instance/bastion host
edgeSecGrp, err := ec2.NewSecurityGroup(ctx, "edge-sg", &ec2.SecurityGroupArgs{
Name: pulumi.Sprintf("%s-edge-sg", clusterName),
VpcId: vpc.ID(),
Description: pulumi.String("Allows SSH traffic to bastion hosts"),
Description: pulumi.String("Allows inbound SSH and WG traffic"),
Ingress: ec2.SecurityGroupIngressArray{
ec2.SecurityGroupIngressArgs{
Protocol: pulumi.String("tcp"),
@ -283,6 +215,13 @@ func main() {
Description: pulumi.String("Allow Wireguard VPN (UDP 51280) from anywhere"),
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
},
ec2.SecurityGroupIngressArgs{
Protocol: pulumi.String("-1"),
ToPort: pulumi.Int(0),
FromPort: pulumi.Int(0),
Description: pulumi.String("Allow all traffic from VPC network CIDR"),
CidrBlocks: pulumi.StringArray{pulumi.String(vpcNetworkCidr)},
},
},
Egress: ec2.SecurityGroupEgressArray{
ec2.SecurityGroupEgressArgs{
@ -294,7 +233,7 @@ func main() {
},
},
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("%s-bastion-sg", clusterName),
"Name": pulumi.Sprintf("%s-edge-sg", clusterName),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
@ -303,7 +242,77 @@ func main() {
if err != nil {
log.Printf("error creating security group: %s", err.Error())
}
ctx.Export("bastionSecGrpId", bastionSecGrp.ID())
ctx.Export("edgeSecGrpId", edgeSecGrp.ID())
// Get AMI ID for the NAT instance
natAmi, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{
Owners: []string{"568608671756"},
MostRecent: pulumi.BoolRef(true),
Filters: []ec2.GetAmiFilter{
{Name: "name", Values: []string{"fck-nat-amzn2-*"}},
{Name: "root-device-type", Values: []string{"ebs"}},
{Name: "virtualization-type", Values: []string{"hvm"}},
{Name: "architecture", Values: []string{"arm64"}},
},
})
if err != nil {
log.Printf("error looking up NAT AMI: %s", err.Error())
}
// Launch a fck-nat instance
natInstance, err := ec2.NewInstance(ctx, "nat-instance", &ec2.InstanceArgs{
Ami: pulumi.String(natAmi.Id),
InstanceType: pulumi.String("t4g.nano"),
AssociatePublicIpAddress: pulumi.Bool(true),
KeyName: pulumi.String(keyPair),
SourceDestCheck: pulumi.BoolPtr(false),
SubnetId: pubSubnetIds[0],
VpcSecurityGroupIds: pulumi.StringArray{edgeSecGrp.ID()},
Tags: pulumi.StringMap{
"Name": pulumi.String("nat-instance"),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
},
})
if err != nil {
log.Printf("error launching instance: %s", err.Error())
}
ctx.Export("natInstanceId", natInstance.ID())
ctx.Export("natPublicIpAddress", natInstance.PublicIp)
ctx.Export("natPrivateIpAddress", natInstance.PrivateIp)
// Create a new route table for Internet access from private subnets
privrt, err := ec2.NewRouteTable(ctx, "priv-rt", &ec2.RouteTableArgs{
VpcId: vpc.ID(),
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("%s-priv-rt", clusterName),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
},
Routes: ec2.RouteTableRouteArray{
&ec2.RouteTableRouteArgs{
CidrBlock: pulumi.String("0.0.0.0/0"),
NetworkInterfaceId: natInstance.PrimaryNetworkInterfaceId.ToStringOutput(),
},
},
})
if err != nil {
log.Printf("error creating private route table: %s", err.Error())
}
ctx.Export("privRouteTableId", privrt.ID())
// Associate the private subnets with the NAT instance route table
for idx := 0; idx < numOfAZs; idx++ {
_, err := ec2.NewRouteTableAssociation(ctx, fmt.Sprintf("priv-rta-%d", idx), &ec2.RouteTableAssociationArgs{
SubnetId: privSubnetIds[idx],
RouteTableId: privrt.ID(),
})
if err != nil {
log.Printf("error associating private subnet with route table: %s", err.Error())
}
}
// Create a security group for Kubernetes nodes in this VPC
nodeSecGrp, err := ec2.NewSecurityGroup(ctx, "node-sg", &ec2.SecurityGroupArgs{
@ -312,11 +321,11 @@ func main() {
Description: pulumi.String("Allows traffic between and among K8s nodes"),
Ingress: ec2.SecurityGroupIngressArray{
ec2.SecurityGroupIngressArgs{
Protocol: pulumi.String("tcp"),
ToPort: pulumi.Int(22),
FromPort: pulumi.Int(22),
Description: pulumi.String("Allow inbound SSH (TCP 22) from bastion hosts"),
SecurityGroups: pulumi.StringArray{bastionSecGrp.ID()},
Protocol: pulumi.String("-1"),
ToPort: pulumi.Int(0),
FromPort: pulumi.Int(0),
Description: pulumi.String("Allow all traffic from edge security group"),
SecurityGroups: pulumi.StringArray{edgeSecGrp.ID()},
},
ec2.SecurityGroupIngressArgs{
Protocol: pulumi.String("-1"),
@ -364,45 +373,6 @@ func main() {
}
ctx.Export("k8sSecGrpId", k8sSecGrp.ID())
// Get AMI ID for bastion host
mostRecent := true
instanceAmi, err := ec2.LookupAmi(ctx, &ec2.LookupAmiArgs{
Owners: []string{"099720109477"},
MostRecent: &mostRecent,
Filters: []ec2.GetAmiFilter{
{Name: "name", Values: []string{"ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server*"}},
{Name: "root-device-type", Values: []string{"ebs"}},
{Name: "virtualization-type", Values: []string{"hvm"}},
{Name: "architecture", Values: []string{"x86_64"}},
},
})
if err != nil {
log.Printf("error looking up AMI: %s", err.Error())
}
// Launch an instance to serve as bastion host
bastion, err := ec2.NewInstance(ctx, "bastion", &ec2.InstanceArgs{
Ami: pulumi.String(instanceAmi.Id),
InstanceType: pulumi.String(bastionAmiType),
AssociatePublicIpAddress: pulumi.Bool(true),
KeyName: pulumi.String(keyPair),
SubnetId: pubSubnetIds[0],
SourceDestCheck: pulumi.Bool(false),
VpcSecurityGroupIds: pulumi.StringArray{bastionSecGrp.ID()},
Tags: pulumi.StringMap{
"Name": pulumi.Sprintf("bastion-%s", clusterName),
k8sTag: pulumi.String("shared"),
"Owner": pulumi.String(ownerTagValue),
"Team": pulumi.String(teamTagValue),
},
})
if err != nil {
log.Printf("error launching instance: %s", err.Error())
}
ctx.Export("bastionInstanceId", bastion.ID())
ctx.Export("bastionPublicIpAddress", bastion.PublicIp)
ctx.Export("bastionPrivateIpAddress", bastion.PrivateIp)
return nil
})
}