├── .circleci └── config.yml ├── .dockerignore ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── assets └── images │ ├── how-it-works.png │ └── overview.gif ├── build └── docker │ ├── docker-compose.yml │ ├── entrypoint.sh │ └── sshd_config ├── cmd ├── awslambdaproxy │ ├── root.go │ ├── run.go │ └── setup.go └── main.go ├── deployment ├── iam │ ├── run.json │ └── setup.json └── terraform │ ├── data.tf │ ├── dummy.zip │ ├── lambda.tf │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ ├── provision.tf │ ├── variables.tf │ └── vpc.tf ├── go.mod ├── go.sum └── pkg ├── lambda ├── datacopy.go ├── main.go ├── proxyserver.go └── tunnelconnection.go └── server ├── gost.go ├── infrastructure.go ├── lambdaexecution.go ├── localproxy.go ├── publicip ├── awspublicip │ ├── awspublicip.go │ └── awspublicip_test.go └── publicip.go ├── server.go ├── ssh.go ├── tunnelconnection.go └── util.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.14 6 | 7 | steps: 8 | - checkout 9 | - run: go get -u github.com/go-bindata/go-bindata/... 10 | - run: make 11 | - store_artifacts: 12 | path: artifacts 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | assets 2 | artifacts 3 | bindata.go 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ### Prerequisites 8 | 9 | * [ ] I am running the [latest version](https://github.com/dan-v/awslambdaproxy/releases) of awslambdaproxy 10 | * [ ] I am have read the [README](https://github.com/dan-v/awslambdaproxy#usage) instructions and the [FAQ](https://github.com/dan-v/awslambdaproxy#faq) 11 | 12 | ### Description 13 | 14 | [Description of the issue] 15 | 16 | ### Steps to Reproduce 17 | 18 | 1. [First Step] 19 | 2. [Second Step] 20 | 3. [and so on...] 21 | 22 | **Expected behavior:** [What you expected to happen] 23 | 24 | **Actual behavior:** [What actually happened] 25 | 26 | ### Environment 27 | * If you are using CLI, get the version and specify the full command you are using. 28 | ``` 29 | ./awslambdaproxy version 30 | awslambdaproxy version 0.0.12 31 | ./awslambdaproxy -r us-west-2,us-west-1 -f 60 32 | ``` 33 | * If you are using Docker, get the version and specify the full command you are using. 34 | ``` 35 | docker run -it --rm --entrypoint /app/awslambdaproxy vdan/awslambdaproxy -v 36 | awslambdaproxy version 0.0.12 37 | docker run -d vdan/awslambdaproxy -r us-west-2,us-west-1 -f 60 38 | ``` 39 | 40 | ### Error Output 41 | ``` 42 | ... 43 | ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/go,intellij 3 | 4 | ### Go ### 5 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 6 | *.o 7 | *.a 8 | *.so 9 | 10 | # Folders 11 | _obj 12 | _test 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.exe 27 | *.test 28 | *.prof 29 | 30 | # Output of the go coverage tool, specifically when used with LiteIDE 31 | *.out 32 | 33 | ### Intellij ### 34 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 35 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 36 | 37 | # User-specific stuff: 38 | .idea 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | 42 | # Sensitive or high-churn files: 43 | .idea/dataSources/ 44 | .idea/dataSources.ids 45 | .idea/dataSources.xml 46 | .idea/dataSources.local.xml 47 | .idea/sqlDataSources.xml 48 | .idea/dynamic.xml 49 | .idea/uiDesigner.xml 50 | 51 | # Gradle: 52 | .idea/gradle.xml 53 | .idea/libraries 54 | 55 | # Mongo Explorer plugin: 56 | .idea/mongoSettings.xml 57 | 58 | ## File-based project format: 59 | *.iws 60 | 61 | ## Plugin-specific files: 62 | 63 | # IntelliJ 64 | /out/ 65 | 66 | # mpeltonen/sbt-idea plugin 67 | .idea_modules/ 68 | 69 | # JIRA plugin 70 | atlassian-ide-plugin.xml 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | fabric.properties 77 | 78 | ### Intellij Patch ### 79 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 80 | 81 | *.iml 82 | # modules.xml 83 | # .idea/misc.xml 84 | # *.ipr 85 | 86 | ### Terraform ### 87 | # Local .terraform directories 88 | **/.terraform/* 89 | 90 | # .tfstate files 91 | *.tfstate 92 | *.tfstate.* 93 | 94 | # Crash log files 95 | crash.log 96 | 97 | # Ignore backend.tf files with Terraform backend configuration 98 | backend.tf 99 | 100 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 101 | # .tfvars files are managed as part of configuration and so should be included in 102 | # version control. 103 | *.tfvars 104 | 105 | # Ignore override files as they are usually used to override resources locally and so 106 | # are not checked in 107 | override.tf 108 | override.tf.json 109 | *_override.tf 110 | *_override.tf.json 111 | 112 | # Include override files you do wish to add to version control using negated pattern 113 | # !example_override.tf 114 | 115 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 116 | *tfplan* 117 | 118 | .awslambdaproxy 119 | bindata.go 120 | .DS_Store 121 | vendor/ 122 | artifacts 123 | coverage.html -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14 AS build-env 2 | RUN apt-get update -y 3 | RUN apt-get install -y zip 4 | RUN go get -u github.com/go-bindata/go-bindata/... 5 | ADD . /src 6 | RUN cd /src && make build 7 | 8 | FROM alpine:latest 9 | COPY --from=build-env /src/artifacts/server/linux/awslambdaproxy /app/ 10 | 11 | ENV AWS_ACCESS_KEY_ID= 12 | ENV AWS_SECRET_ACCESS_KEY= 13 | ENV LAMBDA_NAME= 14 | ENV LAMBDA_IAM_ROLE_NAME= 15 | ENV REGIONS= 16 | ENV FREQUENCY= 17 | ENV MEMORY= 18 | ENV SSH_USER= 19 | ENV SSH_PORT=2222 20 | ENV LISTENERS= 21 | ENV DEBUG= 22 | ENV DEBUG_PROXY= 23 | ENV BYPASS= 24 | 25 | WORKDIR /app 26 | 27 | RUN addgroup -g 1000 -S ssh \ 28 | && adduser -u 1000 -S ssh -G ssh \ 29 | && apk add --no-cache openssh-server bash ca-certificates \ 30 | && rm -rf /var/cache/apk/* 31 | 32 | USER ssh 33 | 34 | RUN mkdir ${HOME}/.ssh 35 | 36 | EXPOSE 2222 37 | EXPOSE 8080 38 | 39 | COPY build/docker/sshd_config /etc/ssh/sshd_config 40 | COPY build/docker/entrypoint.sh /entrypoint.sh 41 | 42 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dan Vittegleo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | TARGET := awslambdaproxy 3 | VERSION := $(shell cat VERSION) 4 | OS := linux 5 | ARCH := amd64 6 | PACKAGE := github.com/dan-v/$(TARGET) 7 | 8 | .PHONY: \ 9 | clean \ 10 | tools \ 11 | test \ 12 | coverage \ 13 | vet \ 14 | lint \ 15 | fmt \ 16 | build \ 17 | lambda-build \ 18 | server-build-linux \ 19 | server-build-osx \ 20 | doc \ 21 | release \ 22 | docker-build \ 23 | docker-release \ 24 | 25 | all: tools fmt build lint vet test release 26 | 27 | print-%: 28 | @echo $* = $($*) 29 | 30 | clean: 31 | rm -Rf artifacts 32 | rm -vf $(CURDIR)/coverage.* 33 | 34 | tools: 35 | go get golang.org/x/lint/golint 36 | go get github.com/axw/gocov/gocov 37 | go get github.com/matm/gocov-html 38 | 39 | test: 40 | go test -v ./... 41 | 42 | coverage: 43 | gocov test ./... > $(CURDIR)/coverage.out 2>/dev/null 44 | gocov report $(CURDIR)/coverage.out 45 | if test -z "$$CI"; then \ 46 | gocov-html $(CURDIR)/coverage.out > $(CURDIR)/coverage.html; \ 47 | if which open &>/dev/null; then \ 48 | open $(CURDIR)/coverage.html; \ 49 | fi; \ 50 | fi 51 | 52 | vet: 53 | go vet -v ./... 54 | 55 | lint: 56 | golint $(go list ./... | grep -v /vendor/) 57 | 58 | fmt: 59 | go fmt ./... 60 | 61 | lambda-build: 62 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o artifacts/lambda/main ./pkg/lambda 63 | zip -jr artifacts/lambda artifacts/lambda 64 | go-bindata -nocompress -pkg server -o pkg/server/bindata.go artifacts/lambda.zip 65 | mv artifacts/lambda.zip artifacts/lambda-$(VERSION).zip 66 | 67 | server-build-linux: 68 | CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -ldflags \ 69 | "-X $(PACKAGE)/cmd/awslambdaproxy.version=$(VERSION)" \ 70 | -v -o $(CURDIR)/artifacts/server/$(OS)/$(TARGET) ./cmd/main.go 71 | 72 | server-build-osx: 73 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags \ 74 | "-X $(PACKAGE)/cmd/awslambdaproxy.version=$(VERSION)" \ 75 | -v -o $(CURDIR)/artifacts/server/darwin/$(TARGET) ./cmd/main.go 76 | 77 | build: lambda-build server-build-linux 78 | 79 | build-osx: lambda-build server-build-osx 80 | 81 | doc: 82 | godoc -http=:8080 -index 83 | 84 | release: 85 | mkdir -p ./artifacts 86 | zip -jr ./artifacts/$(TARGET)-$(OS)-$(VERSION).zip ./artifacts/server/$(OS)/$(TARGET) 87 | 88 | docker: 89 | docker build . -t vdan/awslambdaproxy:$(VERSION) -t vdan/awslambdaproxy:latest 90 | 91 | docker-release: 92 | docker push vdan/awslambdaproxy:$(VERSION) 93 | docker push vdan/awslambdaproxy:latest 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTICE: This project is no longer being maintained 2 | 3 | ## Overview 4 | awslambdaproxy is an [AWS Lambda](https://aws.amazon.com/lambda/) powered HTTP/SOCKS web proxy. It provides a constantly rotating IP address for your network traffic from all regions where AWS Lambda is available. The goal is to obfuscate your traffic and make it harder to track you as a user. 5 | 6 | ![](/assets/images/overview.gif?raw=true) 7 | 8 | ## Features 9 | * HTTP/HTTPS/SOCKS5 proxy protocols support (including authentication). 10 | * No special client side software required. Just configure your system to use a proxy. 11 | * Each configured AWS Lambda region provides a large pool of constantly rotating IP address. 12 | * Configurable IP rotation frequency between multiple regions. 13 | * Mostly [AWS free tier](https://aws.amazon.com/free/) compatible (see FAQ below). 14 | 15 | ## Project status 16 | Current code status: proof of concept. This is the first Go application that I've ever written. It has no tests. It may not work. It may blow up. Use at your own risk. 17 | 18 | ## How it works 19 | At a high level, awslambdaproxy proxies TCP/UDP traffic through AWS Lambda regional endpoints. To do this, awslambdaproxy is setup on a publicly accessible host (e.g. EC2 instance) and it handles creating Lambda resources that run a proxy server ([gost](https://github.com/ginuerzh/gost)). Since Lambda does not allow you to connect to bound ports in executing functions, a reverse SSH tunnel is established from the Lambda function to the host running awslambdaproxy. Once a tunnel connection is established, all user traffic is forwarded through this reverse tunnel to the proxy server. Lambda functions have a max execution time of 15 minutes, so there is a goroutine that continuously executes Lambda functions to ensure there is always a live tunnel in place. If multiple regions are specified, user traffic will be routed in a round robin fashion across these regions. 20 | 21 | ![](/assets/images/how-it-works.png?raw=true) 22 | 23 | ## Installation 24 | - [Terraform](#terraform) 25 | - [Manual](#manual) 26 | 27 | ## Terraform 28 | 29 | 1. Clone repository and go to the `deployment/terraform` directory: 30 | ```sh 31 | git clone git@github.com:dan-v/awslambdaproxy.git && cd awslambdaproxy/deployment/terraform 32 | ``` 33 | 34 | 2. Install [Terraform](https://www.terraform.io/) and configure your Terraform backend. Read more about Terraform backends [here](https://www.terraform.io/docs/backends/index.html). 35 | 36 | 3. Create and fill in a variable definitions file ([read more here](https://www.terraform.io/docs/configuration/variables.html#variable-definitions-tfvars-files)) if you don't want to use default variables values defined in `variables.tf`. 37 | 38 | 4. Run these commands to init and apply configuration: 39 | ```sh 40 | terraform init && terraform apply -auto-approve 41 | ``` 42 | 43 | It will create all dependent resources and run awslambdaproxy inside a Docker container. EC2 instance SSH key can be found in AWS Secret Manager in your [AWS Management Console](https://console.aws.amazon.com/). 44 | 45 | NOTE: Some AWS regions have a big list of IP CIDR blocks and they can exceed the default limits of security groups ([read more](https://docs.aws.amazon.com/vpc/latest/userguide/amazon-vpc-limits.html#vpc-limits-security-groups)). In that case, you'll need to make a limit increase request through the `AWS Support Center` by choosing `Create Case` and then choosing `Service Limit Increase` to prevent deployment issues. 46 | 47 | ## Manual 48 | 49 | 1. Download a pre-built binary from the [GitHub Releases](https://github.com/dan-v/awslambdaproxy/releases) page. 50 | 51 | 2. Copy `awslambdaproxy` binary to a publicly accessible linux host (e.g. EC2 instance, VPS instance, etc). You will need to open the following ports on this host: 52 | * Port 22 - functions executing in AWS Lambda will open SSH connections back to the host running `awslambdaproxy`, so this port needs to be open to the world. The SSH key used here is dynamically generated at startup and added to the running users authorized_keys file. 53 | * Port 8080 - the default configuration will start a HTTP/SOCKS proxy listener on this port with default user/password authentication. If you don't want to publicly expose the proxy server, one option is to setup your own VPN server (e.g. [dosxvpn](https://github.com/dan-v/dosxvpn) or [algo](https://github.com/trailofbits/algo)), connect to it, and just run awslambdaproxy with the proxy listener only on localhost (-l localhost:8080). 54 | 55 | 3. Optional, but I'd highly recommend taking a look at the Minimal IAM Policies section below. This will allow you to setup minimal permissions required to setup and run the project. Otherwise, if you don't care about security you can always use an access key with full administrator privileges. 56 | 57 | 4. `awslambdaproxy` will need access to credentials for AWS in some form. This can be either through exporting environment variables (as shown below), shared credential file, or an IAM role if assigned to the instance you are running it on. See [this](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) for more details. 58 | 59 | ```sh 60 | export AWS_ACCESS_KEY_ID=XXXXXXXXXX 61 | export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYY 62 | ``` 63 | 5. Run `awslambdaproxy setup`. 64 | 65 | ```sh 66 | ./awslambdaproxy setup 67 | ``` 68 | 69 | 6. Run `awslambdaproxy run`. 70 | 71 | ```sh 72 | ./awslambdaproxy run -r us-west-2,us-west-1,us-east-1,us-east-2 73 | ``` 74 | 75 | 7. Configure your web browser (or OS) to use the HTTP/SOCKS5 proxy on the publicly accessible host running `awslambdaproxy` on port 8080. 76 | 77 | ### Minimal IAM Policies 78 | * This assumes you have the AWS CLI setup with an admin user 79 | * Create a user with proper permissions needed to run the setup command. This user can be removed after running the setup command. 80 | ``` 81 | aws iam create-user --user-name awslambdaproxy-setup 82 | aws iam put-user-policy --user-name awslambdaproxy-setup --policy-name awslambdaproxy-setup --policy-document file://deployment/iam/setup.json 83 | aws iam create-access-key --user-name awslambdaproxy-setup 84 | { 85 | "AccessKey": { 86 | "UserName": "awslambdaproxy-setup", 87 | "Status": "Active", 88 | "CreateDate": "2017-04-17T06:15:18.858Z", 89 | "SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx", 90 | "AccessKeyId": "xxxxxxxxxxxxxx" 91 | } 92 | } 93 | ``` 94 | * Create a user with proper permission needed to run the proxy. 95 | ``` 96 | aws iam create-user --user-name awslambdaproxy-run 97 | aws iam put-user-policy --user-name awslambdaproxy-run --policy-name awslambdaproxy-run --policy-document file://deployment/iam/run.json 98 | aws iam create-access-key --user-name awslambdaproxy-run 99 | { 100 | "AccessKey": { 101 | "UserName": "awslambdaproxy-run", 102 | "Status": "Active", 103 | "CreateDate": "2017-04-17T06:18:27.531Z", 104 | "SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx", 105 | "AccessKeyId": "xxxxxxxxxxxxxx" 106 | } 107 | } 108 | ``` 109 | 110 | ## Examples 111 | ``` 112 | # execute proxy in four different regions with rotation happening every 60 seconds 113 | ./awslambdaproxy run -r us-west-2,us-west-1,us-east-1,us-east-2 -f 60s 114 | 115 | # choose a different port and username/password for proxy and add another listener on localhost with no auth 116 | ./awslambdaproxy run -l "admin:admin@:8888,localhost:9090" 117 | 118 | # bypass certain domains from using lambda proxy 119 | ./awslambdaproxy run -b "*.websocket.org,*.youtube.com" 120 | 121 | # specify a dns server for the proxy server to use for dns lookups 122 | ./awslambdaproxy run -l "admin:awslambdaproxy@:8080?dns=1.1.1.1" 123 | 124 | # increase function memory size for better network performance 125 | ./awslambdaproxy run -m 512 126 | ``` 127 | 128 | ## FAQ 129 | 1. Should I use awslambdaproxy? That's up to you. Use at your own risk. 130 | 2. Why did you use AWS Lambda for this? The primary reason for using AWS Lambda in this project is the vast pool of IP addresses available that automatically rotate. 131 | 3. How big is the pool of available IP addresses? This I don't know, but I do know I did not have a duplicate IP while running the proxy for a week. 132 | 4. Will this make me completely anonymous? No, absolutely not. The goal of this project is just to obfuscate your web traffic by rotating your IP address. All of your traffic is going through AWS which could be traced back to your account. You can also be tracked still with [browser fingerprinting](https://panopticlick.eff.org/), etc. Your [IP address may still leak](https://ipleak.net/) due to WebRTC, Flash, etc. 133 | 5. How often will my external IP address change? I'm not positive as that's specific to the internals of AWS Lambda, and that can change at any time. However, I'll give an example, with 4 regions specified rotating every 5 minutes it resulted in around 15 unique IPs per hour. 134 | 6. How much does this cost? awslambdaproxy should be able to run mostly on the [AWS free tier](https://aws.amazon.com/free/) minus bandwidth costs. It can run on a t2.micro instance and the default 128MB Lambda function that is constantly running should also fall in the free tier usage. The bandwidth is what will cost you money; you will pay for bandwidth usage for both EC2 and Lambda. 135 | 7. Why does my connection drop periodically? AWS Lambda functions can currently only execute for a maximum of 15 minutes. In order to maintain an ongoing proxy a new function is executed and all new traffic is cut over to it. Any ongoing connections to the previous Lambda function will hard stop after a timeout period. You generally won't see any issues for normal web browsing as connections are very short lived, but for any long lived connections you will see issues. Consider using the `--bypass` flag to specify known domains that you know use persistent connections to avoid having your connection constantly dropping for these. 136 | 137 | # Contributors 138 | * [yurymkomarov](https://github.com/yurymkomarov) - streamlined the entire deployment process with Terraform. 139 | * [unixfox](https://github.com/unixfox) - contributed the Docker image for awslambdaproxy. 140 | 141 | # Powered by 142 | * [gost](https://github.com/ginuerzh/gost) - A simple security tunnel written in Golang. 143 | * [yamux](https://github.com/hashicorp/yamux) - Golang connection multiplexing library. 144 | * [goad](https://github.com/goadapp/goad) - Code was borrowed from this project to handle AWS Lambda zip creation and function upload. 145 | 146 | ## Build From Source 147 | 1. Install [Go](https://golang.org/dl/) and [go-bindata](https://github.com/go-bindata/go-bindata) 148 | 149 | 2. Fetch the project with `git clone`: 150 | 151 | ```sh 152 | git clone git@github.com:dan-v/awslambdaproxy.git && cd awslambdaproxy 153 | ``` 154 | 155 | 3. Run make to build awslambdaproxy. You'll find your `awslambdaproxy` binary in the `artifacts` folder. 156 | 157 | ```sh 158 | make 159 | ``` 160 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.14 2 | -------------------------------------------------------------------------------- /assets/images/how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dan-v/awslambdaproxy/f07f95f8133d4f88b1c20f87299ef610d0a15bdf/assets/images/how-it-works.png -------------------------------------------------------------------------------- /assets/images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dan-v/awslambdaproxy/f07f95f8133d4f88b1c20f87299ef610d0a15bdf/assets/images/overview.gif -------------------------------------------------------------------------------- /build/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | awslambdaproxy: 4 | environment: 5 | AWS_ACCESS_KEY_ID: "" 6 | AWS_SECRET_ACCESS_KEY: "" 7 | image: vdan/awslambdaproxy:latest 8 | ports: 9 | - 2222:2222 10 | - 8080:8080 -------------------------------------------------------------------------------- /build/docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == "setup" ]; then 4 | # ask for credentials to setup as this should be a different key with elevated permissions 5 | read -p 'Enter AWS_ACCESS_KEY_ID: ' AWS_ACCESS_KEY_ID 6 | read -sp 'Enter AWS_SECRET_ACCESS_KEY: ' AWS_SECRET_ACCESS_KEY 7 | export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID 8 | export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY 9 | /app/awslambdaproxy setup 10 | exit 0 11 | fi 12 | 13 | # if docker secret has been provided for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY use it 14 | if [[ -f /run/secrets/AWS_ACCESS_KEY_ID && -f /run/secrets/AWS_SECRET_ACCESS_KEY ]]; 15 | then 16 | export AWS_ACCESS_KEY_ID=$(cat /run/secrets/AWS_ACCESS_KEY_ID) 17 | export AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/AWS_SECRET_ACCESS_KEY) 18 | fi 19 | 20 | # if still don't have keys, exit with error 21 | if [ -z "${AWS_ACCESS_KEY_ID}" ]; then 22 | echo "Need to provide AWS_ACCESS_KEY_ID as secret or environment variable" 23 | exit 1 24 | fi 25 | if [ -z "${AWS_SECRET_ACCESS_KEY}" ]; then 26 | echo "Need to provide AWS_SECRET_ACCESS_KEY as secret or environment variable" 27 | exit 1 28 | fi 29 | 30 | # setup ssh 31 | mkdir -p /tmp/etc/ssh 32 | ssh-keygen -A -f /tmp 33 | /usr/sbin/sshd 34 | 35 | # run by default and pass any supplied arguments 36 | /app/awslambdaproxy run $@ -------------------------------------------------------------------------------- /build/docker/sshd_config: -------------------------------------------------------------------------------- 1 | # $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $ 2 | 3 | # This is the sshd server system-wide configuration file. See 4 | # sshd_config(5) for more information. 5 | 6 | # This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin 7 | 8 | # The strategy used for options in the default sshd_config shipped with 9 | # OpenSSH is to specify options with their default value where 10 | # possible, but leave them commented. Uncommented options override the 11 | # default value. 12 | 13 | Port 2222 14 | #AddressFamily any 15 | #ListenAddress 0.0.0.0 16 | #ListenAddress :: 17 | 18 | HostKey /tmp/etc/ssh/ssh_host_rsa_key 19 | HostKey /tmp/etc/ssh/ssh_host_ecdsa_key 20 | HostKey /tmp/etc/ssh/ssh_host_ed25519_key 21 | 22 | # Ciphers and keying 23 | #RekeyLimit default none 24 | 25 | # Logging 26 | #SyslogFacility AUTH 27 | #LogLevel INFO 28 | 29 | #LoginGraceTime 2m 30 | PermitRootLogin no 31 | #StrictModes yes 32 | #MaxAuthTries 6 33 | #MaxSessions 10 34 | 35 | #PubkeyAuthentication yes 36 | 37 | # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 38 | # but this is overridden so installations will only check .ssh/authorized_keys 39 | AuthorizedKeysFile .ssh/authorized_keys 40 | 41 | AuthenticationMethods publickey 42 | 43 | #AuthorizedPrincipalsFile none 44 | 45 | #AuthorizedKeysCommand none 46 | #AuthorizedKeysCommandUser nobody 47 | 48 | # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts 49 | #HostbasedAuthentication no 50 | # Change to yes if you don't trust ~/.ssh/known_hosts for 51 | # HostbasedAuthentication 52 | #IgnoreUserKnownHosts no 53 | # Don't read the user's ~/.rhosts and ~/.shosts files 54 | #IgnoreRhosts yes 55 | 56 | # To disable tunneled clear text passwords, change to no here! 57 | PasswordAuthentication no 58 | PermitEmptyPasswords no 59 | 60 | # Change to no to disable s/key passwords 61 | #ChallengeResponseAuthentication yes 62 | 63 | # Kerberos options 64 | #KerberosAuthentication no 65 | #KerberosOrLocalPasswd yes 66 | #KerberosTicketCleanup yes 67 | #KerberosGetAFSToken no 68 | 69 | # GSSAPI options 70 | #GSSAPIAuthentication no 71 | #GSSAPICleanupCredentials yes 72 | 73 | # Set this to 'yes' to enable PAM authentication, account processing, 74 | # and session processing. If this is enabled, PAM authentication will 75 | # be allowed through the ChallengeResponseAuthentication and 76 | # PasswordAuthentication. Depending on your PAM configuration, 77 | # PAM authentication via ChallengeResponseAuthentication may bypass 78 | # the setting of "PermitRootLogin without-password". 79 | # If you just want the PAM account and session checks to run without 80 | # PAM authentication, then enable this but set PasswordAuthentication 81 | # and ChallengeResponseAuthentication to 'no'. 82 | #UsePAM no 83 | 84 | #AllowAgentForwarding yes 85 | # Feel free to re-enable these if your use case requires them. 86 | AllowTcpForwarding yes 87 | PermitOpen any 88 | GatewayPorts no 89 | X11Forwarding no 90 | #X11DisplayOffset 10 91 | #X11UseLocalhost yes 92 | #PermitTTY yes 93 | #PrintMotd yes 94 | #PrintLastLog yes 95 | #TCPKeepAlive yes 96 | #PermitUserEnvironment no 97 | #Compression delayed 98 | #ClientAliveInterval 0 99 | #ClientAliveCountMax 3 100 | #UseDNS no 101 | #PidFile /run/sshd.pid 102 | #MaxStartups 10:30:100 103 | #PermitTunnel no 104 | #ChrootDirectory none 105 | #VersionAddendum none 106 | 107 | # no default banner path 108 | #Banner none 109 | 110 | # override default of no subsystems 111 | Subsystem sftp /usr/lib/ssh/sftp-server 112 | 113 | # Example of overriding settings on a per-user basis 114 | #Match User anoncvs 115 | # X11Forwarding no 116 | # AllowTcpForwarding no 117 | # PermitTTY no 118 | # ForceCommand cvs server -------------------------------------------------------------------------------- /cmd/awslambdaproxy/root.go: -------------------------------------------------------------------------------- 1 | package awslambdaproxy 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var ( 13 | version string 14 | cfgFile string 15 | ) 16 | 17 | // RootCmd represents the base command when called without any subcommands 18 | var RootCmd = &cobra.Command{ 19 | Use: "awslambdaproxy", 20 | Short: "An AWS Lambda powered HTTP/SOCKS web proxy", 21 | Version: version, 22 | Long: `awslambdaproxy is an AWS Lambda powered HTTP/SOCKS web proxy. 23 | It provides a constantly rotating IP address for your network traffic 24 | from all regions where AWS Lambda is available. The goal is to obfuscate 25 | your connections and make it harder to track you as a user.`, 26 | } 27 | 28 | // Execute adds all child commands to the root command sets flags appropriately. 29 | // This is called by main.main(). It only needs to happen once to the rootCmd. 30 | func Execute() { 31 | if err := RootCmd.Execute(); err != nil { 32 | fmt.Println(err) 33 | os.Exit(-1) 34 | } 35 | } 36 | 37 | func init() { 38 | cobra.OnInitialize(initConfig) 39 | } 40 | 41 | // initConfig reads in config file and ENV variables if set. 42 | func initConfig() { 43 | if cfgFile != "" { // enable ability to specify config file via flag 44 | viper.SetConfigFile(cfgFile) 45 | } 46 | 47 | var replacer = strings.NewReplacer("-", "_", ".", "_") 48 | viper.SetConfigName(".awslambdaproxy") // name of config file (without extension) 49 | viper.AddConfigPath("$HOME") // adding home directory as first search path 50 | viper.AutomaticEnv() // read in environment variables that match 51 | viper.SetEnvKeyReplacer(replacer) 52 | 53 | // If a config file is found, read it in. 54 | if err := viper.ReadInConfig(); err == nil { 55 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cmd/awslambdaproxy/run.go: -------------------------------------------------------------------------------- 1 | package awslambdaproxy 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/user" 7 | "strings" 8 | "time" 9 | 10 | "github.com/dan-v/awslambdaproxy/pkg/server" 11 | "github.com/spf13/cobra" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | var ( 16 | frequency time.Duration 17 | memory int 18 | debug, debugProxy bool 19 | lambdaName, lambdaIamRole, sshUser, sshPort, regions, listeners, bypass string 20 | ) 21 | 22 | // runCmd represents the run command 23 | var runCmd = &cobra.Command{ 24 | Use: "run", 25 | Short: "run awslambdaproxy", 26 | Long: `this will execute awslambdaproxy in the specified regions. examples: 27 | 28 | # execute proxy in four different regions with rotation happening every 60 seconds 29 | ./awslambdaproxy run -r us-west-2,us-west-1,us-east-1,us-east-2 -f 60s 30 | 31 | # choose a different port and username/password for proxy and add another listener on localhost with no auth 32 | ./awslambdaproxy run -l "admin:admin@:8888,localhost:9090" 33 | 34 | # bypass certain domains from using lambda proxy 35 | ./awslambdaproxy run -b "*.websocket.org,*.youtube.com" 36 | 37 | # specify a dns server for the proxy server to use for dns lookups 38 | ./awslambdaproxy run -l "admin:awslambdaproxy@:8080?dns=1.1.1.1" 39 | 40 | # increase function memory size for better network performance 41 | ./awslambdaproxy run -m 512 42 | `, 43 | Run: func(cmd *cobra.Command, args []string) { 44 | aDebug := viper.GetBool("debug") 45 | aDebugProxy := viper.GetBool("debug-proxy") 46 | aSSHUser := viper.GetString("ssh-user") 47 | aSSHPort := viper.GetString("ssh-port") 48 | aRegions := strings.Split(viper.GetString("regions"), ",") 49 | aMemory := viper.GetInt("memory") 50 | aFrequency := viper.GetDuration("frequency") 51 | aListeners := strings.Split(viper.GetString("listeners"), ",") 52 | aBypass := viper.GetString("bypass") 53 | aLambdaName := viper.GetString("lambda-name") 54 | aLambdaIamRoleName := viper.GetString("lambda-iam-role-name") 55 | 56 | if _, err := server.GetSessionAWS(); err != nil { 57 | log.Fatal("unable to find valid aws credentials") 58 | } 59 | 60 | s, err := server.New(server.Config{ 61 | LambdaName: aLambdaName, 62 | LambdaIamRoleName: aLambdaIamRoleName, 63 | LambdaRegions: aRegions, 64 | LambdaMemory: aMemory, 65 | LambdaExecutionFrequency: aFrequency, 66 | ProxyListeners: aListeners, 67 | ProxyDebug: aDebugProxy, 68 | ReverseTunnelSSHUser: aSSHUser, 69 | ReverseTunnelSSHPort: aSSHPort, 70 | Bypass: aBypass, 71 | Debug: aDebug, 72 | }) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | s.Run() 77 | }, 78 | } 79 | 80 | func getCurrentUserName() string { 81 | u, _ := user.Current() 82 | if u != nil { 83 | return u.Username 84 | } 85 | return "" 86 | } 87 | 88 | func init() { 89 | RootCmd.AddCommand(runCmd) 90 | 91 | runCmd.Flags().StringVarP(&lambdaName, "lambda-name", "n", "awslambdaproxy", 92 | fmt.Sprintf("name of lambda function")) 93 | runCmd.Flags().StringVarP(&lambdaIamRole, "lambda-iam-role-name", "i", "awslambdaproxy-role", 94 | fmt.Sprintf("name of lambda function")) 95 | runCmd.Flags().StringVarP(®ions, "regions", "r", "us-west-2", 96 | fmt.Sprintf("comma separted list of regions to run proxy (e.g. us-west-2,us-west-1,us-east-1). "+ 97 | "valid regions include %v", server.GetValidLambdaRegions())) 98 | runCmd.Flags().DurationVarP(&frequency, "frequency", "f", server.LambdaMaxExecutionFrequency, 99 | fmt.Sprintf("frequency to execute Lambda function. minimum is %v and maximum is %v. "+ 100 | "if multiple regions are specified, this will cause traffic to rotate round robin at the interval "+ 101 | "specified here", server.LambdaMinExecutionFrequency.String(), server.LambdaMaxExecutionFrequency.String())) 102 | runCmd.Flags().IntVarP(&memory, "memory", "m", 128, 103 | fmt.Sprintf("memory size in MB for lambda function. minimum is %v and maximum is %v. "+ 104 | "higher memory size may allow for faster network throughput.", 105 | server.LambdaMinMemorySize, server.LambdaMaxMemorySize)) 106 | runCmd.Flags().StringVarP(&listeners, "listeners", "l", "admin:awslambdaproxy@:8080", 107 | "defines the listening port and authentication details in form [scheme://][user:pass@host]:port. "+ 108 | "add as many listeners as you'd like. see documentation for gost for more details "+ 109 | "https://github.com/ginuerzh/gost/blob/master/README_en.md#getting-started.") 110 | runCmd.Flags().StringVarP(&sshUser, "ssh-user", "", getCurrentUserName(), 111 | "ssh user for tunnel connections from lambda.") 112 | runCmd.Flags().StringVarP(&sshPort, "ssh-port", "", "22", 113 | "ssh port for tunnel connections from lambda.") 114 | runCmd.Flags().BoolVar(&debugProxy, "debug-proxy", false, 115 | "enable debug logging for proxy (note: this will log your visited domains)") 116 | runCmd.Flags().BoolVar(&debug, "debug", false, 117 | "enable general debug logging") 118 | runCmd.Flags().StringVarP(&bypass, "bypass", "b", "", 119 | "comma separated list of domains/ips to bypass lambda proxy (e.g. *.websocket.org,*.youtube.com). "+ 120 | "note that when using sock5 proxy mode you'll need to be remotely resolving dns for this to work.") 121 | 122 | viper.BindPFlag("lambda-name", runCmd.Flags().Lookup("lambda-name")) 123 | viper.BindPFlag("lambda-iam-role-name", runCmd.Flags().Lookup("lambda-iam-role-name")) 124 | viper.BindPFlag("regions", runCmd.Flags().Lookup("regions")) 125 | viper.BindPFlag("frequency", runCmd.Flags().Lookup("frequency")) 126 | viper.BindPFlag("memory", runCmd.Flags().Lookup("memory")) 127 | viper.BindPFlag("ssh-user", runCmd.Flags().Lookup("ssh-user")) 128 | viper.BindPFlag("ssh-port", runCmd.Flags().Lookup("ssh-port")) 129 | viper.BindPFlag("listeners", runCmd.Flags().Lookup("listeners")) 130 | viper.BindPFlag("debug-proxy", runCmd.Flags().Lookup("debug-proxy")) 131 | viper.BindPFlag("debug", runCmd.Flags().Lookup("debug")) 132 | viper.BindPFlag("bypass", runCmd.Flags().Lookup("bypass")) 133 | } 134 | -------------------------------------------------------------------------------- /cmd/awslambdaproxy/setup.go: -------------------------------------------------------------------------------- 1 | package awslambdaproxy 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/dan-v/awslambdaproxy/pkg/server" 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | // setupCmd represents the setup command 12 | var setupCmd = &cobra.Command{ 13 | Use: "setup", 14 | Short: "setup awslambdaproxy aws infrastructure", 15 | Long: `this will setup all required aws infrastructure to run awslambdaproxy.`, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | 18 | aLambdaIamRoleName := viper.GetString("lambda-iam-role-name") 19 | 20 | if _, err := server.GetSessionAWS(); err != nil { 21 | log.Fatal("unable to find valid aws credentials") 22 | } 23 | 24 | err := server.SetupLambdaInfrastructure(aLambdaIamRoleName) 25 | if err != nil { 26 | log.Fatal("failed to run setup for awslambdaproxy: ", err) 27 | } 28 | }, 29 | } 30 | 31 | func init() { 32 | RootCmd.AddCommand(setupCmd) 33 | } 34 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dan-v/awslambdaproxy/cmd/awslambdaproxy" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | if err := awslambdaproxy.RootCmd.Execute(); err != nil { 11 | fmt.Println(err) 12 | os.Exit(-1) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /deployment/iam/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "lambda:*" 8 | ], 9 | "Resource": [ 10 | "arn:aws:lambda:*:*:function:awslambdaproxy" 11 | ] 12 | }, 13 | { 14 | "Effect": "Allow", 15 | "Action": [ 16 | "iam:GetRole", 17 | "iam:PassRole" 18 | ], 19 | "Resource": [ 20 | "arn:aws:iam::*:role/awslambdaproxy-role" 21 | ] 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /deployment/iam/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "lambda:*" 8 | ], 9 | "Resource": [ 10 | "arn:aws:lambda:*:*:function:awslambdaproxy" 11 | ] 12 | }, 13 | { 14 | "Effect": "Allow", 15 | "Action": [ 16 | "iam:GetRole", 17 | "iam:PassRole", 18 | "iam:PutRolePolicy", 19 | "iam:CreateRole" 20 | ], 21 | "Resource": [ 22 | "arn:aws:iam::*:role/awslambdaproxy-role" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /deployment/terraform/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_ami" "this" { 2 | most_recent = true 3 | owners = ["amazon"] 4 | 5 | filter { 6 | name = "name" 7 | values = ["amzn2-ami-hvm*"] 8 | } 9 | } 10 | 11 | data "aws_availability_zones" "this" { 12 | state = "available" 13 | } 14 | 15 | data "aws_vpc" "default" { 16 | default = true 17 | } 18 | 19 | data "aws_subnet_ids" "default" { 20 | vpc_id = data.aws_vpc.default.id 21 | } 22 | 23 | data "aws_subnet" "default" { 24 | id = element(tolist(data.aws_subnet_ids.default.ids), 0) 25 | } 26 | 27 | data "aws_security_group" "default" { 28 | vpc_id = data.aws_vpc.default.id 29 | 30 | filter { 31 | name = "group-name" 32 | values = ["default"] 33 | } 34 | } 35 | 36 | data "aws_iam_policy_document" "role" { 37 | statement { 38 | actions = ["sts:AssumeRole"] 39 | effect = "Allow" 40 | 41 | principals { 42 | identifiers = ["vpc-flow-logs.amazonaws.com"] 43 | type = "Service" 44 | } 45 | } 46 | } 47 | 48 | data "aws_iam_policy_document" "role_policy_cloudwatch" { 49 | statement { 50 | actions = [ 51 | "logs:CreateLogGroup", 52 | "logs:CreateLogStream", 53 | "logs:PutLogEvents", 54 | "logs:DescribeLogGroups", 55 | "logs:DescribeLogStreams" 56 | ] 57 | effect = "Allow" 58 | resources = ["*"] 59 | } 60 | } 61 | 62 | data "aws_iam_policy_document" "profile_sts" { 63 | statement { 64 | effect = "Allow" 65 | actions = ["sts:AssumeRole"] 66 | 67 | principals { 68 | identifiers = ["ec2.amazonaws.com"] 69 | type = "Service" 70 | } 71 | } 72 | } 73 | 74 | data "aws_iam_policy_document" "lambda_sts" { 75 | statement { 76 | effect = "Allow" 77 | actions = ["sts:AssumeRole"] 78 | 79 | principals { 80 | identifiers = ["lambda.amazonaws.com"] 81 | type = "Service" 82 | } 83 | } 84 | } 85 | 86 | data "aws_iam_policy_document" "lambda" { 87 | statement { 88 | effect = "Allow" 89 | actions = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"] 90 | resources = ["arn:aws:logs:*:*:*"] 91 | } 92 | } 93 | 94 | data "aws_iam_policy_document" "user" { 95 | statement { 96 | effect = "Allow" 97 | actions = ["lambda:*"] 98 | resources = ["arn:aws:lambda:*:*:function:${var.name}-*"] 99 | } 100 | 101 | statement { 102 | effect = "Allow" 103 | actions = ["iam:GetRole", "iam:PassRole"] 104 | resources = ["arn:aws:iam::*:role/${var.name}-*"] 105 | } 106 | } 107 | 108 | data "http" "current_ip" { 109 | url = "http://ipv4.icanhazip.com" 110 | } 111 | 112 | data "aws_ip_ranges" "lambda" { 113 | for_each = toset(var.lambda_regions) 114 | 115 | regions = [each.value] 116 | services = ["ec2"] 117 | } 118 | -------------------------------------------------------------------------------- /deployment/terraform/dummy.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dan-v/awslambdaproxy/f07f95f8133d4f88b1c20f87299ef610d0a15bdf/deployment/terraform/dummy.zip -------------------------------------------------------------------------------- /deployment/terraform/lambda.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lambda_function" "ap-northeast-1" { 2 | count = contains(var.lambda_regions, "ap-northeast-1") ? 1 : 0 3 | provider = aws.ap-northeast-1 4 | 5 | filename = "${path.module}/dummy.zip" 6 | function_name = "${var.name}-${random_id.this.hex}" 7 | handler = "main" 8 | role = aws_iam_role.lambda.arn 9 | runtime = "go1.x" 10 | 11 | tags = { 12 | Name = var.name 13 | Workspace = terraform.workspace 14 | } 15 | 16 | lifecycle { 17 | ignore_changes = [timeout] 18 | } 19 | } 20 | 21 | resource "aws_lambda_function" "ap-northeast-2" { 22 | count = contains(var.lambda_regions, "ap-northeast-2") ? 1 : 0 23 | provider = aws.ap-northeast-2 24 | 25 | filename = "${path.module}/dummy.zip" 26 | function_name = "${var.name}-${random_id.this.hex}" 27 | handler = "main" 28 | role = aws_iam_role.lambda.arn 29 | runtime = "go1.x" 30 | 31 | tags = { 32 | Name = var.name 33 | Workspace = terraform.workspace 34 | } 35 | 36 | lifecycle { 37 | ignore_changes = [timeout] 38 | } 39 | } 40 | 41 | resource "aws_lambda_function" "ap-south-1" { 42 | count = contains(var.lambda_regions, "ap-south-1") ? 1 : 0 43 | provider = aws.ap-south-1 44 | 45 | filename = "${path.module}/dummy.zip" 46 | function_name = "${var.name}-${random_id.this.hex}" 47 | handler = "main" 48 | role = aws_iam_role.lambda.arn 49 | runtime = "go1.x" 50 | 51 | tags = { 52 | Name = var.name 53 | Workspace = terraform.workspace 54 | } 55 | 56 | lifecycle { 57 | ignore_changes = [timeout] 58 | } 59 | } 60 | 61 | resource "aws_lambda_function" "ap-southeast-1" { 62 | count = contains(var.lambda_regions, "ap-southeast-1") ? 1 : 0 63 | provider = aws.ap-southeast-1 64 | 65 | filename = "${path.module}/dummy.zip" 66 | function_name = "${var.name}-${random_id.this.hex}" 67 | handler = "main" 68 | role = aws_iam_role.lambda.arn 69 | runtime = "go1.x" 70 | 71 | tags = { 72 | Name = var.name 73 | Workspace = terraform.workspace 74 | } 75 | 76 | lifecycle { 77 | ignore_changes = [timeout] 78 | } 79 | } 80 | 81 | resource "aws_lambda_function" "ap-southeast-2" { 82 | count = contains(var.lambda_regions, "ap-southeast-2") ? 1 : 0 83 | provider = aws.ap-southeast-2 84 | 85 | filename = "${path.module}/dummy.zip" 86 | function_name = "${var.name}-${random_id.this.hex}" 87 | handler = "main" 88 | role = aws_iam_role.lambda.arn 89 | runtime = "go1.x" 90 | 91 | tags = { 92 | Name = var.name 93 | Workspace = terraform.workspace 94 | } 95 | 96 | lifecycle { 97 | ignore_changes = [timeout] 98 | } 99 | } 100 | 101 | resource "aws_lambda_function" "ca-central-1" { 102 | count = contains(var.lambda_regions, "ca-central-1") ? 1 : 0 103 | provider = aws.ca-central-1 104 | 105 | filename = "${path.module}/dummy.zip" 106 | function_name = "${var.name}-${random_id.this.hex}" 107 | handler = "main" 108 | role = aws_iam_role.lambda.arn 109 | runtime = "go1.x" 110 | 111 | tags = { 112 | Name = var.name 113 | Workspace = terraform.workspace 114 | } 115 | 116 | lifecycle { 117 | ignore_changes = [timeout] 118 | } 119 | } 120 | 121 | resource "aws_lambda_function" "eu-central-1" { 122 | count = contains(var.lambda_regions, "eu-central-1") ? 1 : 0 123 | provider = aws.eu-central-1 124 | 125 | filename = "${path.module}/dummy.zip" 126 | function_name = "${var.name}-${random_id.this.hex}" 127 | handler = "main" 128 | role = aws_iam_role.lambda.arn 129 | runtime = "go1.x" 130 | 131 | tags = { 132 | Name = var.name 133 | Workspace = terraform.workspace 134 | } 135 | 136 | lifecycle { 137 | ignore_changes = [timeout] 138 | } 139 | } 140 | 141 | resource "aws_lambda_function" "eu-north-1" { 142 | count = contains(var.lambda_regions, "eu-north-1") ? 1 : 0 143 | provider = aws.eu-north-1 144 | 145 | filename = "${path.module}/dummy.zip" 146 | function_name = "${var.name}-${random_id.this.hex}" 147 | handler = "main" 148 | role = aws_iam_role.lambda.arn 149 | runtime = "go1.x" 150 | 151 | tags = { 152 | Name = var.name 153 | Workspace = terraform.workspace 154 | } 155 | 156 | lifecycle { 157 | ignore_changes = [timeout] 158 | } 159 | } 160 | 161 | resource "aws_lambda_function" "eu-west-1" { 162 | count = contains(var.lambda_regions, "eu-west-1") ? 1 : 0 163 | provider = aws.eu-west-1 164 | 165 | filename = "${path.module}/dummy.zip" 166 | function_name = "${var.name}-${random_id.this.hex}" 167 | handler = "main" 168 | role = aws_iam_role.lambda.arn 169 | runtime = "go1.x" 170 | 171 | tags = { 172 | Name = var.name 173 | Workspace = terraform.workspace 174 | } 175 | 176 | lifecycle { 177 | ignore_changes = [timeout] 178 | } 179 | } 180 | 181 | resource "aws_lambda_function" "eu-west-2" { 182 | count = contains(var.lambda_regions, "eu-west-2") ? 1 : 0 183 | provider = aws.eu-west-2 184 | 185 | filename = "${path.module}/dummy.zip" 186 | function_name = "${var.name}-${random_id.this.hex}" 187 | handler = "main" 188 | role = aws_iam_role.lambda.arn 189 | runtime = "go1.x" 190 | 191 | tags = { 192 | Name = var.name 193 | Workspace = terraform.workspace 194 | } 195 | 196 | lifecycle { 197 | ignore_changes = [timeout] 198 | } 199 | } 200 | 201 | resource "aws_lambda_function" "eu-west-3" { 202 | count = contains(var.lambda_regions, "eu-west-3") ? 1 : 0 203 | provider = aws.eu-west-3 204 | 205 | filename = "${path.module}/dummy.zip" 206 | function_name = "${var.name}-${random_id.this.hex}" 207 | handler = "main" 208 | role = aws_iam_role.lambda.arn 209 | runtime = "go1.x" 210 | 211 | tags = { 212 | Name = var.name 213 | Workspace = terraform.workspace 214 | } 215 | 216 | lifecycle { 217 | ignore_changes = [timeout] 218 | } 219 | } 220 | 221 | resource "aws_lambda_function" "sa-east-1" { 222 | count = contains(var.lambda_regions, "sa-east-1") ? 1 : 0 223 | provider = aws.sa-east-1 224 | 225 | filename = "${path.module}/dummy.zip" 226 | function_name = "${var.name}-${random_id.this.hex}" 227 | handler = "main" 228 | role = aws_iam_role.lambda.arn 229 | runtime = "go1.x" 230 | 231 | tags = { 232 | Name = var.name 233 | Workspace = terraform.workspace 234 | } 235 | 236 | lifecycle { 237 | ignore_changes = [timeout] 238 | } 239 | } 240 | 241 | resource "aws_lambda_function" "us-east-1" { 242 | count = contains(var.lambda_regions, "us-east-1") ? 1 : 0 243 | provider = aws.us-east-1 244 | 245 | filename = "${path.module}/dummy.zip" 246 | function_name = "${var.name}-${random_id.this.hex}" 247 | handler = "main" 248 | role = aws_iam_role.lambda.arn 249 | runtime = "go1.x" 250 | 251 | tags = { 252 | Name = var.name 253 | Workspace = terraform.workspace 254 | } 255 | 256 | lifecycle { 257 | ignore_changes = [timeout] 258 | } 259 | } 260 | 261 | resource "aws_lambda_function" "us-east-2" { 262 | count = contains(var.lambda_regions, "us-east-2") ? 1 : 0 263 | provider = aws.us-east-2 264 | 265 | filename = "${path.module}/dummy.zip" 266 | function_name = "${var.name}-${random_id.this.hex}" 267 | handler = "main" 268 | role = aws_iam_role.lambda.arn 269 | runtime = "go1.x" 270 | 271 | tags = { 272 | Name = var.name 273 | Workspace = terraform.workspace 274 | } 275 | 276 | lifecycle { 277 | ignore_changes = [timeout] 278 | } 279 | } 280 | 281 | resource "aws_lambda_function" "us-west-1" { 282 | count = contains(var.lambda_regions, "us-west-1") ? 1 : 0 283 | provider = aws.us-west-1 284 | 285 | filename = "${path.module}/dummy.zip" 286 | function_name = "${var.name}-${random_id.this.hex}" 287 | handler = "main" 288 | role = aws_iam_role.lambda.arn 289 | runtime = "go1.x" 290 | 291 | tags = { 292 | Name = var.name 293 | Workspace = terraform.workspace 294 | } 295 | 296 | lifecycle { 297 | ignore_changes = [timeout] 298 | } 299 | } 300 | 301 | resource "aws_lambda_function" "us-west-2" { 302 | count = contains(var.lambda_regions, "us-west-2") ? 1 : 0 303 | provider = aws.us-west-2 304 | 305 | filename = "${path.module}/dummy.zip" 306 | function_name = "${var.name}-${random_id.this.hex}" 307 | handler = "main" 308 | role = aws_iam_role.lambda.arn 309 | runtime = "go1.x" 310 | 311 | tags = { 312 | Name = var.name 313 | Workspace = terraform.workspace 314 | } 315 | 316 | lifecycle { 317 | ignore_changes = [timeout] 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /deployment/terraform/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | docker_image = "vdan/awslambdaproxy" 3 | 4 | proxy_credentials = var.proxy_credentials == null ? "${random_string.this.result}:${random_password.this.result}" : var.proxy_credentials 5 | 6 | install_docker = [ 7 | "sudo yum update -y", 8 | "sudo amazon-linux-extras install docker -y", 9 | "sudo service docker start", 10 | "sudo usermod -a -G docker ec2-user", 11 | ] 12 | 13 | start_awslambdaproxy = <<-EOF 14 | docker run --detach --restart=always \ 15 | --name ${var.name} \ 16 | --env AWS_ACCESS_KEY_ID=${aws_iam_access_key.this.id} \ 17 | --env AWS_SECRET_ACCESS_KEY=${aws_iam_access_key.this.secret} \ 18 | --env LAMBDA_NAME="${var.name}-${random_id.this.hex}" \ 19 | --env LAMBDA_IAM_ROLE_NAME="${aws_iam_role.lambda.name}" \ 20 | --env REGIONS=${join(",", var.lambda_regions)} \ 21 | --env FREQUENCY=${var.lambda_frequency} \ 22 | --env SSH_USER=${var.tunnel_ssh_user} \ 23 | --env SSH_PORT=${var.tunnel_ssh_port} \ 24 | --env MEMORY=${var.lambda_memory} \ 25 | --env LISTENERS=${local.proxy_credentials}@:${var.proxy_port}?dns=${var.proxy_dns} \ 26 | --env DEBUG=${var.app_debug} \ 27 | --env DEBUG_PROXY=${var.proxy_debug} \ 28 | --env BYPASS=${join(",", var.proxy_bypass_domains)} \ 29 | --publish ${var.tunnel_ssh_port}:${var.tunnel_ssh_port} \ 30 | --publish ${var.proxy_port}:${var.proxy_port} \ 31 | ${local.docker_image}:${var.app_version} 32 | EOF 33 | 34 | stop_awslambdaproxy = "docker rm -f ${var.name}" 35 | 36 | default_subnet = element(tolist(data.aws_subnet_ids.default.ids), 0) 37 | custom_subnet = try(aws_subnet.public[0].id, "") 38 | } 39 | 40 | resource "random_id" "this" { 41 | byte_length = 1 42 | 43 | keepers = { 44 | cidr_block = var.vpc_cidr_block 45 | } 46 | } 47 | 48 | resource "random_string" "this" { 49 | length = 8 50 | special = false 51 | } 52 | 53 | resource "random_password" "this" { 54 | length = 16 55 | special = false 56 | } 57 | 58 | resource "aws_instance" "this" { 59 | ami = data.aws_ami.this.id 60 | instance_type = var.instance_type 61 | iam_instance_profile = aws_iam_instance_profile.this.name 62 | vpc_security_group_ids = [for i in aws_security_group.this : i.id] 63 | subnet_id = var.create_vpc ? local.custom_subnet : local.default_subnet 64 | key_name = aws_key_pair.ec2.key_name 65 | 66 | provisioner "remote-exec" { 67 | inline = local.install_docker 68 | 69 | connection { 70 | host = aws_instance.this.public_ip 71 | user = "ec2-user" 72 | private_key = tls_private_key.ec2.private_key_pem 73 | } 74 | } 75 | 76 | tags = { 77 | Name = var.name 78 | Workspace = terraform.workspace 79 | } 80 | 81 | lifecycle { 82 | create_before_destroy = true 83 | } 84 | } 85 | 86 | resource "aws_eip" "this" { 87 | vpc = true 88 | 89 | tags = { 90 | Name = var.name 91 | Workspace = terraform.workspace 92 | } 93 | } 94 | 95 | resource "aws_eip_association" "this" { 96 | allocation_id = aws_eip.this.id 97 | instance_id = aws_instance.this.id 98 | } 99 | 100 | resource "null_resource" "start_awslambdaproxy" { 101 | provisioner "remote-exec" { 102 | inline = [local.start_awslambdaproxy] 103 | 104 | connection { 105 | host = aws_eip_association.this.public_ip 106 | user = "ec2-user" 107 | private_key = tls_private_key.ec2.private_key_pem 108 | } 109 | } 110 | } 111 | 112 | resource "null_resource" "restart_awslambdaproxy" { 113 | triggers = { 114 | start_awslambdaproxy = local.start_awslambdaproxy 115 | } 116 | 117 | provisioner "remote-exec" { 118 | inline = [local.stop_awslambdaproxy, local.start_awslambdaproxy] 119 | 120 | connection { 121 | host = aws_eip_association.this.public_ip 122 | user = "ec2-user" 123 | private_key = tls_private_key.ec2.private_key_pem 124 | } 125 | } 126 | 127 | depends_on = [null_resource.start_awslambdaproxy] 128 | } 129 | 130 | resource "tls_private_key" "ec2" { 131 | algorithm = "RSA" 132 | rsa_bits = 4096 133 | } 134 | 135 | resource "aws_key_pair" "ec2" { 136 | key_name = "${var.name}-${random_id.this.hex}" 137 | public_key = tls_private_key.ec2.public_key_openssh 138 | } 139 | 140 | resource "aws_secretsmanager_secret" "this" { 141 | name = "${var.name}-${random_id.this.hex}" 142 | 143 | tags = { 144 | Name = var.name 145 | Workspace = terraform.workspace 146 | } 147 | } 148 | 149 | resource "aws_secretsmanager_secret_version" "this" { 150 | secret_id = aws_secretsmanager_secret.this.id 151 | secret_string = jsonencode({ 152 | public_key = tls_private_key.ec2.public_key_openssh 153 | private_key = tls_private_key.ec2.private_key_pem 154 | }) 155 | } 156 | 157 | resource "aws_security_group" "this" { 158 | for_each = toset(var.lambda_regions) 159 | 160 | name = "${var.name}-${each.value}-${random_id.this.hex}" 161 | vpc_id = var.create_vpc ? try(aws_vpc.this[0].id, "") : data.aws_vpc.default.id 162 | 163 | dynamic "ingress" { 164 | for_each = var.ssh_cidr_blocks 165 | content { 166 | from_port = 22 167 | protocol = "tcp" 168 | to_port = 22 169 | cidr_blocks = [ingress.value] 170 | } 171 | } 172 | 173 | ingress { 174 | from_port = 22 175 | protocol = "tcp" 176 | to_port = 22 177 | cidr_blocks = ["${chomp(data.http.current_ip.body)}/32"] 178 | } 179 | 180 | ingress { 181 | from_port = var.tunnel_ssh_port 182 | protocol = "tcp" 183 | to_port = var.tunnel_ssh_port 184 | cidr_blocks = data.aws_ip_ranges.lambda[each.value].cidr_blocks 185 | } 186 | 187 | ingress { 188 | from_port = var.proxy_port 189 | protocol = "tcp" 190 | to_port = var.proxy_port 191 | cidr_blocks = var.proxy_cidr_blocks 192 | } 193 | 194 | egress { 195 | from_port = 0 196 | protocol = "-1" 197 | to_port = 0 198 | cidr_blocks = ["0.0.0.0/0"] 199 | } 200 | 201 | tags = { 202 | Name = var.name 203 | Workspace = terraform.workspace 204 | } 205 | } 206 | 207 | resource "aws_iam_instance_profile" "this" { 208 | name = "${var.name}-instance-profile-${random_id.this.hex}" 209 | role = aws_iam_role.profile.name 210 | } 211 | 212 | resource "aws_iam_role" "profile" { 213 | name = "${var.name}-profile-${random_id.this.hex}" 214 | assume_role_policy = data.aws_iam_policy_document.profile_sts.json 215 | 216 | tags = { 217 | Name = var.name 218 | Workspace = terraform.workspace 219 | } 220 | } 221 | 222 | resource "aws_iam_role_policy" "profile" { 223 | policy = data.aws_iam_policy_document.user.json 224 | role = aws_iam_role.profile.id 225 | } 226 | 227 | resource "aws_iam_role" "lambda" { 228 | name = "${var.name}-lambda-${random_id.this.hex}" 229 | assume_role_policy = data.aws_iam_policy_document.lambda_sts.json 230 | 231 | tags = { 232 | Name = var.name 233 | Workspace = terraform.workspace 234 | } 235 | } 236 | 237 | resource "aws_iam_role_policy" "lambda" { 238 | policy = data.aws_iam_policy_document.lambda.json 239 | role = aws_iam_role.lambda.id 240 | } 241 | 242 | resource "aws_iam_user" "this" { 243 | name = "${var.name}-user-${random_id.this.hex}" 244 | 245 | tags = { 246 | Name = var.name 247 | Workspace = terraform.workspace 248 | } 249 | } 250 | 251 | resource "aws_iam_user_policy" "this" { 252 | name = "${var.name}-user-${random_id.this.hex}" 253 | user = aws_iam_user.this.name 254 | policy = data.aws_iam_policy_document.user.json 255 | } 256 | 257 | resource "aws_iam_access_key" "this" { 258 | user = aws_iam_user.this.name 259 | } 260 | -------------------------------------------------------------------------------- /deployment/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "proxy_url" { 2 | value = { 3 | http = "http://${local.proxy_credentials}@${aws_eip.this.public_ip}:${var.proxy_port}" 4 | socks5 = "socks5://${local.proxy_credentials}@${aws_eip.this.public_ip}:${var.proxy_port}" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /deployment/terraform/providers.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = var.aws_access_key 3 | secret_key = var.aws_secret_key 4 | region = var.aws_region 5 | } 6 | 7 | provider "aws" { 8 | alias = "ap-northeast-1" 9 | 10 | access_key = var.aws_access_key 11 | secret_key = var.aws_secret_key 12 | region = "ap-northeast-1" 13 | } 14 | 15 | provider "aws" { 16 | alias = "ap-northeast-2" 17 | 18 | access_key = var.aws_access_key 19 | secret_key = var.aws_secret_key 20 | region = "ap-northeast-2" 21 | } 22 | 23 | provider "aws" { 24 | alias = "ap-south-1" 25 | 26 | access_key = var.aws_access_key 27 | secret_key = var.aws_secret_key 28 | region = "ap-south-1" 29 | } 30 | 31 | provider "aws" { 32 | alias = "ap-southeast-1" 33 | 34 | access_key = var.aws_access_key 35 | secret_key = var.aws_secret_key 36 | region = "ap-southeast-1" 37 | } 38 | 39 | provider "aws" { 40 | alias = "ap-southeast-2" 41 | 42 | access_key = var.aws_access_key 43 | secret_key = var.aws_secret_key 44 | region = "ap-southeast-2" 45 | } 46 | 47 | provider "aws" { 48 | alias = "ca-central-1" 49 | 50 | access_key = var.aws_access_key 51 | secret_key = var.aws_secret_key 52 | region = "ca-central-1" 53 | } 54 | 55 | provider "aws" { 56 | alias = "eu-central-1" 57 | 58 | access_key = var.aws_access_key 59 | secret_key = var.aws_secret_key 60 | region = "eu-central-1" 61 | } 62 | 63 | provider "aws" { 64 | alias = "eu-north-1" 65 | 66 | access_key = var.aws_access_key 67 | secret_key = var.aws_secret_key 68 | region = "eu-north-1" 69 | } 70 | 71 | provider "aws" { 72 | alias = "eu-west-1" 73 | 74 | access_key = var.aws_access_key 75 | secret_key = var.aws_secret_key 76 | region = "eu-west-1" 77 | } 78 | 79 | provider "aws" { 80 | alias = "eu-west-2" 81 | 82 | access_key = var.aws_access_key 83 | secret_key = var.aws_secret_key 84 | region = "eu-west-2" 85 | } 86 | 87 | provider "aws" { 88 | alias = "eu-west-3" 89 | 90 | access_key = var.aws_access_key 91 | secret_key = var.aws_secret_key 92 | region = "eu-west-3" 93 | } 94 | 95 | provider "aws" { 96 | alias = "sa-east-1" 97 | 98 | access_key = var.aws_access_key 99 | secret_key = var.aws_secret_key 100 | region = "sa-east-1" 101 | } 102 | 103 | provider "aws" { 104 | alias = "us-east-1" 105 | 106 | access_key = var.aws_access_key 107 | secret_key = var.aws_secret_key 108 | region = "us-east-1" 109 | } 110 | 111 | provider "aws" { 112 | alias = "us-east-2" 113 | 114 | access_key = var.aws_access_key 115 | secret_key = var.aws_secret_key 116 | region = "us-east-2" 117 | } 118 | 119 | provider "aws" { 120 | alias = "us-west-1" 121 | 122 | access_key = var.aws_access_key 123 | secret_key = var.aws_secret_key 124 | region = "us-west-1" 125 | } 126 | 127 | provider "aws" { 128 | alias = "us-west-2" 129 | 130 | access_key = var.aws_access_key 131 | secret_key = var.aws_secret_key 132 | region = "us-west-2" 133 | } 134 | -------------------------------------------------------------------------------- /deployment/terraform/provision.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dan-v/awslambdaproxy/f07f95f8133d4f88b1c20f87299ef610d0a15bdf/deployment/terraform/provision.tf -------------------------------------------------------------------------------- /deployment/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | experiments = [variable_validation] 3 | } 4 | 5 | variable "aws_access_key" { 6 | type = string 7 | description = "AWS access key associated with an IAM user or role" 8 | } 9 | 10 | variable "aws_secret_key" { 11 | type = string 12 | description = "The secret key associated with the access key. This is essentially the 'password' for the access key." 13 | } 14 | 15 | variable "aws_region" { 16 | type = string 17 | description = "AWS Region to send the request to" 18 | } 19 | 20 | variable "name" { 21 | type = string 22 | description = "Name that will be used in resources names and tags" 23 | default = "terraform-aws-lambda-proxy-single-instance" 24 | } 25 | 26 | variable "create_vpc" { 27 | type = bool 28 | description = "Create personal VPC." 29 | default = false 30 | } 31 | 32 | variable "vpc_cidr_block" { 33 | type = string 34 | description = "CIDR block for the VPC." 35 | default = "10.0.0.0/16" 36 | } 37 | 38 | variable "flow_log_enable" { 39 | type = bool 40 | description = "Enable Flow Log for VPC." 41 | default = true 42 | } 43 | 44 | variable "flow_log_destination" { 45 | type = string 46 | description = "Provides a VPC/Subnet/ENI Flow Log to capture IP traffic for a specific network interface, subnet, or VPC." 47 | default = "cloudwatch" 48 | 49 | validation { 50 | condition = contains(["cloudwatch", "s3"], var.flow_log_destination) 51 | error_message = "Logs can be sent only to a CloudWatch Log Group or a S3 Bucket." 52 | } 53 | } 54 | 55 | variable "app_version" { 56 | type = string 57 | description = "AWS Lambda Proxy app version" 58 | default = "latest" 59 | } 60 | 61 | variable "app_debug" { 62 | type = bool 63 | description = "Enable general debug logging" 64 | default = false 65 | } 66 | 67 | variable "instance_type" { 68 | type = string 69 | description = "The instance type of the EC2 instance" 70 | default = "t3.small" 71 | } 72 | 73 | variable "elastic_ip" { 74 | type = bool 75 | description = "Create EIP for instance" 76 | default = true 77 | } 78 | 79 | variable "lambda_regions" { 80 | type = list(string) 81 | description = "The list of AWS regions names where proxy lambda will be deployed" 82 | default = ["ap-northeast-1", "ap-northeast-2", "ap-south-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2"] 83 | } 84 | 85 | variable "lambda_frequency" { 86 | type = string 87 | description = "Frequency to execute Lambda function. If multiple lambda-regions are specified, this will cause traffic to rotate round robin at the interval specified here" 88 | default = "5m" 89 | } 90 | 91 | variable "lambda_memory" { 92 | type = number 93 | description = "Memory size in MB for Lambda function. Higher memory may allow for faster network throughput" 94 | default = 128 95 | } 96 | 97 | variable "proxy_debug" { 98 | type = bool 99 | description = "Enable debug logging for proxy" 100 | default = false 101 | } 102 | 103 | variable "proxy_credentials" { 104 | type = string 105 | description = "Add proxy credentials in format $USERNAME:$PASSWORD" 106 | default = null 107 | } 108 | 109 | variable "proxy_port" { 110 | type = number 111 | description = "Proxy server port" 112 | default = 8080 113 | } 114 | 115 | variable "proxy_dns" { 116 | type = string 117 | description = "Specify a DNS server for the proxy server to use for DNS lookups" 118 | default = "1.1.1.1" 119 | } 120 | 121 | variable "proxy_bypass_domains" { 122 | type = list(string) 123 | description = "Bypass certain domains from using lambda proxy" 124 | default = [] 125 | } 126 | 127 | variable "proxy_cidr_blocks" { 128 | type = list(string) 129 | description = "List of CIDR blocks for proxy access" 130 | default = ["0.0.0.0/0"] 131 | } 132 | 133 | variable "tunnel_ssh_user" { 134 | type = string 135 | description = "SSH user for tunnel connections from Lambda" 136 | default = "" 137 | } 138 | 139 | variable "tunnel_ssh_port" { 140 | type = number 141 | description = "SSH port for tunnel connections from Lambda" 142 | default = 2222 143 | } 144 | 145 | variable "ssh_cidr_blocks" { 146 | type = list(string) 147 | description = "List of CIDR blocks for SSH access" 148 | default = [] 149 | } 150 | -------------------------------------------------------------------------------- /deployment/terraform/vpc.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_flog_log_destination = var.flow_log_destination == "cloudwatch" ? try(aws_cloudwatch_log_group.this[0].arn, "") : try(aws_s3_bucket.this[0].arn, "") 3 | aws_flog_log_iam_role_arn = try(aws_iam_role.this[0].arn, null) 4 | aws_flog_log_destination_type = var.flow_log_destination == "s3" ? "s3" : null 5 | } 6 | 7 | resource "aws_vpc" "this" { 8 | count = var.create_vpc ? 1 : 0 9 | 10 | cidr_block = var.vpc_cidr_block 11 | enable_dns_hostnames = true 12 | enable_dns_support = true 13 | 14 | tags = { 15 | Name = var.name 16 | Module = path.module 17 | Workspace = terraform.workspace 18 | } 19 | 20 | lifecycle { 21 | create_before_destroy = true 22 | } 23 | } 24 | 25 | resource "aws_security_group" "default" { 26 | count = var.create_vpc ? 1 : 0 27 | 28 | name = "${var.name}-default-${random_id.this.hex}" 29 | description = "Default security group for ${var.name}" 30 | vpc_id = aws_vpc.this[0].id 31 | 32 | ingress { 33 | from_port = 0 34 | protocol = "-1" 35 | to_port = 0 36 | cidr_blocks = [var.vpc_cidr_block] 37 | } 38 | 39 | egress { 40 | from_port = 0 41 | protocol = "-1" 42 | to_port = 0 43 | cidr_blocks = [var.vpc_cidr_block] 44 | } 45 | 46 | tags = { 47 | Name = var.name 48 | Module = path.module 49 | Workspace = terraform.workspace 50 | } 51 | 52 | lifecycle { 53 | create_before_destroy = true 54 | } 55 | } 56 | 57 | resource "aws_subnet" "public" { 58 | count = var.create_vpc ? 1 : 0 59 | 60 | availability_zone = data.aws_availability_zones.this.names[count.index] 61 | cidr_block = cidrsubnet(var.vpc_cidr_block, 4, count.index) 62 | map_public_ip_on_launch = true 63 | vpc_id = aws_vpc.this[0].id 64 | 65 | tags = { 66 | Name = var.name 67 | Module = path.module 68 | Workspace = terraform.workspace 69 | SubnetType = "public" 70 | } 71 | 72 | lifecycle { 73 | create_before_destroy = true 74 | } 75 | } 76 | 77 | resource "aws_internet_gateway" "this" { 78 | count = var.create_vpc ? 1 : 0 79 | 80 | vpc_id = aws_vpc.this[0].id 81 | 82 | tags = { 83 | Name = var.name 84 | Module = path.module 85 | Workspace = terraform.workspace 86 | } 87 | 88 | lifecycle { 89 | create_before_destroy = true 90 | } 91 | } 92 | 93 | resource "aws_route_table" "public" { 94 | count = var.create_vpc ? 1 : 0 95 | 96 | vpc_id = aws_vpc.this[0].id 97 | 98 | tags = { 99 | Name = var.name 100 | Module = path.module 101 | Workspace = terraform.workspace 102 | SubnetType = "public" 103 | } 104 | 105 | lifecycle { 106 | create_before_destroy = true 107 | } 108 | } 109 | 110 | resource "aws_route" "public" { 111 | count = var.create_vpc ? 1 : 0 112 | 113 | route_table_id = aws_route_table.public[0].id 114 | destination_cidr_block = "0.0.0.0/0" 115 | gateway_id = aws_internet_gateway.this[0].id 116 | 117 | lifecycle { 118 | create_before_destroy = true 119 | } 120 | } 121 | 122 | resource "aws_route_table_association" "public" { 123 | count = var.create_vpc ? 1 : 0 124 | 125 | route_table_id = aws_route_table.public[0].id 126 | subnet_id = aws_subnet.public[count.index].id 127 | 128 | lifecycle { 129 | create_before_destroy = true 130 | } 131 | } 132 | 133 | resource "aws_flow_log" "this" { 134 | count = var.create_vpc && var.flow_log_enable ? 1 : 0 135 | 136 | iam_role_arn = local.aws_flog_log_iam_role_arn 137 | log_destination = local.aws_flog_log_destination 138 | log_destination_type = local.aws_flog_log_destination_type 139 | traffic_type = "ALL" 140 | vpc_id = var.create_vpc ? aws_vpc.this[0].id : data.aws_vpc.default.id 141 | 142 | lifecycle { 143 | create_before_destroy = true 144 | } 145 | } 146 | 147 | resource "aws_cloudwatch_log_group" "this" { 148 | count = var.create_vpc && var.flow_log_enable && var.flow_log_destination == "cloudwatch" ? 1 : 0 149 | 150 | name = "${var.name}-flow-log-${random_id.this.hex}" 151 | retention_in_days = 14 152 | 153 | tags = { 154 | Name = var.name 155 | Module = path.module 156 | Workspace = terraform.workspace 157 | } 158 | 159 | lifecycle { 160 | create_before_destroy = true 161 | } 162 | } 163 | 164 | resource "aws_s3_bucket" "this" { 165 | count = var.create_vpc && var.flow_log_enable && var.flow_log_destination == "s3" ? 1 : 0 166 | 167 | bucket = "${var.name}-flow-log-${random_id.this.hex}" 168 | 169 | tags = { 170 | Name = var.name 171 | Module = path.module 172 | Workspace = terraform.workspace 173 | } 174 | 175 | lifecycle { 176 | create_before_destroy = true 177 | } 178 | } 179 | 180 | resource "aws_iam_role" "this" { 181 | count = var.create_vpc && var.flow_log_enable && var.flow_log_destination == "cloudwatch" ? 1 : 0 182 | 183 | name = "${var.name}-flow-log-${random_id.this.hex}" 184 | 185 | assume_role_policy = data.aws_iam_policy_document.role.json 186 | force_detach_policies = true 187 | path = "/" 188 | 189 | tags = { 190 | Name = var.name 191 | Module = path.module 192 | Workspace = terraform.workspace 193 | } 194 | 195 | lifecycle { 196 | create_before_destroy = true 197 | } 198 | } 199 | 200 | resource "aws_iam_role_policy" "this" { 201 | count = var.create_vpc && var.flow_log_enable && var.flow_log_destination == "cloudwatch" ? 1 : 0 202 | 203 | name = "${var.name}-flow-log-${random_id.this.hex}" 204 | 205 | role = aws_iam_role.this[0].id 206 | policy = data.aws_iam_policy_document.role_policy_cloudwatch.json 207 | 208 | lifecycle { 209 | create_before_destroy = true 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dan-v/awslambdaproxy 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.16.0 7 | github.com/aws/aws-sdk-go v1.30.9 8 | github.com/axw/gocov v1.0.0 // indirect 9 | github.com/ginuerzh/gost v0.0.0-20200414134316-6e46ac03c7a7 10 | github.com/google/uuid v1.1.1 11 | github.com/gorilla/websocket v1.4.2 // indirect 12 | github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d 13 | github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82 // indirect 14 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 15 | github.com/onsi/ginkgo v1.12.0 // indirect 16 | github.com/onsi/gomega v1.9.0 // indirect 17 | github.com/pkg/errors v0.9.1 18 | github.com/sirupsen/logrus v1.2.0 19 | github.com/spf13/cobra v1.0.0 20 | github.com/spf13/viper v1.6.3 21 | github.com/stretchr/testify v1.5.1 22 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 23 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect 24 | golang.org/x/text v0.3.2 // indirect 25 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 26 | gopkg.in/ini.v1 v1.55.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e h1:PYcONLFUhr00kGrq7Mf14JRtoXHG7BOSKIfIha0Hu5Q= 3 | git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q= 4 | git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e h1:c8h60PKrRxEB5debIHBmP7T+s/EUNXTklXqlmJfYiJQ= 5 | git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e/go.mod h1:jRZbfRcLIgFQoCw6tRmsnETVyIj54jOmXhHCYYa0jbs= 6 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 7 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 8 | github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4= 9 | github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ= 10 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 11 | github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= 12 | github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= 13 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 14 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 15 | github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= 16 | github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 20 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= 21 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 22 | github.com/aws/aws-lambda-go v1.16.0 h1:9+Pp1/6cjEXYhwadp8faFXKSOWt7/tHRCnQxQmKvVwM= 23 | github.com/aws/aws-lambda-go v1.16.0/go.mod h1:FEwgPLE6+8wcGBTe5cJN3JWurd1Ztm9zN4jsXsjzKKw= 24 | github.com/aws/aws-sdk-go v1.30.9 h1:DntpBUKkchINPDbhEzDRin1eEn1TG9TZFlzWPf0i8to= 25 | github.com/aws/aws-sdk-go v1.30.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= 26 | github.com/axw/gocov v1.0.0 h1:YsqYR66hUmilVr23tu8USgnJIJvnwh3n7j5zRn7x4LU= 27 | github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= 28 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 29 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 30 | github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4= 31 | github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= 32 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 33 | github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= 34 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 35 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 36 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 37 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 38 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 39 | github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38= 40 | github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= 41 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 42 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 43 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 44 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 45 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 46 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 47 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 48 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 49 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 | github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= 51 | github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= 52 | github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= 53 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 54 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 55 | github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= 56 | github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= 57 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 58 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 59 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 60 | github.com/ginuerzh/gosocks4 v0.0.1 h1:ojDKUyz+uaEeRm2usY1cyQiXTqJqrKxfeE6SVBXq4m0= 61 | github.com/ginuerzh/gosocks4 v0.0.1/go.mod h1:8SdwBMKjfJ9+BfP2vDJM1jcrgWUbWV6qxBPHHVrwptY= 62 | github.com/ginuerzh/gosocks5 v0.2.0 h1:K0Ua23U9LU3BZrf3XpGDcs0mP8DiEpa6PJE4TA/MU3s= 63 | github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno= 64 | github.com/ginuerzh/gost v0.0.0-20200414134316-6e46ac03c7a7 h1:q0Aznh/+f3H6aOhl5XIwx2g6p+WbgKFQ5+GERz1N+Ao= 65 | github.com/ginuerzh/gost v0.0.0-20200414134316-6e46ac03c7a7/go.mod h1:Se27DamrH1hAfELIHEO9SD/ahPR1BJ2hlxhFEM6YpuU= 66 | github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796 h1:VPXbYRvZUzTemsI7u0FzOnEuHeHwQuMTPXApAu8aeX4= 67 | github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0= 68 | github.com/go-gost/relay v0.1.0 h1:UOf2YwAzzaUjY5mdpMuLfSw0vz62iIFYk7oJQkuhlGw= 69 | github.com/go-gost/relay v0.1.0/go.mod h1:YFCpddLOFE3NlIkeDWRdEs8gL/GFsqXdtaf8SV5v4YQ= 70 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 71 | github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= 72 | github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= 73 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 74 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 75 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 76 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 77 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 78 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 79 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 80 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 81 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 83 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 84 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 85 | github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= 86 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 87 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 88 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 89 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 90 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 91 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 92 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 93 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 94 | github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= 95 | github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= 96 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 97 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 98 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 99 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 100 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 101 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 102 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 103 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 104 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 105 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 106 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 107 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 108 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 109 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 110 | github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d h1:W+SIwDdl3+jXWeidYySAgzytE3piq6GumXeBjFBG67c= 111 | github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 112 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 113 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 114 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 115 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 116 | github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= 117 | github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 118 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 119 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 120 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 121 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 122 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 123 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 124 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 125 | github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E= 126 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 127 | github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= 128 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 129 | github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE= 130 | github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= 131 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 132 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 133 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 134 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 135 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 136 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 137 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 138 | github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A= 139 | github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= 140 | github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc= 141 | github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY= 142 | github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug= 143 | github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= 144 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 145 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 146 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 147 | github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82 h1:pMb6HhXFlcC2qHIx7Z++2nRhgQ+5kQx8wLbMqBpuU6U= 148 | github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= 149 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 150 | github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= 151 | github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 152 | github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8 h1:4WFQEfEJ7zaHYViIVM2Cd6tnQOOhiEHbmQtlcV7aOpc= 153 | github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8/go.mod h1:G95Wwn625/q6JCCytI4VR/a5VtPwrtI0B+Q1Gi38QLA= 154 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 155 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 156 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 157 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 158 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 159 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 160 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 161 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 162 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 163 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 164 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 165 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 166 | github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= 167 | github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= 168 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 169 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 170 | github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= 171 | github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 172 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 173 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 174 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 175 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 176 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 177 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 178 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 179 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 180 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 181 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 182 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 183 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 184 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 185 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 186 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 187 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 188 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 189 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 190 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 191 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 192 | github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= 193 | github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 194 | github.com/shadowsocks/go-shadowsocks2 v0.1.0 h1:jQhkjAmMuOTQ7B04bnrRJ5IAoZEwoaXXkKspE7rQ6ck= 195 | github.com/shadowsocks/go-shadowsocks2 v0.1.0/go.mod h1:/0aFGbhK8mtOX4J/6kTJsPLZlEs9KnzKoWCOCvjd7vk= 196 | github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba h1:tJgNXb3S+RkB4kNPi6N5OmEWe3m+Y3Qs6LUMiNDAONM= 197 | github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY= 198 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 199 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 200 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 201 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 202 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 203 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 204 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 205 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 206 | github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= 207 | github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= 208 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 209 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 210 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 211 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 212 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 213 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 214 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 215 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 216 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 217 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 218 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 219 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 220 | github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= 221 | github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= 222 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 223 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 224 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 225 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 226 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 227 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 228 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 229 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 230 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 231 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 232 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 233 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 234 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM= 235 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 236 | github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU= 237 | github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= 238 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 239 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 240 | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 241 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 242 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 243 | github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk= 244 | github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk= 245 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 246 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 247 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 248 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 249 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 250 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 251 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 252 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 253 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 254 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 255 | golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 256 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= 257 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 258 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 259 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= 260 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 261 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 262 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 263 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 264 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 265 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 266 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 267 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 270 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 271 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 272 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 273 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 274 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 275 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 276 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 277 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 278 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 279 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 280 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 281 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 282 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 283 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 284 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 285 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 286 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 287 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 288 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 289 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 290 | golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= 294 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 296 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 297 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 298 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 299 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 300 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 301 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 302 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 303 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 304 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 305 | golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 306 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= 307 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 308 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= 309 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 310 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 311 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 312 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= 313 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 314 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 315 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 316 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 317 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 318 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 319 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 320 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 321 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 322 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 323 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 324 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 325 | gopkg.in/gorilla/websocket.v1 v1.4.0 h1:lREme3ezAGPCpxSHwjGkHhAJX+ed2B6vzAJ+kaqBEIM= 326 | gopkg.in/gorilla/websocket.v1 v1.4.0/go.mod h1:Ons1i8d00TjvJPdla7bJyeXFsdOacUyrTYbg9IetsIE= 327 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 328 | gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= 329 | gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 330 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 331 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 332 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 333 | gopkg.in/xtaci/kcp-go.v4 v4.3.2 h1:S9IF+L55Ugzl/hVA6wvuL3SuAtTUzH2cBBC88MXQxnE= 334 | gopkg.in/xtaci/kcp-go.v4 v4.3.2/go.mod h1:fFYTlSOHNOHDNTKfoqarZMQsu7g7oXKwJ9wq0i9lODc= 335 | gopkg.in/xtaci/smux.v1 v1.0.7 h1:qootIZs4ZPSx5blhvgaFpx2epdFSWkyw99xT+q0mRXI= 336 | gopkg.in/xtaci/smux.v1 v1.0.7/go.mod h1:NbrPjLp8lNAYN8KqTplnFr2JjIBbr7CdHBkHtHsXtWA= 337 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 338 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 339 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 340 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 341 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 342 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 343 | -------------------------------------------------------------------------------- /pkg/lambda/datacopy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type lambdaDataCopyManager struct { 12 | lambdaTunnelConnection *lambdaTunnelConnection 13 | lambdaProxyServer *lambdaProxyServer 14 | } 15 | 16 | func (l *lambdaDataCopyManager) run() { 17 | for { 18 | proxySocketConn, proxySocketErr := net.Dial("tcp", l.lambdaProxyServer.port) 19 | if proxySocketErr != nil { 20 | log.Printf("Failed to open connection to proxy: %v\n", proxySocketErr) 21 | time.Sleep(time.Second) 22 | continue 23 | } 24 | log.Printf("Opened local connection to proxy on port %v\n", l.lambdaProxyServer.port) 25 | 26 | tunnelStream, tunnelErr := l.lambdaTunnelConnection.sess.Accept() 27 | if tunnelErr != nil { 28 | log.Printf("Failed to start new stream: %v. Exiting function.\n", tunnelErr) 29 | return 30 | } 31 | log.Println("Started new stream") 32 | 33 | go bidirectionalCopy(tunnelStream, proxySocketConn) 34 | } 35 | } 36 | 37 | func newLambdaDataCopyManager(p *lambdaProxyServer, t *lambdaTunnelConnection) *lambdaDataCopyManager { 38 | return &lambdaDataCopyManager{ 39 | lambdaTunnelConnection: t, 40 | lambdaProxyServer: p, 41 | } 42 | } 43 | 44 | func bidirectionalCopy(src io.ReadWriteCloser, dst io.ReadWriteCloser) { 45 | defer dst.Close() 46 | defer src.Close() 47 | 48 | var wg sync.WaitGroup 49 | wg.Add(1) 50 | go func() { 51 | io.Copy(dst, src) 52 | wg.Done() 53 | }() 54 | 55 | wg.Add(1) 56 | go func() { 57 | io.Copy(src, dst) 58 | wg.Done() 59 | }() 60 | wg.Wait() 61 | } 62 | -------------------------------------------------------------------------------- /pkg/lambda/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | 8 | "github.com/aws/aws-lambda-go/lambda" 9 | ) 10 | 11 | const privateKeyFile = "/tmp/privatekey" 12 | 13 | type Request struct { 14 | UUID string `json:"UUID"` 15 | Address string `json:"ConnectBackAddress"` 16 | SSHPort string `json:"SSHPort"` 17 | SSHKey string `json:"SSHKey"` 18 | SSHUser string `json:"SSHUser"` 19 | } 20 | 21 | type Response struct { 22 | Message string `json:"message"` 23 | Ok bool `json:"ok"` 24 | } 25 | 26 | func Handler(request Request) (Response, error) { 27 | log.Printf("Processing request UUID=%v\n", request.UUID) 28 | sshKeyData := []byte(request.SSHKey) 29 | err := ioutil.WriteFile(privateKeyFile, sshKeyData, 0600) 30 | if err != nil { 31 | log.Fatal("Failed to write SSH key to disk. ", err) 32 | } 33 | 34 | log.Println("Starting proxy server") 35 | lambdaProxyServer := startLambdaProxyServer() 36 | defer lambdaProxyServer.close() 37 | 38 | log.Printf("Establishing ssh tunnel connection to %v\n", request.Address) 39 | lambdaTunnelConnection, err := setupLambdaTunnelConnection(request.Address, request.SSHPort, request.SSHUser, privateKeyFile) 40 | if err != nil { 41 | log.Fatalf("Failed to establish connection to %v: %v\n", request.Address, err) 42 | } 43 | defer lambdaTunnelConnection.close() 44 | 45 | log.Println("Starting lambdaDataCopyManager") 46 | dataCopyManager := newLambdaDataCopyManager(lambdaProxyServer, lambdaTunnelConnection) 47 | dataCopyManager.run() 48 | return Response{ 49 | Message: fmt.Sprintf("Finished processing request"), 50 | Ok: true, 51 | }, nil 52 | } 53 | 54 | func main() { 55 | lambda.Start(Handler) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/lambda/proxyserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | "github.com/ginuerzh/gost" 9 | ) 10 | 11 | type lambdaProxyServer struct { 12 | port string 13 | ln gost.Listener 14 | server *gost.Server 15 | } 16 | 17 | func (l *lambdaProxyServer) run() { 18 | ln, err := gost.TCPListener(":0") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | l.ln = ln 23 | l.port = fmt.Sprintf(":%v", ln.Addr().(*net.TCPAddr).Port) 24 | 25 | h := gost.AutoHandler() 26 | s := &gost.Server{Listener: ln} 27 | l.server = s 28 | 29 | err = s.Serve(h) 30 | if err != nil { 31 | log.Printf("Server is now exiting: %v\n", err) 32 | } 33 | } 34 | 35 | func (l *lambdaProxyServer) close() { 36 | log.Println("Closing down server") 37 | err := l.server.Close() 38 | if err != nil { 39 | log.Printf("closing server error: %v\n", err) 40 | } 41 | log.Println("Closing down listener") 42 | err = l.ln.Close() 43 | if err != nil { 44 | log.Printf("closing listener error: %v\n", err) 45 | } 46 | } 47 | 48 | func startLambdaProxyServer() *lambdaProxyServer { 49 | server := &lambdaProxyServer{} 50 | go server.run() 51 | return server 52 | } 53 | -------------------------------------------------------------------------------- /pkg/lambda/tunnelconnection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net" 7 | 8 | "github.com/hashicorp/yamux" 9 | "golang.org/x/crypto/ssh" 10 | ) 11 | 12 | const ( 13 | tunnelPortOnRemoteServer = "localhost:8081" 14 | ) 15 | 16 | type lambdaTunnelConnection struct { 17 | tunnelHost string 18 | sshUsername string 19 | sshSigner ssh.Signer 20 | conn net.Conn 21 | sess *yamux.Session 22 | } 23 | 24 | func (l *lambdaTunnelConnection) publicKeyFile() ssh.AuthMethod { 25 | return ssh.PublicKeys(l.sshSigner) 26 | } 27 | 28 | func (l *lambdaTunnelConnection) setup() { 29 | sshConfig := &ssh.ClientConfig{ 30 | User: l.sshUsername, 31 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 32 | Auth: []ssh.AuthMethod{ 33 | l.publicKeyFile(), 34 | }, 35 | } 36 | 37 | tunnelConn, err := ssh.Dial("tcp", l.tunnelHost, sshConfig) 38 | if err != nil { 39 | log.Fatalf("Failed to start SSH tunnel to %v: %v\n", l.tunnelHost, err) 40 | } 41 | log.Printf("Setup SSH tunnel to tunnelHost=%v\n", l.tunnelHost) 42 | 43 | localConn, err := tunnelConn.Dial("tcp", tunnelPortOnRemoteServer) 44 | if err != nil { 45 | log.Fatalf("Failed to create connection to tunnelPortOnRemoteServer=%v: %v\n", tunnelPortOnRemoteServer, err) 46 | } 47 | l.conn = localConn 48 | log.Printf("Setup connection to tunnelPortOnRemoteServer=%v\n", tunnelPortOnRemoteServer) 49 | 50 | tunnelSession, err := yamux.Server(localConn, nil) 51 | if err != nil { 52 | log.Fatalf("Failed to start session inside tunnel: %v\n", err) 53 | } 54 | log.Println("Started yamux session inside tunnel") 55 | l.sess = tunnelSession 56 | } 57 | 58 | func (l *lambdaTunnelConnection) close() { 59 | log.Printf("Closing session") 60 | err := l.sess.Close() 61 | if err != nil { 62 | log.Printf("Error closing session: %v", err) 63 | } 64 | log.Printf("Closing connection") 65 | err = l.conn.Close() 66 | if err != nil { 67 | log.Printf("Error closing connection: %v", err) 68 | } 69 | } 70 | 71 | func setupLambdaTunnelConnection(tunnelHost string, sshPort string, sshUsername string, 72 | sshPrivateKeyFile string) (*lambdaTunnelConnection, error) { 73 | data, err := ioutil.ReadFile(sshPrivateKeyFile) 74 | if err != nil { 75 | log.Println("Failed to read private key file", sshPrivateKeyFile) 76 | return nil, err 77 | } 78 | signer, err := ssh.ParsePrivateKey(data) 79 | if err != nil { 80 | log.Println("Failed to parse private key", sshPrivateKeyFile) 81 | return nil, err 82 | } 83 | 84 | tunnel := &lambdaTunnelConnection{ 85 | tunnelHost: tunnelHost + ":" + sshPort, 86 | sshUsername: sshUsername, 87 | sshSigner: signer, 88 | } 89 | 90 | tunnel.setup() 91 | return tunnel, nil 92 | } 93 | -------------------------------------------------------------------------------- /pkg/server/gost.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/sha256" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "encoding/base64" 10 | "encoding/json" 11 | "errors" 12 | "fmt" 13 | "github.com/ginuerzh/gost" 14 | "io" 15 | "io/ioutil" 16 | "log" 17 | "net" 18 | "net/url" 19 | "os" 20 | "strconv" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | // the cli for gost has a bunch of unexported functions 26 | // to help setup chains and listeners (https://github.com/ginuerzh/gost/tree/8ab2fe6f77d43fdd5613569377f9e853a84bcc4d) 27 | // rather than recreate this, just copying as is for now 28 | 29 | type stringList []string 30 | 31 | func (l *stringList) String() string { 32 | return fmt.Sprintf("%s", *l) 33 | } 34 | 35 | func (l *stringList) Set(value string) error { 36 | *l = append(*l, value) 37 | return nil 38 | } 39 | 40 | type route struct { 41 | ServeNodes stringList 42 | ChainNodes stringList 43 | Retries int 44 | } 45 | 46 | func (r *route) parseChain() (*gost.Chain, error) { 47 | chain := gost.NewChain() 48 | chain.Retries = r.Retries 49 | gid := 1 // group ID 50 | 51 | for _, ns := range r.ChainNodes { 52 | ngroup := gost.NewNodeGroup() 53 | ngroup.ID = gid 54 | gid++ 55 | 56 | // parse the base nodes 57 | nodes, err := parseChainNode(ns) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | nid := 1 // node ID 63 | for i := range nodes { 64 | nodes[i].ID = nid 65 | nid++ 66 | } 67 | ngroup.AddNode(nodes...) 68 | 69 | ngroup.SetSelector(nil, 70 | gost.WithFilter( 71 | &gost.FailFilter{ 72 | MaxFails: nodes[0].GetInt("max_fails"), 73 | FailTimeout: nodes[0].GetDuration("fail_timeout"), 74 | }, 75 | &gost.InvalidFilter{}, 76 | ), 77 | gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))), 78 | ) 79 | 80 | if cfg := nodes[0].Get("peer"); cfg != "" { 81 | f, err := os.Open(cfg) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | peerCfg := newPeerConfig() 87 | peerCfg.group = ngroup 88 | peerCfg.baseNodes = nodes 89 | peerCfg.Reload(f) 90 | f.Close() 91 | 92 | go gost.PeriodReload(peerCfg, cfg) 93 | } 94 | 95 | chain.AddNodeGroup(ngroup) 96 | } 97 | 98 | return chain, nil 99 | } 100 | 101 | func parseChainNode(ns string) (nodes []gost.Node, err error) { 102 | node, err := gost.ParseNode(ns) 103 | if err != nil { 104 | return 105 | } 106 | 107 | if auth := node.Get("auth"); auth != "" && node.User == nil { 108 | c, err := base64.StdEncoding.DecodeString(auth) 109 | if err != nil { 110 | return nil, err 111 | } 112 | cs := string(c) 113 | s := strings.IndexByte(cs, ':') 114 | if s < 0 { 115 | node.User = url.User(cs) 116 | } else { 117 | node.User = url.UserPassword(cs[:s], cs[s+1:]) 118 | } 119 | } 120 | if node.User == nil { 121 | users, err := parseUsers(node.Get("secrets")) 122 | if err != nil { 123 | return nil, err 124 | } 125 | if len(users) > 0 { 126 | node.User = users[0] 127 | } 128 | } 129 | 130 | serverName, sport, _ := net.SplitHostPort(node.Addr) 131 | if serverName == "" { 132 | serverName = "localhost" // default server name 133 | } 134 | 135 | rootCAs, err := loadCA(node.Get("ca")) 136 | if err != nil { 137 | return 138 | } 139 | tlsCfg := &tls.Config{ 140 | ServerName: serverName, 141 | InsecureSkipVerify: !node.GetBool("secure"), 142 | RootCAs: rootCAs, 143 | } 144 | wsOpts := &gost.WSOptions{} 145 | wsOpts.EnableCompression = node.GetBool("compression") 146 | wsOpts.ReadBufferSize = node.GetInt("rbuf") 147 | wsOpts.WriteBufferSize = node.GetInt("wbuf") 148 | wsOpts.UserAgent = node.Get("agent") 149 | wsOpts.Path = node.Get("path") 150 | 151 | timeout := node.GetDuration("timeout") 152 | 153 | var tr gost.Transporter 154 | switch node.Transport { 155 | case "tls": 156 | tr = gost.TLSTransporter() 157 | case "mtls": 158 | tr = gost.MTLSTransporter() 159 | case "ws": 160 | tr = gost.WSTransporter(wsOpts) 161 | case "mws": 162 | tr = gost.MWSTransporter(wsOpts) 163 | case "wss": 164 | tr = gost.WSSTransporter(wsOpts) 165 | case "mwss": 166 | tr = gost.MWSSTransporter(wsOpts) 167 | case "kcp": 168 | config, err := parseKCPConfig(node.Get("c")) 169 | if err != nil { 170 | return nil, err 171 | } 172 | if config == nil { 173 | conf := gost.DefaultKCPConfig 174 | if node.GetBool("tcp") { 175 | conf.TCP = true 176 | } 177 | config = &conf 178 | } 179 | tr = gost.KCPTransporter(config) 180 | case "ssh": 181 | if node.Protocol == "direct" || node.Protocol == "remote" { 182 | tr = gost.SSHForwardTransporter() 183 | } else { 184 | tr = gost.SSHTunnelTransporter() 185 | } 186 | case "quic": 187 | config := &gost.QUICConfig{ 188 | TLSConfig: tlsCfg, 189 | KeepAlive: node.GetBool("keepalive"), 190 | Timeout: timeout, 191 | IdleTimeout: node.GetDuration("idle"), 192 | } 193 | 194 | if cipher := node.Get("cipher"); cipher != "" { 195 | sum := sha256.Sum256([]byte(cipher)) 196 | config.Key = sum[:] 197 | } 198 | 199 | tr = gost.QUICTransporter(config) 200 | case "http2": 201 | tr = gost.HTTP2Transporter(tlsCfg) 202 | case "h2": 203 | tr = gost.H2Transporter(tlsCfg, node.Get("path")) 204 | case "h2c": 205 | tr = gost.H2CTransporter(node.Get("path")) 206 | case "obfs4": 207 | tr = gost.Obfs4Transporter() 208 | case "ohttp": 209 | tr = gost.ObfsHTTPTransporter() 210 | case "otls": 211 | tr = gost.ObfsTLSTransporter() 212 | case "ftcp": 213 | tr = gost.FakeTCPTransporter() 214 | case "udp": 215 | tr = gost.UDPTransporter() 216 | default: 217 | tr = gost.TCPTransporter() 218 | } 219 | 220 | var connector gost.Connector 221 | switch node.Protocol { 222 | case "http2": 223 | connector = gost.HTTP2Connector(node.User) 224 | case "socks", "socks5": 225 | connector = gost.SOCKS5Connector(node.User) 226 | case "socks4": 227 | connector = gost.SOCKS4Connector() 228 | case "socks4a": 229 | connector = gost.SOCKS4AConnector() 230 | case "ss": 231 | connector = gost.ShadowConnector(node.User) 232 | case "ssu": 233 | connector = gost.ShadowUDPConnector(node.User) 234 | case "direct": 235 | connector = gost.SSHDirectForwardConnector() 236 | case "remote": 237 | connector = gost.SSHRemoteForwardConnector() 238 | case "forward": 239 | connector = gost.ForwardConnector() 240 | case "sni": 241 | connector = gost.SNIConnector(node.Get("host")) 242 | case "http": 243 | connector = gost.HTTPConnector(node.User) 244 | case "relay": 245 | connector = gost.RelayConnector(node.User) 246 | default: 247 | connector = gost.AutoConnector(node.User) 248 | } 249 | 250 | node.DialOptions = append(node.DialOptions, 251 | gost.TimeoutDialOption(timeout), 252 | ) 253 | 254 | node.ConnectOptions = []gost.ConnectOption{ 255 | gost.UserAgentConnectOption(node.Get("agent")), 256 | gost.NoTLSConnectOption(node.GetBool("notls")), 257 | gost.NoDelayConnectOption(node.GetBool("nodelay")), 258 | } 259 | 260 | host := node.Get("host") 261 | if host == "" { 262 | host = node.Host 263 | } 264 | 265 | sshConfig := &gost.SSHConfig{} 266 | if s := node.Get("ssh_key"); s != "" { 267 | key, err := gost.ParseSSHKeyFile(s) 268 | if err != nil { 269 | return nil, err 270 | } 271 | sshConfig.Key = key 272 | } 273 | handshakeOptions := []gost.HandshakeOption{ 274 | gost.AddrHandshakeOption(node.Addr), 275 | gost.HostHandshakeOption(host), 276 | gost.UserHandshakeOption(node.User), 277 | gost.TLSConfigHandshakeOption(tlsCfg), 278 | gost.IntervalHandshakeOption(node.GetDuration("ping")), 279 | gost.TimeoutHandshakeOption(timeout), 280 | gost.RetryHandshakeOption(node.GetInt("retry")), 281 | gost.SSHConfigHandshakeOption(sshConfig), 282 | } 283 | 284 | node.Client = &gost.Client{ 285 | Connector: connector, 286 | Transporter: tr, 287 | } 288 | 289 | node.Bypass = parseBypass(node.Get("bypass")) 290 | 291 | ips := parseIP(node.Get("ip"), sport) 292 | for _, ip := range ips { 293 | nd := node.Clone() 294 | nd.Addr = ip 295 | // override the default node address 296 | nd.HandshakeOptions = append(handshakeOptions, gost.AddrHandshakeOption(ip)) 297 | // One node per IP 298 | nodes = append(nodes, nd) 299 | } 300 | if len(ips) == 0 { 301 | node.HandshakeOptions = handshakeOptions 302 | nodes = []gost.Node{node} 303 | } 304 | 305 | if node.Transport == "obfs4" { 306 | for i := range nodes { 307 | if err := gost.Obfs4Init(nodes[i], false); err != nil { 308 | return nil, err 309 | } 310 | } 311 | } 312 | 313 | return 314 | } 315 | 316 | func (r *route) GenRouters() ([]router, error) { 317 | chain, err := r.parseChain() 318 | if err != nil { 319 | return nil, err 320 | } 321 | 322 | var rts []router 323 | 324 | for _, ns := range r.ServeNodes { 325 | node, err := gost.ParseNode(ns) 326 | if err != nil { 327 | return nil, err 328 | } 329 | 330 | if auth := node.Get("auth"); auth != "" && node.User == nil { 331 | c, err := base64.StdEncoding.DecodeString(auth) 332 | if err != nil { 333 | return nil, err 334 | } 335 | cs := string(c) 336 | s := strings.IndexByte(cs, ':') 337 | if s < 0 { 338 | node.User = url.User(cs) 339 | } else { 340 | node.User = url.UserPassword(cs[:s], cs[s+1:]) 341 | } 342 | } 343 | authenticator, err := parseAuthenticator(node.Get("secrets")) 344 | if err != nil { 345 | return nil, err 346 | } 347 | if authenticator == nil && node.User != nil { 348 | kvs := make(map[string]string) 349 | kvs[node.User.Username()], _ = node.User.Password() 350 | authenticator = gost.NewLocalAuthenticator(kvs) 351 | } 352 | if node.User == nil { 353 | if users, _ := parseUsers(node.Get("secrets")); len(users) > 0 { 354 | node.User = users[0] 355 | } 356 | } 357 | certFile, keyFile := node.Get("cert"), node.Get("key") 358 | tlsCfg, err := tlsConfig(certFile, keyFile) 359 | if err != nil && certFile != "" && keyFile != "" { 360 | return nil, err 361 | } 362 | 363 | wsOpts := &gost.WSOptions{} 364 | wsOpts.EnableCompression = node.GetBool("compression") 365 | wsOpts.ReadBufferSize = node.GetInt("rbuf") 366 | wsOpts.WriteBufferSize = node.GetInt("wbuf") 367 | wsOpts.Path = node.Get("path") 368 | 369 | ttl := node.GetDuration("ttl") 370 | timeout := node.GetDuration("timeout") 371 | 372 | tunRoutes := parseIPRoutes(node.Get("route")) 373 | gw := net.ParseIP(node.Get("gw")) // default gateway 374 | for i := range tunRoutes { 375 | if tunRoutes[i].Gateway == nil { 376 | tunRoutes[i].Gateway = gw 377 | } 378 | } 379 | 380 | var ln gost.Listener 381 | switch node.Transport { 382 | case "tls": 383 | ln, err = gost.TLSListener(node.Addr, tlsCfg) 384 | case "mtls": 385 | ln, err = gost.MTLSListener(node.Addr, tlsCfg) 386 | case "ws": 387 | ln, err = gost.WSListener(node.Addr, wsOpts) 388 | case "mws": 389 | ln, err = gost.MWSListener(node.Addr, wsOpts) 390 | case "wss": 391 | ln, err = gost.WSSListener(node.Addr, tlsCfg, wsOpts) 392 | case "mwss": 393 | ln, err = gost.MWSSListener(node.Addr, tlsCfg, wsOpts) 394 | case "kcp": 395 | config, er := parseKCPConfig(node.Get("c")) 396 | if er != nil { 397 | return nil, er 398 | } 399 | if config == nil { 400 | conf := gost.DefaultKCPConfig 401 | if node.GetBool("tcp") { 402 | conf.TCP = true 403 | } 404 | config = &conf 405 | } 406 | ln, err = gost.KCPListener(node.Addr, config) 407 | case "ssh": 408 | config := &gost.SSHConfig{ 409 | Authenticator: authenticator, 410 | TLSConfig: tlsCfg, 411 | } 412 | if s := node.Get("ssh_key"); s != "" { 413 | key, err := gost.ParseSSHKeyFile(s) 414 | if err != nil { 415 | return nil, err 416 | } 417 | config.Key = key 418 | } 419 | if s := node.Get("ssh_authorized_keys"); s != "" { 420 | keys, err := gost.ParseSSHAuthorizedKeysFile(s) 421 | if err != nil { 422 | return nil, err 423 | } 424 | config.AuthorizedKeys = keys 425 | } 426 | if node.Protocol == "forward" { 427 | ln, err = gost.TCPListener(node.Addr) 428 | } else { 429 | ln, err = gost.SSHTunnelListener(node.Addr, config) 430 | } 431 | case "quic": 432 | config := &gost.QUICConfig{ 433 | TLSConfig: tlsCfg, 434 | KeepAlive: node.GetBool("keepalive"), 435 | Timeout: timeout, 436 | IdleTimeout: node.GetDuration("idle"), 437 | } 438 | if cipher := node.Get("cipher"); cipher != "" { 439 | sum := sha256.Sum256([]byte(cipher)) 440 | config.Key = sum[:] 441 | } 442 | 443 | ln, err = gost.QUICListener(node.Addr, config) 444 | case "http2": 445 | ln, err = gost.HTTP2Listener(node.Addr, tlsCfg) 446 | case "h2": 447 | ln, err = gost.H2Listener(node.Addr, tlsCfg, node.Get("path")) 448 | case "h2c": 449 | ln, err = gost.H2CListener(node.Addr, node.Get("path")) 450 | case "tcp": 451 | // Directly use SSH port forwarding if the last chain node is forward+ssh 452 | if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" { 453 | chain.Nodes()[len(chain.Nodes())-1].Client.Connector = gost.SSHDirectForwardConnector() 454 | chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter() 455 | } 456 | ln, err = gost.TCPListener(node.Addr) 457 | case "udp": 458 | ln, err = gost.UDPListener(node.Addr, &gost.UDPListenConfig{ 459 | TTL: ttl, 460 | Backlog: node.GetInt("backlog"), 461 | QueueSize: node.GetInt("queue"), 462 | }) 463 | case "rtcp": 464 | // Directly use SSH port forwarding if the last chain node is forward+ssh 465 | if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" { 466 | chain.Nodes()[len(chain.Nodes())-1].Client.Connector = gost.SSHRemoteForwardConnector() 467 | chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter() 468 | } 469 | ln, err = gost.TCPRemoteForwardListener(node.Addr, chain) 470 | case "rudp": 471 | ln, err = gost.UDPRemoteForwardListener(node.Addr, 472 | chain, 473 | &gost.UDPListenConfig{ 474 | TTL: ttl, 475 | Backlog: node.GetInt("backlog"), 476 | QueueSize: node.GetInt("queue"), 477 | }) 478 | case "obfs4": 479 | if err = gost.Obfs4Init(node, true); err != nil { 480 | return nil, err 481 | } 482 | ln, err = gost.Obfs4Listener(node.Addr) 483 | case "ohttp": 484 | ln, err = gost.ObfsHTTPListener(node.Addr) 485 | case "otls": 486 | ln, err = gost.ObfsTLSListener(node.Addr) 487 | case "tun": 488 | cfg := gost.TunConfig{ 489 | Name: node.Get("name"), 490 | Addr: node.Get("net"), 491 | Peer: node.Get("peer"), 492 | MTU: node.GetInt("mtu"), 493 | Routes: tunRoutes, 494 | Gateway: node.Get("gw"), 495 | } 496 | ln, err = gost.TunListener(cfg) 497 | case "tap": 498 | cfg := gost.TapConfig{ 499 | Name: node.Get("name"), 500 | Addr: node.Get("net"), 501 | MTU: node.GetInt("mtu"), 502 | Routes: strings.Split(node.Get("route"), ","), 503 | Gateway: node.Get("gw"), 504 | } 505 | ln, err = gost.TapListener(cfg) 506 | case "ftcp": 507 | ln, err = gost.FakeTCPListener( 508 | node.Addr, 509 | &gost.FakeTCPListenConfig{ 510 | TTL: ttl, 511 | Backlog: node.GetInt("backlog"), 512 | QueueSize: node.GetInt("queue"), 513 | }, 514 | ) 515 | case "dns": 516 | ln, err = gost.DNSListener( 517 | node.Addr, 518 | &gost.DNSOptions{ 519 | Mode: node.Get("mode"), 520 | TLSConfig: tlsCfg, 521 | }, 522 | ) 523 | case "redu", "redirectu": 524 | ln, err = gost.UDPRedirectListener(node.Addr, &gost.UDPListenConfig{ 525 | TTL: ttl, 526 | Backlog: node.GetInt("backlog"), 527 | QueueSize: node.GetInt("queue"), 528 | }) 529 | default: 530 | ln, err = gost.TCPListener(node.Addr) 531 | } 532 | if err != nil { 533 | return nil, err 534 | } 535 | 536 | var handler gost.Handler 537 | switch node.Protocol { 538 | case "http2": 539 | handler = gost.HTTP2Handler() 540 | case "socks", "socks5": 541 | handler = gost.SOCKS5Handler() 542 | case "socks4", "socks4a": 543 | handler = gost.SOCKS4Handler() 544 | case "ss": 545 | handler = gost.ShadowHandler() 546 | case "http": 547 | handler = gost.HTTPHandler() 548 | case "tcp": 549 | handler = gost.TCPDirectForwardHandler(node.Remote) 550 | case "rtcp": 551 | handler = gost.TCPRemoteForwardHandler(node.Remote) 552 | case "udp": 553 | handler = gost.UDPDirectForwardHandler(node.Remote) 554 | case "rudp": 555 | handler = gost.UDPRemoteForwardHandler(node.Remote) 556 | case "forward": 557 | handler = gost.SSHForwardHandler() 558 | case "red", "redirect": 559 | handler = gost.TCPRedirectHandler() 560 | case "redu", "redirectu": 561 | handler = gost.UDPRedirectHandler() 562 | case "ssu": 563 | handler = gost.ShadowUDPHandler() 564 | case "sni": 565 | handler = gost.SNIHandler() 566 | case "tun": 567 | handler = gost.TunHandler() 568 | case "tap": 569 | handler = gost.TapHandler() 570 | case "dns": 571 | handler = gost.DNSHandler(node.Remote) 572 | case "relay": 573 | handler = gost.RelayHandler(node.Remote) 574 | default: 575 | // start from 2.5, if remote is not empty, then we assume that it is a forward tunnel. 576 | if node.Remote != "" { 577 | handler = gost.TCPDirectForwardHandler(node.Remote) 578 | } else { 579 | handler = gost.AutoHandler() 580 | } 581 | } 582 | 583 | var whitelist, blacklist *gost.Permissions 584 | if node.Values.Get("whitelist") != "" { 585 | if whitelist, err = gost.ParsePermissions(node.Get("whitelist")); err != nil { 586 | return nil, err 587 | } 588 | } 589 | if node.Values.Get("blacklist") != "" { 590 | if blacklist, err = gost.ParsePermissions(node.Get("blacklist")); err != nil { 591 | return nil, err 592 | } 593 | } 594 | 595 | node.Bypass = parseBypass(node.Get("bypass")) 596 | hosts := parseHosts(node.Get("hosts")) 597 | ips := parseIP(node.Get("ip"), "") 598 | 599 | resolver := parseResolver(node.Get("dns")) 600 | if resolver != nil { 601 | resolver.Init( 602 | gost.ChainResolverOption(chain), 603 | gost.TimeoutResolverOption(timeout), 604 | gost.TTLResolverOption(ttl), 605 | gost.PreferResolverOption(node.Get("prefer")), 606 | gost.SrcIPResolverOption(net.ParseIP(node.Get("ip"))), 607 | ) 608 | } 609 | 610 | handler.Init( 611 | gost.AddrHandlerOption(ln.Addr().String()), 612 | gost.ChainHandlerOption(chain), 613 | gost.UsersHandlerOption(node.User), 614 | gost.AuthenticatorHandlerOption(authenticator), 615 | gost.TLSConfigHandlerOption(tlsCfg), 616 | gost.WhitelistHandlerOption(whitelist), 617 | gost.BlacklistHandlerOption(blacklist), 618 | gost.StrategyHandlerOption(gost.NewStrategy(node.Get("strategy"))), 619 | gost.MaxFailsHandlerOption(node.GetInt("max_fails")), 620 | gost.FailTimeoutHandlerOption(node.GetDuration("fail_timeout")), 621 | gost.BypassHandlerOption(node.Bypass), 622 | gost.ResolverHandlerOption(resolver), 623 | gost.HostsHandlerOption(hosts), 624 | gost.RetryHandlerOption(node.GetInt("retry")), // override the global retry option. 625 | gost.TimeoutHandlerOption(timeout), 626 | gost.ProbeResistHandlerOption(node.Get("probe_resist")), 627 | gost.KnockingHandlerOption(node.Get("knock")), 628 | gost.NodeHandlerOption(node), 629 | gost.IPsHandlerOption(ips), 630 | gost.TCPModeHandlerOption(node.GetBool("tcp")), 631 | gost.IPRoutesHandlerOption(tunRoutes...), 632 | ) 633 | 634 | rt := router{ 635 | node: node, 636 | server: &gost.Server{Listener: ln}, 637 | handler: handler, 638 | chain: chain, 639 | resolver: resolver, 640 | hosts: hosts, 641 | } 642 | rts = append(rts, rt) 643 | } 644 | 645 | return rts, nil 646 | } 647 | 648 | type router struct { 649 | node gost.Node 650 | server *gost.Server 651 | handler gost.Handler 652 | chain *gost.Chain 653 | resolver gost.Resolver 654 | hosts *gost.Hosts 655 | } 656 | 657 | func (r *router) Serve() error { 658 | log.Printf("%s on %s", r.node.String(), r.server.Addr()) 659 | return r.server.Serve(r.handler) 660 | } 661 | 662 | func (r *router) Close() error { 663 | if r == nil || r.server == nil { 664 | return nil 665 | } 666 | return r.server.Close() 667 | } 668 | 669 | var ( 670 | routers []router 671 | ) 672 | 673 | type baseConfig struct { 674 | route 675 | Routes []route 676 | Debug bool 677 | } 678 | 679 | var ( 680 | defaultCertFile = "cert.pem" 681 | defaultKeyFile = "key.pem" 682 | ) 683 | 684 | // Load the certificate from cert and key files, will use the default certificate if the provided info are invalid. 685 | func tlsConfig(certFile, keyFile string) (*tls.Config, error) { 686 | if certFile == "" || keyFile == "" { 687 | certFile, keyFile = defaultCertFile, defaultKeyFile 688 | } 689 | 690 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 691 | if err != nil { 692 | return nil, err 693 | } 694 | return &tls.Config{Certificates: []tls.Certificate{cert}}, nil 695 | } 696 | 697 | func loadCA(caFile string) (cp *x509.CertPool, err error) { 698 | if caFile == "" { 699 | return 700 | } 701 | cp = x509.NewCertPool() 702 | data, err := ioutil.ReadFile(caFile) 703 | if err != nil { 704 | return nil, err 705 | } 706 | if !cp.AppendCertsFromPEM(data) { 707 | return nil, errors.New("AppendCertsFromPEM failed") 708 | } 709 | return 710 | } 711 | 712 | func parseKCPConfig(configFile string) (*gost.KCPConfig, error) { 713 | if configFile == "" { 714 | return nil, nil 715 | } 716 | file, err := os.Open(configFile) 717 | if err != nil { 718 | return nil, err 719 | } 720 | defer file.Close() 721 | 722 | config := &gost.KCPConfig{} 723 | if err = json.NewDecoder(file).Decode(config); err != nil { 724 | return nil, err 725 | } 726 | return config, nil 727 | } 728 | 729 | func parseUsers(authFile string) (users []*url.Userinfo, err error) { 730 | if authFile == "" { 731 | return 732 | } 733 | 734 | file, err := os.Open(authFile) 735 | if err != nil { 736 | return 737 | } 738 | scanner := bufio.NewScanner(file) 739 | for scanner.Scan() { 740 | line := strings.TrimSpace(scanner.Text()) 741 | if line == "" || strings.HasPrefix(line, "#") { 742 | continue 743 | } 744 | 745 | s := strings.SplitN(line, " ", 2) 746 | if len(s) == 1 { 747 | users = append(users, url.User(strings.TrimSpace(s[0]))) 748 | } else if len(s) == 2 { 749 | users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1]))) 750 | } 751 | } 752 | 753 | err = scanner.Err() 754 | return 755 | } 756 | 757 | func parseAuthenticator(s string) (gost.Authenticator, error) { 758 | if s == "" { 759 | return nil, nil 760 | } 761 | f, err := os.Open(s) 762 | if err != nil { 763 | return nil, err 764 | } 765 | defer f.Close() 766 | 767 | au := gost.NewLocalAuthenticator(nil) 768 | au.Reload(f) 769 | 770 | go gost.PeriodReload(au, s) 771 | 772 | return au, nil 773 | } 774 | 775 | func parseIP(s string, port string) (ips []string) { 776 | if s == "" { 777 | return 778 | } 779 | if port == "" { 780 | port = "8080" // default port 781 | } 782 | 783 | file, err := os.Open(s) 784 | if err != nil { 785 | ss := strings.Split(s, ",") 786 | for _, s := range ss { 787 | s = strings.TrimSpace(s) 788 | if s != "" { 789 | // TODO: support IPv6 790 | if !strings.Contains(s, ":") { 791 | s = s + ":" + port 792 | } 793 | ips = append(ips, s) 794 | } 795 | 796 | } 797 | return 798 | } 799 | 800 | scanner := bufio.NewScanner(file) 801 | for scanner.Scan() { 802 | line := strings.TrimSpace(scanner.Text()) 803 | if line == "" || strings.HasPrefix(line, "#") { 804 | continue 805 | } 806 | if !strings.Contains(line, ":") { 807 | line = line + ":" + port 808 | } 809 | ips = append(ips, line) 810 | } 811 | return 812 | } 813 | 814 | func parseBypass(s string) *gost.Bypass { 815 | if s == "" { 816 | return nil 817 | } 818 | var matchers []gost.Matcher 819 | var reversed bool 820 | if strings.HasPrefix(s, "~") { 821 | reversed = true 822 | s = strings.TrimLeft(s, "~") 823 | } 824 | 825 | f, err := os.Open(s) 826 | if err != nil { 827 | for _, s := range strings.Split(s, ",") { 828 | s = strings.TrimSpace(s) 829 | if s == "" { 830 | continue 831 | } 832 | matchers = append(matchers, gost.NewMatcher(s)) 833 | } 834 | return gost.NewBypass(reversed, matchers...) 835 | } 836 | defer f.Close() 837 | 838 | bp := gost.NewBypass(reversed) 839 | bp.Reload(f) 840 | go gost.PeriodReload(bp, s) 841 | 842 | return bp 843 | } 844 | 845 | func parseResolver(cfg string) gost.Resolver { 846 | if cfg == "" { 847 | return nil 848 | } 849 | var nss []gost.NameServer 850 | 851 | f, err := os.Open(cfg) 852 | if err != nil { 853 | for _, s := range strings.Split(cfg, ",") { 854 | s = strings.TrimSpace(s) 855 | if s == "" { 856 | continue 857 | } 858 | if strings.HasPrefix(s, "https") { 859 | p := "https" 860 | u, _ := url.Parse(s) 861 | if u == nil || u.Scheme == "" { 862 | continue 863 | } 864 | if u.Scheme == "https-chain" { 865 | p = u.Scheme 866 | } 867 | ns := gost.NameServer{ 868 | Addr: s, 869 | Protocol: p, 870 | } 871 | nss = append(nss, ns) 872 | continue 873 | } 874 | 875 | ss := strings.Split(s, "/") 876 | if len(ss) == 1 { 877 | ns := gost.NameServer{ 878 | Addr: ss[0], 879 | } 880 | nss = append(nss, ns) 881 | } 882 | if len(ss) == 2 { 883 | ns := gost.NameServer{ 884 | Addr: ss[0], 885 | Protocol: ss[1], 886 | } 887 | nss = append(nss, ns) 888 | } 889 | } 890 | return gost.NewResolver(0, nss...) 891 | } 892 | defer f.Close() 893 | 894 | resolver := gost.NewResolver(0) 895 | resolver.Reload(f) 896 | 897 | go gost.PeriodReload(resolver, cfg) 898 | 899 | return resolver 900 | } 901 | 902 | func parseHosts(s string) *gost.Hosts { 903 | f, err := os.Open(s) 904 | if err != nil { 905 | return nil 906 | } 907 | defer f.Close() 908 | 909 | hosts := gost.NewHosts() 910 | hosts.Reload(f) 911 | 912 | go gost.PeriodReload(hosts, s) 913 | 914 | return hosts 915 | } 916 | 917 | func parseIPRoutes(s string) (routes []gost.IPRoute) { 918 | if s == "" { 919 | return 920 | } 921 | 922 | file, err := os.Open(s) 923 | if err != nil { 924 | ss := strings.Split(s, ",") 925 | for _, s := range ss { 926 | if _, inet, _ := net.ParseCIDR(strings.TrimSpace(s)); inet != nil { 927 | routes = append(routes, gost.IPRoute{Dest: inet}) 928 | } 929 | } 930 | return 931 | } 932 | 933 | defer file.Close() 934 | scanner := bufio.NewScanner(file) 935 | for scanner.Scan() { 936 | line := strings.Replace(scanner.Text(), "\t", " ", -1) 937 | line = strings.TrimSpace(line) 938 | if line == "" || strings.HasPrefix(line, "#") { 939 | continue 940 | } 941 | 942 | var route gost.IPRoute 943 | var ss []string 944 | for _, s := range strings.Split(line, " ") { 945 | if s = strings.TrimSpace(s); s != "" { 946 | ss = append(ss, s) 947 | } 948 | } 949 | if len(ss) > 0 && ss[0] != "" { 950 | _, route.Dest, _ = net.ParseCIDR(strings.TrimSpace(ss[0])) 951 | if route.Dest == nil { 952 | continue 953 | } 954 | } 955 | if len(ss) > 1 && ss[1] != "" { 956 | route.Gateway = net.ParseIP(ss[1]) 957 | } 958 | routes = append(routes, route) 959 | } 960 | return routes 961 | } 962 | 963 | type peerConfig struct { 964 | Strategy string `json:"strategy"` 965 | MaxFails int `json:"max_fails"` 966 | FailTimeout time.Duration 967 | period time.Duration // the period for live reloading 968 | Nodes []string `json:"nodes"` 969 | group *gost.NodeGroup 970 | baseNodes []gost.Node 971 | stopped chan struct{} 972 | } 973 | 974 | func newPeerConfig() *peerConfig { 975 | return &peerConfig{ 976 | stopped: make(chan struct{}), 977 | } 978 | } 979 | 980 | func (cfg *peerConfig) Validate() { 981 | } 982 | 983 | func (cfg *peerConfig) Reload(r io.Reader) error { 984 | if cfg.Stopped() { 985 | return nil 986 | } 987 | 988 | if err := cfg.parse(r); err != nil { 989 | return err 990 | } 991 | cfg.Validate() 992 | 993 | group := cfg.group 994 | group.SetSelector( 995 | nil, 996 | gost.WithFilter( 997 | &gost.FailFilter{ 998 | MaxFails: cfg.MaxFails, 999 | FailTimeout: cfg.FailTimeout, 1000 | }, 1001 | &gost.InvalidFilter{}, 1002 | ), 1003 | gost.WithStrategy(gost.NewStrategy(cfg.Strategy)), 1004 | ) 1005 | 1006 | gNodes := cfg.baseNodes 1007 | nid := len(gNodes) + 1 1008 | for _, s := range cfg.Nodes { 1009 | nodes, err := parseChainNode(s) 1010 | if err != nil { 1011 | return err 1012 | } 1013 | 1014 | for i := range nodes { 1015 | nodes[i].ID = nid 1016 | nid++ 1017 | } 1018 | 1019 | gNodes = append(gNodes, nodes...) 1020 | } 1021 | 1022 | nodes := group.SetNodes(gNodes...) 1023 | for _, node := range nodes[len(cfg.baseNodes):] { 1024 | if node.Bypass != nil { 1025 | node.Bypass.Stop() // clear the old nodes 1026 | } 1027 | } 1028 | 1029 | return nil 1030 | } 1031 | 1032 | func (cfg *peerConfig) parse(r io.Reader) error { 1033 | data, err := ioutil.ReadAll(r) 1034 | if err != nil { 1035 | return err 1036 | } 1037 | 1038 | // compatible with JSON format 1039 | if err := json.NewDecoder(bytes.NewReader(data)).Decode(cfg); err == nil { 1040 | return nil 1041 | } 1042 | 1043 | split := func(line string) []string { 1044 | if line == "" { 1045 | return nil 1046 | } 1047 | if n := strings.IndexByte(line, '#'); n >= 0 { 1048 | line = line[:n] 1049 | } 1050 | line = strings.Replace(line, "\t", " ", -1) 1051 | line = strings.TrimSpace(line) 1052 | 1053 | var ss []string 1054 | for _, s := range strings.Split(line, " ") { 1055 | if s = strings.TrimSpace(s); s != "" { 1056 | ss = append(ss, s) 1057 | } 1058 | } 1059 | return ss 1060 | } 1061 | 1062 | cfg.Nodes = nil 1063 | scanner := bufio.NewScanner(bytes.NewReader(data)) 1064 | for scanner.Scan() { 1065 | line := scanner.Text() 1066 | ss := split(line) 1067 | if len(ss) < 2 { 1068 | continue 1069 | } 1070 | 1071 | switch ss[0] { 1072 | case "strategy": 1073 | cfg.Strategy = ss[1] 1074 | case "max_fails": 1075 | cfg.MaxFails, _ = strconv.Atoi(ss[1]) 1076 | case "fail_timeout": 1077 | cfg.FailTimeout, _ = time.ParseDuration(ss[1]) 1078 | case "reload": 1079 | cfg.period, _ = time.ParseDuration(ss[1]) 1080 | case "peer": 1081 | cfg.Nodes = append(cfg.Nodes, ss[1]) 1082 | } 1083 | } 1084 | 1085 | return scanner.Err() 1086 | } 1087 | 1088 | func (cfg *peerConfig) Period() time.Duration { 1089 | if cfg.Stopped() { 1090 | return -1 1091 | } 1092 | return cfg.period 1093 | } 1094 | 1095 | // Stop stops reloading. 1096 | func (cfg *peerConfig) Stop() { 1097 | select { 1098 | case <-cfg.stopped: 1099 | default: 1100 | close(cfg.stopped) 1101 | } 1102 | } 1103 | 1104 | // Stopped checks whether the reloader is stopped. 1105 | func (cfg *peerConfig) Stopped() bool { 1106 | select { 1107 | case <-cfg.stopped: 1108 | return true 1109 | default: 1110 | return false 1111 | } 1112 | } 1113 | -------------------------------------------------------------------------------- /pkg/server/infrastructure.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | // majority of this is borrowed from https://github.com/goadapp/goad/blob/master/infrastructure/infrastructure.go 4 | 5 | import ( 6 | "log" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/awserr" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/iam" 13 | "github.com/aws/aws-sdk-go/service/lambda" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | const ( 18 | lambdaFunctionHandler = "main" 19 | lambdaFunctionRuntime = "go1.x" 20 | lambdaFunctionZipLocation = "artifacts/lambda.zip" 21 | ) 22 | 23 | type lambdaInfrastructure struct { 24 | config *aws.Config 25 | name string 26 | iamRole string 27 | regions []string 28 | lambdaTimeout int64 29 | lambdaMemorySize int64 30 | } 31 | 32 | // SetupLambdaInfrastructure sets up IAM role needed to run awslambdaproxy 33 | func SetupLambdaInfrastructure(lambdaIamRole string) error { 34 | sess, err := GetSessionAWS() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | svc := iam.New(sess, &aws.Config{}) 40 | _, err = svc.GetRole(&iam.GetRoleInput{ 41 | RoleName: aws.String(lambdaIamRole), 42 | }) 43 | if err != nil { 44 | if awsErr, ok := err.(awserr.Error); ok { 45 | if awsErr.Code() == "NoSuchEntity" { 46 | _, err := svc.CreateRole(&iam.CreateRoleInput{ 47 | AssumeRolePolicyDocument: aws.String(`{ 48 | "Version": "2012-10-17", 49 | "Statement": { 50 | "Effect": "Allow", 51 | "Principal": {"Service": "lambda.amazonaws.com"}, 52 | "Action": "sts:AssumeRole" 53 | } 54 | }`), 55 | RoleName: aws.String(lambdaIamRole), 56 | Path: aws.String("/"), 57 | }) 58 | if err != nil { 59 | return err 60 | } 61 | _, err = svc.PutRolePolicy(&iam.PutRolePolicyInput{ 62 | PolicyDocument: aws.String(`{ 63 | "Version": "2012-10-17", 64 | "Statement": [ 65 | { 66 | "Action": [ 67 | "logs:CreateLogGroup", 68 | "logs:CreateLogStream", 69 | "logs:PutLogEvents" 70 | ], 71 | "Effect": "Allow", 72 | "Resource": "arn:aws:logs:*:*:*" 73 | } 74 | ] 75 | }`), 76 | PolicyName: aws.String(lambdaIamRole + "-policy"), 77 | RoleName: aws.String(lambdaIamRole), 78 | }) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | return nil 84 | } 85 | } else { 86 | return err 87 | } 88 | } else { 89 | log.Println("Setup has already been run successfully") 90 | return nil 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func (infra *lambdaInfrastructure) setup() error { 97 | sess, err := GetSessionAWS() 98 | if err != nil { 99 | return err 100 | } 101 | 102 | svc := iam.New(sess, infra.config) 103 | resp, err := svc.GetRole(&iam.GetRoleInput{ 104 | RoleName: aws.String(infra.iamRole), 105 | }) 106 | if err != nil { 107 | return errors.Wrap(err, "Could not find IAM role "+infra.iamRole+". Probably need to run setup.") 108 | } 109 | roleArn := *resp.Role.Arn 110 | zip, err := Asset(lambdaFunctionZipLocation) 111 | if err != nil { 112 | return errors.Wrap(err, "Could not read ZIP file: "+lambdaFunctionZipLocation) 113 | } 114 | for _, region := range infra.regions { 115 | log.Println("Setting up Lambda function in region: " + region) 116 | err = infra.createOrUpdateLambdaFunction(sess, infra.name, region, roleArn, zip) 117 | if err != nil { 118 | return errors.Wrap(err, "Could not create Lambda function in region "+region) 119 | } 120 | } 121 | return nil 122 | } 123 | 124 | func setupLambdaInfrastructure(name string, iamRole string, regions []string, memorySize int64, timeout int64) error { 125 | infra := lambdaInfrastructure{ 126 | name: name, 127 | iamRole: iamRole, 128 | regions: regions, 129 | config: &aws.Config{}, 130 | lambdaTimeout: timeout, 131 | lambdaMemorySize: memorySize, 132 | } 133 | if err := infra.setup(); err != nil { 134 | return errors.Wrap(err, "Could not setup Lambda Infrastructure") 135 | } 136 | return nil 137 | } 138 | 139 | func (infra *lambdaInfrastructure) createOrUpdateLambdaFunction(sess *session.Session, name, region, roleArn string, payload []byte) error { 140 | config := infra.config.WithRegion(region) 141 | 142 | svc := lambda.New(sess, config) 143 | exists, err := lambdaExists(svc, name) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | if exists { 149 | err := infra.deleteLambdaFunction(svc) 150 | if err != nil { 151 | return err 152 | } 153 | } 154 | 155 | return infra.createLambdaFunction(svc, roleArn, payload) 156 | } 157 | 158 | func (infra *lambdaInfrastructure) deleteLambdaFunction(svc *lambda.Lambda) error { 159 | _, err := svc.DeleteFunction(&lambda.DeleteFunctionInput{ 160 | FunctionName: aws.String(infra.name), 161 | }) 162 | if err != nil { 163 | return err 164 | } 165 | return nil 166 | } 167 | 168 | func (infra *lambdaInfrastructure) createLambdaFunction(svc *lambda.Lambda, roleArn string, payload []byte) error { 169 | _, err := svc.CreateFunction(&lambda.CreateFunctionInput{ 170 | Code: &lambda.FunctionCode{ 171 | ZipFile: payload, 172 | }, 173 | FunctionName: aws.String(infra.name), 174 | Handler: aws.String(lambdaFunctionHandler), 175 | Role: aws.String(roleArn), 176 | Runtime: aws.String(lambdaFunctionRuntime), 177 | MemorySize: aws.Int64(infra.lambdaMemorySize), 178 | Publish: aws.Bool(true), 179 | Timeout: aws.Int64(infra.lambdaTimeout), 180 | }) 181 | if err != nil { 182 | if awsErr, ok := err.(awserr.Error); ok { 183 | if awsErr.Code() == "InvalidParameterValueException" { 184 | time.Sleep(time.Second) 185 | return infra.createLambdaFunction(svc, roleArn, payload) 186 | } 187 | } 188 | return err 189 | } 190 | return nil 191 | } 192 | 193 | func lambdaExists(svc *lambda.Lambda, name string) (bool, error) { 194 | _, err := svc.GetFunction(&lambda.GetFunctionInput{ 195 | FunctionName: aws.String(name), 196 | }) 197 | 198 | if err != nil { 199 | if awsErr, ok := err.(awserr.Error); ok { 200 | if awsErr.Code() == "ResourceNotFoundException" { 201 | return false, nil 202 | } 203 | } 204 | return false, err 205 | } 206 | 207 | return true, nil 208 | } 209 | 210 | func (infra *lambdaInfrastructure) createIAMLambdaRole(sess *session.Session, roleName string) (arn string, err error) { 211 | svc := iam.New(sess, infra.config) 212 | resp, err := svc.GetRole(&iam.GetRoleInput{ 213 | RoleName: aws.String(roleName), 214 | }) 215 | if err != nil { 216 | if awsErr, ok := err.(awserr.Error); ok { 217 | if awsErr.Code() == "NoSuchEntity" { 218 | res, err := svc.CreateRole(&iam.CreateRoleInput{ 219 | AssumeRolePolicyDocument: aws.String(`{ 220 | "Version": "2012-10-17", 221 | "Statement": { 222 | "Effect": "Allow", 223 | "Principal": {"Service": "lambda.amazonaws.com"}, 224 | "Action": "sts:AssumeRole" 225 | } 226 | }`), 227 | RoleName: aws.String(roleName), 228 | Path: aws.String("/"), 229 | }) 230 | if err != nil { 231 | return "", err 232 | } 233 | if err := infra.createIAMLambdaRolePolicy(sess, *res.Role.RoleName); err != nil { 234 | return "", err 235 | } 236 | return *res.Role.Arn, nil 237 | } 238 | } 239 | return "", err 240 | } 241 | 242 | return *resp.Role.Arn, nil 243 | } 244 | 245 | func (infra *lambdaInfrastructure) createIAMLambdaRolePolicy(sess *session.Session, roleName string) error { 246 | svc := iam.New(sess, infra.config) 247 | _, err := svc.PutRolePolicy(&iam.PutRolePolicyInput{ 248 | PolicyDocument: aws.String(`{ 249 | "Version": "2012-10-17", 250 | "Statement": [ 251 | { 252 | "Action": [ 253 | "logs:CreateLogGroup", 254 | "logs:CreateLogStream", 255 | "logs:PutLogEvents" 256 | ], 257 | "Effect": "Allow", 258 | "Resource": "arn:aws:logs:*:*:*" 259 | } 260 | ] 261 | }`), 262 | PolicyName: aws.String(infra.iamRole + "-policy"), 263 | RoleName: aws.String(infra.iamRole), 264 | }) 265 | return err 266 | } 267 | -------------------------------------------------------------------------------- /pkg/server/lambdaexecution.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/service/lambda" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | type lambdaExecutionManager struct { 16 | name string 17 | regions []string 18 | frequency time.Duration 19 | publicIP string 20 | sshPort string 21 | sshKey string 22 | sshUser string 23 | } 24 | 25 | type lambdaPayload struct { 26 | UUID string 27 | ConnectBackAddress string 28 | SSHPort string 29 | SSHKey string 30 | SSHUser string 31 | } 32 | 33 | func (l *lambdaExecutionManager) run() { 34 | log.Println("Using public IP", l.publicIP) 35 | log.Println("Lambda execution frequency", l.frequency) 36 | count := 0 37 | setInvokeConfig := true 38 | for { 39 | if count > 0 { 40 | setInvokeConfig = false 41 | } 42 | for region := range l.regions { 43 | err := l.executeFunction(region, setInvokeConfig) 44 | if err != nil { 45 | log.Println(err) 46 | } 47 | time.Sleep(l.frequency) 48 | } 49 | count++ 50 | } 51 | } 52 | 53 | func (l *lambdaExecutionManager) executeFunction(region int, setInvokeConfig bool) error { 54 | log.Println("Executing Lambda function in region", l.regions[region]) 55 | sess, err := GetSessionAWS() 56 | if err != nil { 57 | return err 58 | } 59 | 60 | svc := lambda.New(sess, &aws.Config{Region: aws.String(l.regions[region])}) 61 | 62 | if setInvokeConfig { 63 | maximumEventAgeInSeconds := int64(1800) 64 | maximumRetryAttempts := int64(0) 65 | log.Printf("Setting invoke configuration maximumRetryAttempts=%v maximumEventAgeInSeconds=%v\n", 66 | maximumRetryAttempts, maximumEventAgeInSeconds) 67 | _, err = svc.PutFunctionEventInvokeConfig(&lambda.PutFunctionEventInvokeConfigInput{ 68 | FunctionName: aws.String(l.name), 69 | MaximumEventAgeInSeconds: aws.Int64(maximumEventAgeInSeconds), 70 | MaximumRetryAttempts: aws.Int64(maximumRetryAttempts), 71 | }) 72 | if err != nil { 73 | return err 74 | } 75 | } 76 | 77 | id, err := uuid.NewUUID() 78 | if err != nil { 79 | return err 80 | } 81 | lambdaPayload := lambdaPayload{ 82 | UUID: id.String(), 83 | ConnectBackAddress: l.publicIP, 84 | SSHPort: l.sshPort, 85 | SSHKey: l.sshKey, 86 | SSHUser: l.sshUser, 87 | } 88 | payload, _ := json.Marshal(lambdaPayload) 89 | params := &lambda.InvokeInput{ 90 | FunctionName: aws.String(l.name), 91 | InvocationType: aws.String(lambda.InvocationTypeEvent), 92 | Payload: payload, 93 | } 94 | 95 | log.Printf("Invoking Lambda function with UUID=%v\n", lambdaPayload.UUID) 96 | _, err = svc.Invoke(params) 97 | if err != nil { 98 | return errors.Wrap(err, "Failed to execute Lambda function") 99 | } 100 | return nil 101 | } 102 | 103 | func newLambdaExecutionManager(name string, publicIP string, regions []string, frequency time.Duration, sshUser string, sshPort string, 104 | privateKey []byte, onDemandExecution chan bool) (*lambdaExecutionManager, error) { 105 | executionManager := &lambdaExecutionManager{ 106 | name: name, 107 | regions: regions, 108 | frequency: frequency, 109 | publicIP: publicIP, 110 | sshPort: sshPort, 111 | sshKey: string(privateKey[:]), 112 | sshUser: sshUser, 113 | } 114 | go executionManager.run() 115 | 116 | go func() { 117 | for { 118 | <-onDemandExecution 119 | log.Println("Starting new tunnel as existing tunnel failed") 120 | err := executionManager.executeFunction(0, false) 121 | if err != nil { 122 | log.Println(err) 123 | } 124 | time.Sleep(time.Second * 5) 125 | } 126 | }() 127 | 128 | return executionManager, nil 129 | } 130 | -------------------------------------------------------------------------------- /pkg/server/localproxy.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ginuerzh/gost" 9 | ) 10 | 11 | const ( 12 | forwardProxy = "localhost:8082" 13 | ) 14 | 15 | // LocalProxy is proxy listener and where to forward 16 | type LocalProxy struct { 17 | listeners []string 18 | forwardProxy string 19 | } 20 | 21 | func (l *LocalProxy) run() { 22 | baseCfg := &baseConfig{} 23 | baseCfg.route.ChainNodes = []string{l.forwardProxy} 24 | baseCfg.route.ServeNodes = l.listeners 25 | 26 | cert, err := gost.GenCertificate() 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | tlsConfig := &tls.Config{ 31 | Certificates: []tls.Certificate{cert}, 32 | } 33 | gost.DefaultTLSConfig = tlsConfig 34 | 35 | var routers []router 36 | rts, err := baseCfg.route.GenRouters() 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | routers = append(routers, rts...) 41 | 42 | if len(routers) == 0 { 43 | log.Fatalln("invalid config", err) 44 | } 45 | for i := range routers { 46 | go routers[i].Serve() 47 | } 48 | } 49 | 50 | // NewLocalProxy starts a local proxy that will forward to proxy running in Lambda 51 | func NewLocalProxy(listeners []string, debugProxy bool, bypass string) (*LocalProxy, error) { 52 | if debugProxy { 53 | gost.SetLogger(&gost.LogLogger{}) 54 | } 55 | fproxy := forwardProxy 56 | if bypass != "" { 57 | fproxy += fmt.Sprintf("?bypass=%v", bypass) 58 | } 59 | l := &LocalProxy{ 60 | listeners: listeners, 61 | forwardProxy: fproxy, 62 | } 63 | go l.run() 64 | return l, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/server/publicip/awspublicip/awspublicip.go: -------------------------------------------------------------------------------- 1 | package awspublicip 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/dan-v/awslambdaproxy/pkg/server/publicip" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | const ( 14 | DefaultHTTPTimeout = time.Second * 10 15 | AWSProviderURL = "http://checkip.amazonaws.com/" 16 | ) 17 | 18 | type PublicIPClient struct { 19 | providerURL string 20 | httpClient *http.Client 21 | } 22 | 23 | func New() *PublicIPClient { 24 | return &PublicIPClient{ 25 | providerURL: AWSProviderURL, 26 | httpClient: &http.Client{ 27 | Timeout: DefaultHTTPTimeout, 28 | }, 29 | } 30 | } 31 | 32 | func (p *PublicIPClient) GetIP() (string, error) { 33 | resp, err := p.httpClient.Get(p.providerURL) 34 | if err != nil { 35 | return "", fmt.Errorf("http request to get ip address from %v failed: %w", p.providerURL, err) 36 | } 37 | defer resp.Body.Close() 38 | 39 | buf, err := ioutil.ReadAll(resp.Body) 40 | if err != nil { 41 | return "", fmt.Errorf("reading response body from %v failed: %w", p.providerURL, err) 42 | } 43 | 44 | ip := string(bytes.TrimSpace(buf)) 45 | if net.ParseIP(ip) == nil { 46 | return "", fmt.Errorf("unable to parse ip %v: %w", 47 | publicip.ErrInvalidIPAddress, publicip.ErrInvalidIPAddress) 48 | } 49 | return ip, nil 50 | } 51 | 52 | func (p *PublicIPClient) ProviderURL() string { 53 | return p.providerURL 54 | } 55 | -------------------------------------------------------------------------------- /pkg/server/publicip/awspublicip/awspublicip_test.go: -------------------------------------------------------------------------------- 1 | package awspublicip 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/dan-v/awslambdaproxy/pkg/server/publicip" 8 | "github.com/stretchr/testify/assert" 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func TestPublicIPClient_GetIP(t *testing.T) { 16 | tests := []struct { 17 | name string 18 | handler func(w http.ResponseWriter, r *http.Request) 19 | expected string 20 | error error 21 | }{ 22 | { 23 | name: "valid ip address returns as expected", 24 | handler: func(w http.ResponseWriter, r *http.Request) { 25 | fmt.Fprintln(w, "1.1.1.1") 26 | }, 27 | expected: "1.1.1.1", 28 | error: nil, 29 | }, 30 | { 31 | name: "valid ip address with padding returns trimmed", 32 | handler: func(w http.ResponseWriter, r *http.Request) { 33 | fmt.Fprintln(w, " 1.1.1.1 ") 34 | }, 35 | expected: "1.1.1.1", 36 | error: nil, 37 | }, 38 | { 39 | name: "invalid ip results in error", 40 | handler: func(w http.ResponseWriter, r *http.Request) { 41 | fmt.Fprintln(w, "invalid") 42 | }, 43 | expected: "", 44 | error: publicip.ErrInvalidIPAddress, 45 | }, 46 | { 47 | name: "empty response results in error", 48 | handler: func(w http.ResponseWriter, r *http.Request) { 49 | fmt.Fprintln(w, "") 50 | }, 51 | expected: "", 52 | error: publicip.ErrInvalidIPAddress, 53 | }, 54 | { 55 | name: "server timeout causes deadline exceeded error", 56 | handler: func(w http.ResponseWriter, r *http.Request) { 57 | time.Sleep(20 * time.Millisecond) 58 | }, 59 | expected: "", 60 | error: context.DeadlineExceeded, 61 | }, 62 | { 63 | name: "invalid content length causes body read error", 64 | handler: func(w http.ResponseWriter, r *http.Request) { 65 | w.Header().Set("Content-Length", "1") 66 | }, 67 | expected: "", 68 | error: errors.New("unexpected EOF"), 69 | }, 70 | } 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | ts := httptest.NewServer(http.HandlerFunc(tt.handler)) 74 | defer ts.Close() 75 | 76 | p := New() 77 | p.providerURL = ts.URL 78 | p.httpClient.Timeout = time.Millisecond * 10 79 | ip, err := p.GetIP() 80 | if tt.error != nil { 81 | assert.Contains(t, errors.Unwrap(err).Error(), tt.error.Error()) 82 | } else { 83 | assert.NoError(t, err) 84 | } 85 | assert.Equal(t, tt.expected, ip) 86 | }) 87 | } 88 | } 89 | 90 | func TestPublicIPClient_ProviderURL(t *testing.T) { 91 | p := New() 92 | assert.Equal(t, AWSProviderURL, p.ProviderURL()) 93 | } 94 | -------------------------------------------------------------------------------- /pkg/server/publicip/publicip.go: -------------------------------------------------------------------------------- 1 | package publicip 2 | 3 | import "errors" 4 | 5 | type Client interface { 6 | GetIP() (string, error) 7 | ProviderURL() string 8 | } 9 | 10 | var ( 11 | ErrInvalidIPAddress = errors.New("invalid ip address") 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | 8 | "github.com/dan-v/awslambdaproxy/pkg/server/publicip" 9 | "github.com/dan-v/awslambdaproxy/pkg/server/publicip/awspublicip" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | const ( 14 | // LambdaMinMemorySize is the minimum memory size for a Lambda function in MB 15 | LambdaMinMemorySize = 128 16 | // LambdaMaxMemorySize is the maximum memory size for a Lambda function in MB 17 | LambdaMaxMemorySize = 3008 18 | 19 | // LambdaDelayedCleanupTime is the time to wait if active connections still exist 20 | // this should be never be higher than LambdaExecutionTimeoutBuffer or function timeout 21 | // will happen before cleanup occurs 22 | LambdaDelayedCleanupTime = time.Second * 20 23 | // LambdaExecutionTimeoutBuffer is the time added to user specified execution frequency 24 | // to get the overall function timeout value 25 | LambdaExecutionTimeoutBuffer = time.Second * 30 26 | 27 | // LambdaMinExecutionFrequency is the minimum frequency for function execution. 28 | LambdaMinExecutionFrequency = time.Second * 60 29 | // LambdaMaxExecutionFrequency is the maximum frequency for function execution. 30 | // The current max execution time is 900 seconds, but this takes into account 31 | // LambdaExecutionTimeoutBuffer + 10 seconds of leeway 32 | LambdaMaxExecutionFrequency = time.Second * 860 33 | ) 34 | 35 | // Config is used to define the configuration for Server 36 | type Config struct { 37 | // LambdaName is a name of Lambda function 38 | LambdaName string 39 | // LambdaIamRoleName is a name of Lambda function IAM role 40 | LambdaIamRoleName string 41 | // LambdaRegions is all regions to execute Lambda functions in 42 | LambdaRegions []string 43 | // LambdaMemory is the size of memory to assign Lambda function 44 | LambdaMemory int 45 | // LambdaExecutionFrequency is the frequency at which to execute Lambda functions 46 | LambdaExecutionFrequency time.Duration 47 | // ProxyListeners defines all listeners, protocol, and auth information 48 | // in format like this [scheme://][user:pass@host]:port. 49 | // see https://github.com/ginuerzh/gost/blob/master/README_en.md#getting-started 50 | ProxyListeners []string 51 | // ProxyDebug is whether debug logging should be shown for proxy traffic 52 | // note this will log all visited domains 53 | ProxyDebug bool 54 | // ReverseTunnelSSHUser is the ssh user to use for the lambda reverse ssh tunnel 55 | ReverseTunnelSSHUser string 56 | // ReverseTunnelSSHPort is the ssh port to use for the lambda reverse ssh tunnel 57 | ReverseTunnelSSHPort string 58 | // Debug enables general debug logging 59 | Debug bool 60 | // Bypass is a comma separated list of ips/domains to bypass proxy 61 | Bypass string 62 | } 63 | 64 | // Server is the long running server component of awslambdaproxy 65 | type Server struct { 66 | publicIPClient publicip.Client 67 | lambdaName string 68 | lambdaIamRole string 69 | lambdaRegions []string 70 | lambdaMemory int64 71 | lambdaExecutionFrequency time.Duration 72 | lambdaTimeoutSeconds int64 73 | proxyListeners []string 74 | proxyDebug bool 75 | reverseTunnelSSHUser string 76 | reverseTunnelSSHPort string 77 | debug bool 78 | bypass string 79 | logger *logrus.Logger 80 | } 81 | 82 | func New(config Config) (*Server, error) { 83 | logger := logrus.New() 84 | if config.Debug { 85 | logger.SetLevel(logrus.DebugLevel) 86 | } 87 | 88 | err := validateConfig(config) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | functionTimeout := int(config.LambdaExecutionFrequency.Seconds()) + int(LambdaExecutionTimeoutBuffer.Seconds()) 94 | s := &Server{ 95 | publicIPClient: awspublicip.New(), 96 | lambdaName: config.LambdaName, 97 | lambdaIamRole: config.LambdaIamRoleName, 98 | lambdaRegions: config.LambdaRegions, 99 | lambdaMemory: int64(config.LambdaMemory), 100 | lambdaExecutionFrequency: config.LambdaExecutionFrequency, 101 | lambdaTimeoutSeconds: int64(functionTimeout), 102 | proxyListeners: config.ProxyListeners, 103 | proxyDebug: config.ProxyDebug, 104 | reverseTunnelSSHUser: config.ReverseTunnelSSHUser, 105 | reverseTunnelSSHPort: config.ReverseTunnelSSHPort, 106 | debug: config.Debug, 107 | bypass: config.Bypass, 108 | logger: logger, 109 | } 110 | 111 | logger.WithFields(logrus.Fields{ 112 | "publicIPClient": s.publicIPClient.ProviderURL(), 113 | "lambdaName": s.lambdaName, 114 | "lambdaIamRole": s.lambdaIamRole, 115 | "lambdaRegions": s.lambdaRegions, 116 | "lambdaMemory": s.lambdaMemory, 117 | "lambdaExecutionFrequency": s.lambdaExecutionFrequency, 118 | "lambdaTimeoutSeconds": s.lambdaTimeoutSeconds, 119 | "proxyListeners": s.proxyListeners, 120 | "proxyDebug": s.proxyDebug, 121 | "reverseTunnelSSHUser": s.reverseTunnelSSHUser, 122 | "reverseTunnelSSHPort": s.reverseTunnelSSHPort, 123 | "debug": s.debug, 124 | "bypass": s.bypass, 125 | }).Info("server has been configured with the following values") 126 | 127 | return s, nil 128 | } 129 | 130 | func (s *Server) Run() { 131 | publicIP, err := s.publicIPClient.GetIP() 132 | if err != nil { 133 | s.logger.WithError(err).Fatalf("error getting public IP address") 134 | } 135 | 136 | s.logger.Infof("setting up lambda infrastructure") 137 | err = setupLambdaInfrastructure(s.lambdaName, s.lambdaIamRole, s.lambdaRegions, s.lambdaMemory, s.lambdaTimeoutSeconds) 138 | if err != nil { 139 | s.logger.WithError(err).Fatalf("failed to setup lambda infrastructure") 140 | } 141 | 142 | s.logger.Infof("starting ssh tunnel manager") 143 | privateKey, err := NewSSHManager() 144 | if err != nil { 145 | s.logger.WithError(err).Fatalf("failed to setup ssh tunnel manager") 146 | } 147 | 148 | s.logger.Infof("starting local proxy") 149 | localProxy, err := NewLocalProxy(s.proxyListeners, s.proxyDebug, s.bypass) 150 | if err != nil { 151 | s.logger.WithError(err).Fatalf("failed to setup local proxy") 152 | } 153 | 154 | s.logger.Println("starting connection manager") 155 | tunnelConnectionManager, err := newTunnelConnectionManager(s.lambdaExecutionFrequency, localProxy) 156 | if err != nil { 157 | s.logger.WithError(err).Fatalf("failed to setup connection manager") 158 | } 159 | 160 | s.logger.Println("starting lambda execution manager") 161 | _, err = newLambdaExecutionManager(s.lambdaName, publicIP, s.lambdaRegions, s.lambdaExecutionFrequency, 162 | s.reverseTunnelSSHUser, s.reverseTunnelSSHPort, privateKey, tunnelConnectionManager.tunnelRedeployNeeded) 163 | if err != nil { 164 | s.logger.WithError(err).Fatalf("failed to setup lambda execution manager") 165 | } 166 | 167 | s.logger.Println("#######################################") 168 | s.logger.Println("proxy ip address: ", publicIP) 169 | s.logger.Println("listeners: ", s.proxyListeners) 170 | s.logger.Println("#######################################") 171 | 172 | runtime.Goexit() 173 | } 174 | 175 | func validateConfig(config Config) error { 176 | // validate memory 177 | if config.LambdaMemory < LambdaMinMemorySize || config.LambdaMemory > LambdaMaxMemorySize { 178 | return fmt.Errorf("invalid lambda memory size '%vMB' - should be between %v and %v", 179 | config.LambdaMemory, LambdaMinMemorySize, LambdaMaxMemorySize) 180 | } 181 | if config.LambdaMemory%64 != 0 { 182 | return fmt.Errorf("invalid lambda memory size '%vMB' - should be in increments of 64MB", 183 | config.LambdaMemory) 184 | } 185 | 186 | // validate frequency 187 | if config.LambdaExecutionFrequency < LambdaMinExecutionFrequency || config.LambdaExecutionFrequency > LambdaMaxExecutionFrequency { 188 | return fmt.Errorf("invalid lambda execution frequency '%v' - should be between %v and %v", 189 | config.LambdaExecutionFrequency, LambdaMinExecutionFrequency, LambdaMaxExecutionFrequency) 190 | } 191 | 192 | // validate ssh user and port 193 | if config.ReverseTunnelSSHUser == "" { 194 | return fmt.Errorf("need to specify ReverseTunnelSSHUser") 195 | } 196 | if config.ReverseTunnelSSHPort == "" { 197 | return fmt.Errorf("need to specify ReverseTunnelSSHPort") 198 | } 199 | 200 | // validate listeners 201 | if len(config.ProxyListeners) == 0 { 202 | return fmt.Errorf("no listener has been specified") 203 | } 204 | 205 | // validate regions 206 | validRegions := GetValidLambdaRegions() 207 | for _, region := range config.LambdaRegions { 208 | valid := false 209 | for _, validRegion := range validRegions { 210 | if region == validRegion { 211 | valid = true 212 | break 213 | } 214 | } 215 | if !valid { 216 | return fmt.Errorf("invalid region '%v' specified. valid regions: %v", 217 | region, validRegions) 218 | } 219 | } 220 | return nil 221 | } 222 | -------------------------------------------------------------------------------- /pkg/server/ssh.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "os/signal" 12 | "os/user" 13 | "strings" 14 | 15 | "github.com/pkg/errors" 16 | "golang.org/x/crypto/ssh" 17 | ) 18 | 19 | type sshManager struct { 20 | privateKey *rsa.PrivateKey 21 | } 22 | 23 | func (s *sshManager) getPrivateKeyBytes() []byte { 24 | return pem.EncodeToMemory( 25 | &pem.Block{ 26 | Type: "RSA PRIVATE KEY", 27 | Bytes: x509.MarshalPKCS1PrivateKey(s.privateKey), 28 | }, 29 | ) 30 | } 31 | 32 | func (s *sshManager) getPublicKeyBytes() []byte { 33 | publicKey, _ := ssh.NewPublicKey(&s.privateKey.PublicKey) 34 | return ssh.MarshalAuthorizedKey(publicKey) 35 | } 36 | 37 | func (s *sshManager) getPublicKeyString() string { 38 | return strings.Trim(string(s.getPublicKeyBytes()[:]), "\n") 39 | } 40 | 41 | func (s *sshManager) insertAuthorizedKey() error { 42 | f, err := os.OpenFile(s.getAuthorizedKeysFile(), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) 43 | if err != nil { 44 | return errors.Wrap(err, "Failed to open authorized_keys file") 45 | } 46 | defer f.Close() 47 | 48 | if _, err = f.Write(s.getPublicKeyBytes()); err != nil { 49 | errors.Wrap(err, "Failed to write authorized_keys file") 50 | } 51 | return nil 52 | } 53 | 54 | func (s *sshManager) removeAuthorizedKey() error { 55 | authorizedKeysBytes, err := ioutil.ReadFile(s.getAuthorizedKeysFile()) 56 | if err != nil { 57 | errors.Wrap(err, "Failed to read authorized_keys file") 58 | } 59 | 60 | lines := strings.Split(string(authorizedKeysBytes), "\n") 61 | 62 | for i, line := range lines { 63 | if line == s.getPublicKeyString() { 64 | log.Println("Removed entry to authorized_keys") 65 | lines[i] = "" 66 | } 67 | } 68 | 69 | output := strings.Join(lines, "\n") 70 | outputClean := strings.Replace(output, "\n\n", "\n", -1) 71 | err = ioutil.WriteFile(s.getAuthorizedKeysFile(), []byte(outputClean), 0644) 72 | if err != nil { 73 | errors.Wrap(err, "Failed to write authorized_keys file") 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func (s *sshManager) getAuthorizedKeysFile() string { 80 | usr, _ := user.Current() 81 | return usr.HomeDir + "/.ssh/authorized_keys" 82 | } 83 | 84 | // NewSSHManager generates an ssh key and adds to authorized_keys so Lambda can connect to the host 85 | func NewSSHManager() ([]byte, error) { 86 | privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 87 | if err != nil { 88 | return nil, errors.Wrap(err, "Error generating private SSH key") 89 | } 90 | s := &sshManager{ 91 | privateKey: privateKey, 92 | } 93 | log.Println("Generated SSH key: ", s.getPublicKeyString()) 94 | 95 | err = s.insertAuthorizedKey() 96 | if err != nil { 97 | return nil, errors.Wrap(err, "Error adding authorized key") 98 | } 99 | log.Println("Added entry to authorized keys file ", s.getAuthorizedKeysFile()) 100 | 101 | c := make(chan os.Signal, 1) 102 | signal.Notify(c, os.Interrupt) 103 | go func() { 104 | for sig := range c { 105 | log.Println("Shutting down due to ", sig.String()) 106 | log.Println("Cleaning up authorized_key file ", s.getAuthorizedKeysFile()) 107 | s.removeAuthorizedKey() 108 | os.Exit(0) 109 | } 110 | }() 111 | 112 | return s.getPrivateKeyBytes(), nil 113 | } 114 | -------------------------------------------------------------------------------- /pkg/server/tunnelconnection.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "strconv" 14 | 15 | "github.com/hashicorp/yamux" 16 | "github.com/pkg/errors" 17 | ) 18 | 19 | const ( 20 | checkIPURL = "http://checkip.amazonaws.com" 21 | maxTunnels = 10 22 | forwardPort = "8082" 23 | tunnelPort = "8081" 24 | ) 25 | 26 | type tunnelConnection struct { 27 | conn net.Conn 28 | sess *yamux.Session 29 | streams map[uint32]*yamux.Stream 30 | time time.Time 31 | } 32 | 33 | type connectionManager struct { 34 | forwardListener net.Listener 35 | tunnelListener net.Listener 36 | tunnelConnections map[string]tunnelConnection 37 | tunnelMutex sync.RWMutex 38 | tunnelExpectedRuntime float64 39 | tunnelRedeployNeeded chan bool 40 | activeTunnel string 41 | localProxy *LocalProxy 42 | } 43 | 44 | func (t *connectionManager) runForwarder() { 45 | t.waitUntilTunnelIsAvailable() 46 | for { 47 | c, err := t.forwardListener.Accept() 48 | if err != nil { 49 | log.Println("Failed to accept user connection") 50 | return 51 | } 52 | go t.handleForwardConnection(c) 53 | } 54 | } 55 | 56 | func (t *connectionManager) handleForwardConnection(localProxyConn net.Conn) { 57 | tunnelStream, err := t.openNewStreamInActiveTunnel() 58 | if err != nil { 59 | log.Println("Failed to open new stream in active tunnel", err) 60 | return 61 | } 62 | 63 | bidirectionalCopy(localProxyConn, tunnelStream) 64 | } 65 | 66 | func (t *connectionManager) runTunnel() { 67 | allLambdaIPs := map[string]int{} 68 | for { 69 | if len(t.tunnelConnections) > maxTunnels { 70 | log.Println("Too many active tunnelConnections: " + string(len(t.tunnelConnections)) + ". MAX=" + 71 | string(maxTunnels) + ". Waiting for cleanup.") 72 | time.Sleep(time.Second * 5) 73 | continue 74 | } 75 | c, err := t.tunnelListener.Accept() 76 | if err != nil { 77 | log.Println("Failed to accept tunnel connection") 78 | time.Sleep(time.Second * 5) 79 | continue 80 | } 81 | log.Println("Accepted tunnel connection from", c.RemoteAddr()) 82 | 83 | tunnelSession, err := yamux.Client(c, nil) 84 | if err != nil { 85 | log.Println("Failed to start session inside tunnel") 86 | time.Sleep(time.Second * 5) 87 | continue 88 | } 89 | log.Println("Established session to", tunnelSession.RemoteAddr()) 90 | 91 | t.tunnelMutex.Lock() 92 | t.activeTunnel = c.RemoteAddr().String() 93 | t.tunnelConnections[t.activeTunnel] = tunnelConnection{ 94 | conn: c, 95 | sess: tunnelSession, 96 | streams: make(map[uint32]*yamux.Stream), 97 | time: time.Now(), 98 | } 99 | t.tunnelMutex.Unlock() 100 | 101 | go t.monitorTunnelSessionHealth(t.activeTunnel) 102 | 103 | externalIP, err := t.getLambdaExternalIP() 104 | if err != nil { 105 | log.Println("Failed to check ip address:", err) 106 | } else { 107 | allLambdaIPs[externalIP] += 1 108 | } 109 | 110 | log.Println("---------------") 111 | log.Println("Current Lambda IP Address: ", externalIP) 112 | log.Println("Active Lambda tunnel count: ", len(t.tunnelConnections)) 113 | count := 1 114 | for k, v := range t.tunnelConnections { 115 | log.Printf("Lambda Tunnel #%v\n", count) 116 | log.Println(" Connection ID: " + k) 117 | log.Println(" Start Time: " + v.time.Format("2006-01-02T15:04:05")) 118 | log.Println(" Active Streams: " + strconv.Itoa(v.sess.NumStreams())) 119 | count++ 120 | } 121 | ips := make([]string, 0, len(allLambdaIPs)) 122 | for k := range allLambdaIPs { 123 | ips = append(ips, k) 124 | } 125 | log.Printf("%v Unique Lambda IPs used so far\n", len(allLambdaIPs)) 126 | log.Println("---------------") 127 | } 128 | } 129 | 130 | func (t *connectionManager) getLambdaExternalIP() (string, error) { 131 | proxyURL, err := url.Parse("http://localhost:" + forwardPort) 132 | if err != nil { 133 | return "", err 134 | } 135 | 136 | ipURL, err := url.Parse(checkIPURL) 137 | if err != nil { 138 | return "", err 139 | } 140 | 141 | transport := &http.Transport{ 142 | Proxy: http.ProxyURL(proxyURL), 143 | } 144 | client := &http.Client{ 145 | Transport: transport, 146 | } 147 | request, err := http.NewRequest("GET", ipURL.String(), nil) 148 | if err != nil { 149 | return "", err 150 | } 151 | response, err := client.Do(request) 152 | if err != nil { 153 | return "", err 154 | } 155 | 156 | defer response.Body.Close() 157 | data, err := ioutil.ReadAll(response.Body) 158 | if err != nil { 159 | return "", err 160 | } 161 | return strings.TrimSpace(string(data)), nil 162 | } 163 | 164 | func (t *connectionManager) removeTunnelConnection(connectionID string) { 165 | err := t.tunnelConnections[connectionID].sess.Close() 166 | if err != nil { 167 | log.Printf("error closing session for connectionID=%v: %v", connectionID, err) 168 | } 169 | err = t.tunnelConnections[connectionID].conn.Close() 170 | if err != nil { 171 | log.Printf("error closing connection for connectionID=%v: %v", connectionID, err) 172 | } 173 | t.tunnelMutex.Lock() 174 | delete(t.tunnelConnections, connectionID) 175 | t.tunnelMutex.Unlock() 176 | } 177 | 178 | func (t *connectionManager) monitorTunnelSessionHealth(connectionID string) { 179 | for { 180 | _, err := t.tunnelConnections[connectionID].sess.Ping() 181 | if err != nil { 182 | if time.Since(t.tunnelConnections[connectionID].time).Seconds() < t.tunnelExpectedRuntime { 183 | log.Println("Signaling for emergency tunnel due to tunnel ending early: ", time.Since(t.tunnelConnections[connectionID].time).Seconds()) 184 | t.tunnelRedeployNeeded <- true 185 | } 186 | t.removeTunnelConnection(connectionID) 187 | break 188 | } 189 | if time.Since(t.tunnelConnections[connectionID].time).Seconds() > t.tunnelExpectedRuntime { 190 | numStreams := t.tunnelConnections[connectionID].sess.NumStreams() 191 | if numStreams > 0 { 192 | log.Printf("Tunnel '%v' that is being closed still has %v open streams. "+ 193 | "Delaying cleanup for %v.\n", 194 | connectionID, strconv.Itoa(numStreams), LambdaDelayedCleanupTime.String()) 195 | time.Sleep(LambdaDelayedCleanupTime) 196 | log.Println("Delayed cleanup now running for ", connectionID) 197 | } else { 198 | log.Println("Tunnel " + connectionID + " is safe to close") 199 | } 200 | log.Println("Removing tunnel", connectionID) 201 | t.removeTunnelConnection(connectionID) 202 | break 203 | } 204 | time.Sleep(time.Millisecond * 50) 205 | } 206 | } 207 | 208 | func (t *connectionManager) openNewStreamInActiveTunnel() (*yamux.Stream, error) { 209 | for { 210 | t.tunnelMutex.RLock() 211 | tunnel, ok := t.tunnelConnections[t.activeTunnel] 212 | t.tunnelMutex.RUnlock() 213 | if ok { 214 | stream, err := tunnel.sess.OpenStream() 215 | tunnel.streams[stream.StreamID()] = stream 216 | return stream, err 217 | } 218 | log.Println("No active tunnel session available. Retrying..") 219 | time.Sleep(time.Second) 220 | } 221 | } 222 | 223 | func (t *connectionManager) waitUntilTunnelIsAvailable() error { 224 | timeout := time.After(time.Second * time.Duration(t.tunnelExpectedRuntime)) 225 | tick := time.Tick(time.Second) 226 | for { 227 | select { 228 | case <-timeout: 229 | return errors.New("Timed out waiting for tunnel to be established. Likely the " + 230 | "Lambda function is having issues communicating with this host.") 231 | case <-tick: 232 | if t.isReady() == true { 233 | return nil 234 | } 235 | log.Println("Waiting for tunnel to be established..") 236 | } 237 | } 238 | } 239 | 240 | func (t *connectionManager) isReady() bool { 241 | if t.activeTunnel == "" { 242 | return false 243 | } 244 | return true 245 | } 246 | 247 | func newTunnelConnectionManager(frequency time.Duration, localProxy *LocalProxy) (*connectionManager, error) { 248 | forwardListener, err := startForwardListener() 249 | if err != nil { 250 | return nil, errors.Wrap(err, "Failed to start UserListener") 251 | } 252 | 253 | tunnelListener, err := startTunnelListener() 254 | if err != nil { 255 | return nil, errors.Wrap(err, "Failed to start TunnelListener") 256 | } 257 | 258 | connectionManager := &connectionManager{ 259 | forwardListener: forwardListener, 260 | tunnelListener: tunnelListener, 261 | tunnelConnections: make(map[string]tunnelConnection), 262 | tunnelRedeployNeeded: make(chan bool), 263 | tunnelExpectedRuntime: frequency.Seconds(), 264 | localProxy: localProxy, 265 | } 266 | 267 | go connectionManager.runTunnel() 268 | go connectionManager.runForwarder() 269 | 270 | return connectionManager, nil 271 | } 272 | 273 | func startTunnelListener() (net.Listener, error) { 274 | tunnelAddress := "localhost:" + tunnelPort 275 | tunnelListener, err := net.Listen("tcp", tunnelAddress) 276 | 277 | if err != nil { 278 | return nil, errors.Wrap(err, "Failed to start TCP tunnel listener on port "+tunnelPort) 279 | } 280 | log.Println("Started tunnel listener on port " + tunnelPort) 281 | return tunnelListener, nil 282 | } 283 | 284 | func startForwardListener() (net.Listener, error) { 285 | forwardAddress := "localhost:" + forwardPort 286 | forwardListener, err := net.Listen("tcp", forwardAddress) 287 | 288 | if err != nil { 289 | return nil, errors.Wrap(err, "Failed to start TCP user listener on port "+forwardPort) 290 | } 291 | log.Println("Started user listener on port " + forwardPort) 292 | return forwardListener, nil 293 | } 294 | -------------------------------------------------------------------------------- /pkg/server/util.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | 7 | "github.com/aws/aws-sdk-go/aws/endpoints" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | ) 12 | 13 | func bidirectionalCopy(src io.ReadWriteCloser, dst io.ReadWriteCloser) { 14 | defer dst.Close() 15 | defer src.Close() 16 | 17 | var wg sync.WaitGroup 18 | wg.Add(1) 19 | go func() { 20 | io.Copy(dst, src) 21 | wg.Done() 22 | }() 23 | 24 | wg.Add(1) 25 | go func() { 26 | io.Copy(src, dst) 27 | wg.Done() 28 | }() 29 | wg.Wait() 30 | } 31 | 32 | func GetSessionAWS() (*session.Session, error) { 33 | sess, err := session.NewSession(aws.NewConfig()) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if _, err = sess.Config.Credentials.Get(); err != nil { 38 | return nil, err 39 | } 40 | return sess, nil 41 | } 42 | 43 | func GetValidLambdaRegions() []string { 44 | resolver := endpoints.DefaultResolver() 45 | partitions := resolver.(endpoints.EnumPartitions).Partitions() 46 | var validLambdaRegions []string 47 | for _, p := range partitions { 48 | if p.ID() == "aws" { 49 | for k := range p.Regions() { 50 | validLambdaRegions = append(validLambdaRegions, k) 51 | } 52 | } 53 | } 54 | return validLambdaRegions 55 | } 56 | --------------------------------------------------------------------------------