diff --git a/README.md b/README.md index 8a805ba..fc32976 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,29 @@ The instance will run [init.sh](data/init.sh) to enable NAT as follows: ## Configuration -### Allow SSH access +### User data -You can log in to the NAT instance from [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html). +You can set additional `write_files` and `runcmd` section. For example, + +```tf +module "nat" { + user_data_write_files = [ + { + path : "/opt/nat/run.sh", + content : file("./run.sh"), + permissions : "0755", + }, + ] + user_data_runcmd = [ + ["/opt/nat/run.sh"], + ] +} +``` + +See also the [example](example/) for more. + + +### SSH access You can enable SSH access by setting `key_name` option and opening the security group. For example, @@ -127,6 +147,8 @@ No requirements. | public\_subnet | ID of the public subnet to place the NAT instance | `string` | n/a | yes | | tags | Tags applied to resources created with this module | `map` | `{}` | no | | use\_spot\_instance | Whether to use spot or on-demand EC2 instance | `bool` | `true` | no | +| user\_data\_runcmd | Additional runcmd section of cloud-init | `list` | `[]` | no | +| user\_data\_write\_files | Additional write\_files section of cloud-init | `list` | `[]` | no | | vpc\_id | ID of the VPC | `string` | n/a | yes | ## Outputs diff --git a/example/README.md b/example/README.md index f2a0918..20ef522 100644 --- a/example/README.md +++ b/example/README.md @@ -1,27 +1,39 @@ # Example of terraform-aws-nat-instance +This example shows the following things: + +- Create a VPC and subnets using `vpc` module. +- Create a NAT instance using this module. +- Create an instance in the private subnet. +- Add custom scripts to the NAT instance. + In this example, http port of the private instance will be exposed. + + +## Getting Started + Provision the stack. ```console % terraform init - % terraform apply ... -Plan: 37 to add, 0 to change, 0 to destroy. -Do you want to perform these actions? - Terraform will perform the actions described above. - Only 'yes' will be accepted to approve. +Outputs: - Enter a value: yes -... -Apply complete! Resources: 37 added, 0 changed, 0 destroyed. +nat_public_ip = 54.212.155.23 +private_instance_id = i-07c076946c5142cdd +``` + +Make sure you have access to the instance in the private subnet. + +```console +% aws ssm start-session --region us-west-2 --target i-07c076946c5142cdd ``` -Make sure you can access an instance in the private subnet. +Make sure you can access http port of the NAT instance. ```console -% aws ssm start-session --region us-west-2 --target i-01d945b895167862a +% curl http://54.212.155.23 ``` You can completely destroy the stack. diff --git a/example/dnat.service b/example/dnat.service new file mode 100644 index 0000000..68a22a5 --- /dev/null +++ b/example/dnat.service @@ -0,0 +1,10 @@ +[Unit] +Description = DNAT via ENI eth1 + +[Service] +ExecStart = /opt/nat/dnat.sh +Type = simple +Restart = always + +[Install] +WantedBy = multi-user.target diff --git a/example/dnat.sh b/example/dnat.sh new file mode 100644 index 0000000..de99b21 --- /dev/null +++ b/example/dnat.sh @@ -0,0 +1,25 @@ +#!/bin/bash -x + +region="$(/opt/aws/bin/ec2-metadata -z | sed 's/placement: \(.*\).$/\1/')" +eth1_addr="$(ip -f inet -o addr show dev eth1 | cut -d' ' -f 7 | cut -d/ -f 1)" + +function get_instance_private_ip_by_name() { + local name="$1" + aws ec2 describe-instances \ + --region "$region" \ + --filters "Name=tag:Name,Values=$name" "Name=instance-state-name,Values=running" | + jq -r .Reservations[0].Instances[0].PrivateIpAddress +} + +function run_iptables() { + local action="$1" + iptables -t nat "$action" PREROUTING 1 -m tcp -p tcp \ + --dst "$eth1_addr" --dport 80 \ + -j DNAT --to-destination "$(get_instance_private_ip_by_name ${ec2_name}):80" +} + +run_iptables -I +while true; do + sleep 30 + run_iptables -R +done diff --git a/example/example.tf b/example/example.tf index 897bf3a..35a680f 100644 --- a/example/example.tf +++ b/example/example.tf @@ -21,68 +21,56 @@ module "nat" { public_subnet = module.vpc.public_subnets[0] private_subnets_cidr_blocks = module.vpc.private_subnets_cidr_blocks private_route_table_ids = module.vpc.private_route_table_ids -} - -# instance in the private subnet -resource "aws_instance" "private_instance" { - ami = data.aws_ami.amazon_linux_2.id - instance_type = "t3.micro" - iam_instance_profile = aws_iam_instance_profile.private_instance.name - subnet_id = module.vpc.private_subnets[0] - - tags = { - Name = "Example of terraform-aws-nat-instance" - } -} -# AMI of the latest Amazon Linux 2 -data "aws_ami" "amazon_linux_2" { - most_recent = true - owners = ["amazon"] - filter { - name = "architecture" - values = ["x86_64"] - } - filter { - name = "root-device-type" - values = ["ebs"] - } - filter { - name = "name" - values = ["amzn2-ami-hvm-*"] - } - filter { - name = "virtualization-type" - values = ["hvm"] - } - filter { - name = "block-device-mapping.volume-type" - values = ["gp2"] - } -} - -resource "aws_iam_instance_profile" "private_instance" { - role = aws_iam_role.private_instance.name + # enable port forwarding (optional) + user_data_write_files = [ + { + path : "/opt/nat/dnat.sh", + content : templatefile("./dnat.sh", { ec2_name = "example-terraform-aws-nat-instance" }), + permissions : "0755", + }, + { + path : "/etc/systemd/system/dnat.service", + content : file("./dnat.service"), + }, + ] + user_data_runcmd = [ + ["yum", "install", "-y", "jq"], + ["systemctl", "enable", "dnat"], + ["systemctl", "start", "dnat"], + ] } -resource "aws_iam_role" "private_instance" { - assume_role_policy = <