├── LICENSE.md ├── README.md ├── config └── wireguard-ap-southeast-2.json ├── img └── arch.png ├── pipeline.yml └── wireguard.yml /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Rupert Bryant-Greene 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example: WireGuard deployed on AWS with Load Balancing 2 | This is an example repo to demonstrate WireGuard VPN deployed on Amazon EC2 with Network Load Balancer, a Route53 Domain and Amazon Linux 2. 3 | Keys are handled via AWS Parameter Store, encrypted with AWS KMS to prevent the need for reconfiguration when instances are scaled, updated or terminated. 4 | 5 | *Disclaimer: provided as an example and some assumptions have been made for network layout.* 6 | 7 | Reach out on [Reddit](https://www.reddit.com/r/WireGuard/comments/d0vjs6/ive_automated_wireguard_on_aws_with_amazon_linux/) with any queries or tips! 8 | 9 | ## Setup 10 | 1. Generate keys for your server and peer with `wg genkey | tee privatekey | wg pubkey > publickey` then save them in encrypted SSM Parameters called `/wireguard/private` and `/wireguard/peerpublic` 11 | 2. Set Cloudformation parameters in `config/wireguard-${your region}.json` to suit 12 | 3. Deploy `pipeline.yml` via Cloudformation 13 | 4. Configure and connect your peer 14 | 15 | ## Architecture 16 | ![WireGuard on AWS Architecture](img/arch.png) 17 | -------------------------------------------------------------------------------- /config/wireguard-ap-southeast-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Parameters": { 3 | "AMIID": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs", 4 | "ExternalSubnetIds": "subnet-123,subnet-345,subnet-456", 5 | "InternalSubnetIds": "subnet-778,subnet-890,subnet-012", 6 | "VpcId": "vpc-123", 7 | "InstanceType": "t3.nano", 8 | "InternalPeerIp": "172.16.1.2/32", 9 | "InternalServerIp": "172.16.1.1/32", 10 | "WireguardPort": "12345", 11 | "ParameterStoreKMSKeyARN": "", 12 | "VPNDomainName": "", 13 | "VPNDomainHostedZoneId": "" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /img/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaphor-cloud/aws-wireguard-linux/1a0d3e9762d50496529edacba11e30210a9236ad/img/arch.png -------------------------------------------------------------------------------- /pipeline.yml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | 3 | RepoOwner: 4 | Type: String 5 | Description: Username of the Github Repo owner 6 | 7 | RepoName: 8 | Type: String 9 | Description: Name of the Github Repo 10 | 11 | GithubCredsSecret: 12 | Type: String 13 | Description: Name of the AWS Secrets Manager Secret for Github creds 14 | 15 | GithubCredsSecretParam: 16 | Type: String 17 | Description: Key of the Personal Access Token in the Secret 18 | 19 | Resources: 20 | 21 | PipelineBucket: 22 | Type: AWS::S3::Bucket 23 | DeletionPolicy: Retain 24 | 25 | Pipeline: 26 | Type: AWS::CodePipeline::Pipeline 27 | Properties: 28 | Name: !Ref RepoName 29 | ArtifactStore: 30 | Type: S3 31 | Location: !Ref PipelineBucket 32 | RestartExecutionOnUpdate: true 33 | RoleArn: !GetAtt CodePipelineRole.Arn 34 | Stages: 35 | - Name: Source 36 | Actions: 37 | - Name: GitHub 38 | ActionTypeId: 39 | Category: Source 40 | Owner: ThirdParty 41 | Version: 1 42 | Provider: GitHub 43 | OutputArtifacts: 44 | - Name: source 45 | Configuration: 46 | Owner: !Ref RepoOwner 47 | Repo: !Ref RepoName 48 | Branch: master 49 | OAuthToken: !Sub '{{resolve:secretsmanager:${GithubCredsSecret}:SecretString:${GithubCredsSecretParam}}}' 50 | RunOrder: 1 51 | - Name: Update 52 | Actions: 53 | - Name: Pipeline 54 | RunOrder: 1 55 | ActionTypeId: 56 | Category: Deploy 57 | Owner: AWS 58 | Version: 1 59 | Provider: CloudFormation 60 | InputArtifacts: 61 | - Name: source 62 | Configuration: 63 | ActionMode: REPLACE_ON_FAILURE 64 | StackName: !Ref AWS::StackName 65 | RoleArn: !GetAtt CodePipelineRole.Arn 66 | Capabilities: CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND 67 | TemplatePath: source::pipeline.yml 68 | - Name: Deploy 69 | Actions: 70 | - Name: Controller 71 | RunOrder: 1 72 | ActionTypeId: 73 | Category: Deploy 74 | Owner: AWS 75 | Version: 1 76 | Provider: CloudFormation 77 | InputArtifacts: 78 | - Name: source 79 | Configuration: 80 | ActionMode: REPLACE_ON_FAILURE 81 | StackName: wireguard 82 | RoleArn: !GetAtt CodePipelineRole.Arn 83 | Capabilities: CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND 84 | TemplatePath: source::wireguard.yml 85 | TemplateConfiguration: !Sub source::config/wireguard-${AWS::Region}.json 86 | 87 | CodePipelineRole: 88 | Type: AWS::IAM::Role 89 | Properties: 90 | AssumeRolePolicyDocument: 91 | Version: 2012-10-17 92 | Statement: 93 | - Effect: Allow 94 | Principal: 95 | Service: 96 | - codepipeline.amazonaws.com 97 | - cloudformation.amazonaws.com 98 | Action: sts:AssumeRole 99 | ManagedPolicyArns: 100 | - arn:aws:iam::aws:policy/AdministratorAccess 101 | -------------------------------------------------------------------------------- /wireguard.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Wireguard VPN appliances 3 | 4 | Parameters: 5 | 6 | VpcId: 7 | Type: AWS::EC2::VPC::Id 8 | 9 | AMIID: 10 | Type: AWS::SSM::Parameter::Value 11 | Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs 12 | 13 | InstanceType: 14 | Type: String 15 | Default: t3.nano 16 | AllowedValues: [t3a.nano, t3.nano] 17 | 18 | SubnetIds: 19 | Type: List 20 | 21 | InternalPeerIp: 22 | Type: String 23 | 24 | InternalServerIp: 25 | Type: String 26 | 27 | WireguardPort: 28 | Type: String 29 | 30 | ParameterStoreKMSKeyARN: 31 | Type: String 32 | 33 | VPNDomainName: 34 | Type: String 35 | 36 | VPNDomainHostedZoneId: 37 | Type: String 38 | 39 | Resources: 40 | 41 | DNSRecord: 42 | Type: AWS::Route53::RecordSet 43 | Properties: 44 | HostedZoneId: !Ref VPNDomainHostedZoneId 45 | Comment: !Ref AWS::StackName 46 | Name: !Ref VPNDomainName 47 | TTL: 300 48 | Type: CNAME 49 | ResourceRecords: 50 | - !GetAtt LoadBalancer.DNSName 51 | 52 | LoadBalancer: 53 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 54 | Properties: 55 | IpAddressType: ipv4 56 | LoadBalancerAttributes: 57 | - Key: load_balancing.cross_zone.enabled 58 | Value: true 59 | Scheme: internet-facing 60 | Subnets: !Ref SubnetIds 61 | Type: network 62 | 63 | Listener: 64 | Type: AWS::ElasticLoadBalancingV2::Listener 65 | Properties: 66 | DefaultActions: 67 | - Type: forward 68 | TargetGroupArn: !Ref TargetGroup 69 | LoadBalancerArn: !Ref LoadBalancer 70 | Port: !Ref WireguardPort 71 | Protocol: UDP 72 | 73 | TargetGroup: 74 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 75 | Properties: 76 | VpcId: !Ref VpcId 77 | HealthCheckPort: 22 78 | HealthCheckProtocol: TCP 79 | HealthCheckTimeoutSeconds: 10 80 | HealthyThresholdCount: 2 81 | UnhealthyThresholdCount: 2 82 | Protocol: UDP 83 | Port: !Ref WireguardPort 84 | TargetType: instance 85 | TargetGroupAttributes: 86 | - Key: deregistration_delay.timeout_seconds 87 | Value: 0 88 | 89 | SecurityGroup: 90 | Type: AWS::EC2::SecurityGroup 91 | Properties: 92 | VpcId: !Ref VpcId 93 | GroupDescription: Wireguard Security Group 94 | SecurityGroupEgress: 95 | - IpProtocol: -1 96 | CidrIp: 0.0.0.0/0 97 | - IpProtocol: -1 98 | CidrIpv6: ::/0 99 | SecurityGroupIngress: 100 | - IpProtocol: udp 101 | FromPort: !Ref WireguardPort 102 | ToPort: !Ref WireguardPort 103 | CidrIp: 0.0.0.0/0 104 | - IpProtocol: udp 105 | FromPort: !Ref WireguardPort 106 | ToPort: !Ref WireguardPort 107 | CidrIpv6: ::/0 108 | 109 | InstanceRole: 110 | Type: AWS::IAM::Role 111 | Properties: 112 | AssumeRolePolicyDocument: 113 | Statement: 114 | - Effect: Allow 115 | Principal: 116 | Service: 117 | - ec2.amazonaws.com 118 | Action: 119 | - sts:AssumeRole 120 | Path: / 121 | Policies: 122 | - PolicyName: wireguard 123 | PolicyDocument: 124 | Statement: 125 | - Effect: Allow 126 | Action: 127 | - ec2:ModifyInstanceAttribute 128 | Resource: 129 | - '*' 130 | - Effect: Allow 131 | Action: 132 | - ssm:GetParameter 133 | Resource: 134 | - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/wireguard/* 135 | - Effect: Allow 136 | Action: 137 | - kms:Decrypt 138 | Resource: 139 | - !Ref ParameterStoreKMSKeyARN 140 | 141 | InstanceProfile: 142 | Type: AWS::IAM::InstanceProfile 143 | Properties: 144 | Path: / 145 | Roles: 146 | - !Ref InstanceRole 147 | 148 | LaunchTemplate: 149 | Type: AWS::EC2::LaunchTemplate 150 | Properties: 151 | LaunchTemplateData: 152 | ImageId: !Ref AMIID 153 | InstanceInitiatedShutdownBehavior: terminate 154 | InstanceType: !Ref InstanceType 155 | NetworkInterfaces: 156 | - AssociatePublicIpAddress: true 157 | Groups: 158 | - !GetAtt SecurityGroup.GroupId 159 | DeviceIndex: 0 160 | IamInstanceProfile: 161 | Arn: !GetAtt InstanceProfile.Arn 162 | TagSpecifications: 163 | - ResourceType: instance 164 | Tags: 165 | - Key: Name 166 | Value: wireguard 167 | UserData: 168 | Fn::Base64: 169 | !Sub | 170 | #cloud-config 171 | write_files: 172 | - path: /etc/cron.d/wg 173 | permissions: '0700' 174 | owner: root:root 175 | content: | 176 | */1 * * * * root /root/wg-check.sh 177 | @reboot root /root/wg-install.sh > /var/log/wg-install.log 2>&1 178 | - path: /root/wg-check.sh 179 | permissions: '0700' 180 | owner: root:root 181 | content: | 182 | #!/bin/bash 183 | export PATH="/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin"; 184 | count=$(/bin/wg | /bin/wc -c); 185 | if [[ `echo $count` == 0 ]]; then 186 | /bin/wg-quick up wg0 187 | else 188 | /bin/wg 189 | fi; 190 | - path: /etc/wireguard/wg0.conf 191 | permissions: '0600' 192 | owner: root:root 193 | content: | 194 | [Interface] 195 | Address = ${InternalServerIp} 196 | ListenPort = ${WireguardPort} 197 | PrivateKey = PRIVATEKEY 198 | SaveConfig = true 199 | PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 200 | PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE 201 | 202 | [Peer] 203 | PublicKey = PEERPUBLIC 204 | AllowedIPs = ${InternalPeerIp} 205 | 206 | - path: /root/wg-install.sh 207 | permissions: '0700' 208 | owner: root:root 209 | content: | 210 | #!/bin/bash 211 | source /root/.bashrc; 212 | set -euxo pipefail; 213 | trap "/opt/aws/bin/cfn-signal --success false --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup;" ERR; 214 | export PATH="/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin"; 215 | 216 | curl -Lo /etc/yum.repos.d/wireguard.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo; 217 | yum clean all; 218 | yum install wireguard-dkms wireguard-tools iptables-services -y; 219 | until /sbin/modprobe wireguard 220 | do 221 | dmesg | grep wireguard; 222 | done; 223 | 224 | # Wireguard keys 225 | set +x; 226 | PRIVATE_KEY=$(aws --region ${AWS::Region} ssm get-parameter --name /wireguard/private --with-decryption --query Parameter.Value --output text); 227 | PEER_PUBLIC_KEY=$(aws --region ${AWS::Region} ssm get-parameter --name /wireguard/public/phone --with-decryption --query Parameter.Value --output text); 228 | sed -i "s#PRIVATEKEY#$PRIVATE_KEY#" /etc/wireguard/wg0.conf; 229 | sed -i "s#PEERPUBLIC#$PEER_PUBLIC_KEY#" /etc/wireguard/wg0.conf; 230 | set -x; 231 | 232 | # source/dest check 233 | INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id); 234 | aws ec2 --region ${AWS::Region} modify-instance-attribute --instance-id $INSTANCE_ID --no-source-dest-check; 235 | 236 | # iptables 237 | /bin/systemctl enable iptables; 238 | /bin/systemctl restart iptables; 239 | /bin/systemctl status iptables; 240 | /sbin/iptables -F; 241 | /sbin/iptables-save > /etc/sysconfig/iptables; 242 | /sbin/iptables -L; 243 | 244 | # IP Forwarding 245 | echo " 246 | net.ipv4.conf.all.forwarding = 1 247 | net.ipv6.conf.all.forwarding = 1 248 | " > /etc/sysctl.d/wg.conf; 249 | /sbin/sysctl --system; 250 | 251 | systemctl enable wg-quick@wg0.service; 252 | /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup; 253 | 254 | - path: /root/bootstrap.sh 255 | permissions: '0700' 256 | owner: root:root 257 | content: | 258 | #!/bin/bash 259 | set -euxo pipefail; 260 | trap "/opt/aws/bin/cfn-signal --success false --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup;" ERR; 261 | 262 | yum update -y; 263 | 264 | # Enable cron jobs 265 | chmod 644 /etc/cron.d/wg; 266 | reboot; 267 | runcmd: 268 | - /root/bootstrap.sh; 269 | 270 | AutoUpdateScaleUp: 271 | Type: AWS::AutoScaling::ScheduledAction 272 | Properties: 273 | AutoScalingGroupName: !Ref AutoScalingGroup 274 | DesiredCapacity: 2 275 | MinSize: 1 276 | MaxSize: 2 277 | Recurrence: '0 14 * * *' 278 | 279 | AutoUpdateScaleDown: 280 | Type: AWS::AutoScaling::ScheduledAction 281 | Properties: 282 | AutoScalingGroupName: !Ref AutoScalingGroup 283 | DesiredCapacity: 1 284 | MinSize: 1 285 | MaxSize: 2 286 | Recurrence: '15 14 * * *' 287 | 288 | AutoScalingGroup: 289 | Type: AWS::AutoScaling::AutoScalingGroup 290 | Properties: 291 | VPCZoneIdentifier: !Ref SubnetIds 292 | DesiredCapacity: 1 293 | LaunchTemplate: 294 | LaunchTemplateId: !Ref LaunchTemplate 295 | Version: !GetAtt LaunchTemplate.LatestVersionNumber 296 | MinSize: 1 297 | MaxSize: 2 298 | TargetGroupARNs: 299 | - !Ref TargetGroup 300 | CreationPolicy: 301 | ResourceSignal: 302 | Timeout: PT15M 303 | UpdatePolicy: 304 | AutoScalingRollingUpdate: 305 | MaxBatchSize: 1 306 | MinInstancesInService: 1 307 | MinSuccessfulInstancesPercent: 100 308 | PauseTime: PT15M 309 | SuspendProcesses: 310 | - ScheduledActions 311 | - AZRebalance 312 | - ReplaceUnhealthy 313 | WaitOnResourceSignals: true 314 | --------------------------------------------------------------------------------