Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Commit c9f707e

Browse files
authored
Merge pull request #2 from dan-v/ssh-auth
Security improvements
2 parents f42a5c3 + fccc077 commit c9f707e

File tree

18 files changed

+459
-190
lines changed

18 files changed

+459
-190
lines changed

README.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
<b>awslambdaproxy</b> is an [AWS Lambda](https://aws.amazon.com/lambda/) powered HTTP(s)/SOCKS web proxy. It provides a constantly rotating IP address for your web traffic from all regions where AWS Lambda is available. The goal is to obfuscate your web traffic and make it harder to track you as a user.
1+
<b>awslambdaproxy</b> is an [AWS Lambda](https://aws.amazon.com/lambda/) powered SOCKS web proxy. It provides a constantly rotating IP address for your web traffic from all regions where AWS Lambda is available. The goal is to obfuscate your web traffic and make it harder to track you as a user.
22

33
![](/images/overview.gif?raw=true)
44

55
## Features
6-
* HTTP(s) and SOCKS5 proxy support.
7-
* No special software required. Just configure your system to use an HTTP or SOCKS proxy.
6+
* SOCKS5 proxy support with username/password authentication.
7+
* No special software required. Just configure your system to use a SOCKS proxy.
88
* Each AWS Lambda region provides 1 outgoing IP address that gets rotated roughly every 4 hours. That means if you use 10 AWS regions, you'll get 60 unique IPs per day.
9-
* Configurable IP rotation frequency between multiple regions. By default IP will rotate to new region every 3 minutes.
9+
* Configurable IP rotation frequency between multiple regions.
1010
* Personal proxy server not shared with anyone else.
1111
* Mostly [AWS free tier](https://aws.amazon.com/free/) compatible (see FAQ below).
1212

1313
## Project status
14-
Current code status: <b>proof of concept</b>. This is the first Go application that I've ever written. It has no tests. Listening endpoints have no security yet. It may not work. It may blow up. Use at your own risk.
14+
Current code status: <b>proof of concept</b>. 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.
1515

1616
## How it works
17-
At a high level, awslambdaproxy proxies HTTP(s) 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 simple HTTP ([elazarl/goproxy](https://github.com/elazarl/goproxy)) or SOCKS ([armon/go-socks5](https://github.com/armon/go-socks5)) proxy. Since Lambda does not allow you to bind ports in your executing functions, the proxy server is bound to a unix socket and a reverse tunnel is established from the Lambda function to port 8081 on the host running awslambdaproxy. Once a tunnel connection is established, all user web traffic is forwarded from port 8080 through this proxy. Lambda functions have a max execution time of 5 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.
17+
At a high level, awslambdaproxy proxies TCP 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 simple SOCKS ([armon/go-socks5](https://github.com/armon/go-socks5)) proxy server. Since Lambda does not allow you to bind ports in your executing functions, the proxy server is bound to a unix socket and a reverse SSH tunnel is established from the Lambda function to the host running awslambdaproxy. Once a tunnel connection is established, all user web traffic is forwarded from port 8080 through this reverse tunnel to the proxy server. Lambda functions have a max execution time of 5 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.
1818

1919
![](/images/how-it-works.png?raw=true)
2020

@@ -48,17 +48,17 @@ The easiest way is to download a pre-built binary from the [GitHub Releases](htt
4848
1. Copy `awslambdaproxy` binary to a publicly accessible linux host (e.g. EC2 instance). You will need to open the following ports on this host:
4949

5050
* Port 8080 - this port listens for user proxy connections and needs to only be opened to whatever your external IP address is where you plan to browse the web.
51-
* Port 8081 - this port listens for tunnel connections from executing Lambda functions and needs to be opened to the world. <b>This is a security concern and will be locked down in the future.</b>
51+
* Alternatively, a more secure option would be to not open any ports and setup a VPN connection to this host. A quick VPN solution for a setup like this may be [algo](https://github.com/trailofbits/algo).
5252

5353
2. On publicly accessible host, run `awslambdaproxy`. You'll need to ensure AWS access key and secret key environment variables are defined. For now, this access key should have AdministratorAccess.
5454

5555
```sh
5656
export AWS_ACCESS_KEY_ID=XXXXXXXXXX
5757
export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYY
58-
./awslambdaproxy -regions us-west-2,us-west-1,us-east-1,us-east-2 -proxy-type socks
58+
./awslambdaproxy -lambda-regions us-west-2,us-west-1,us-east-1,us-east-2
5959
```
6060

61-
3. Configure your web browser (or OS) to use the HTTP/HTTPS or SOCKS5 proxy (depending on -proxy-type selection) on the publicly accessible host running `awslambdaproxy` on port 8080.
61+
3. Configure your web browser (or OS) to use the SOCKS5 proxy on the publicly accessible host running `awslambdaproxy` on port 8080.
6262

6363
## FAQ
6464
1. <b>Should I use awslambdaproxy?</b> That's up to you. Use at your own risk.
@@ -67,16 +67,14 @@ The easiest way is to download a pre-built binary from the [GitHub Releases](htt
6767
4. <b>Will this make me completely anonymous?</b> 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.
6868
5. <b>How often will my external IP address change?</b> For each region specified, the IP address will change roughly every 4 hours. This of course is subject to change at any moment as this is not something that is documented by AWS Lambda.
6969
6. <b>How much does this cost?</b> 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.
70-
7. <b>Why does my connection drop periodically?</b> AWS Lambda functions can currently only execute for a maximum of 5 minutes. In order to maintain an ongoing HTTP proxy a new function is executed and all new traffic is cut over to it. Any ongoing connections to 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 may see issues.
70+
7. <b>Why does my connection drop periodically?</b> AWS Lambda functions can currently only execute for a maximum of 5 minutes. In order to maintain an ongoing HTTP 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 may see issues.
7171
7272
# Powered by
73-
* [goproxy](https://github.com/elazarl/goproxy) - An HTTP proxy server written in Go.
7473
* [go-socks5](https://github.com/armon/go-socks5) - A SOCKS5 proxy written in Go.
7574
* [yamux](https://github.com/hashicorp/yamux) - Golang connection multiplexing library.
7675
* [goad](https://github.com/goadapp/goad) - Code was borrowed from this project to handle AWS Lambda zip creation and function upload.
7776
7877
# Future work
79-
* Add security to proxy and tunnel connections
8078
* Fix connections dropping each time a new tunnel is established
8179
* Create minimal IAM policy
82-
* Rewrite code to be testable and write tests
80+
* Refactor code and write tests

cmd/awslambdaproxy/awslambdaproxy.go

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,102 @@ import (
55
"flag"
66
"os"
77
"strings"
8-
"fmt"
98
"time"
9+
"os/user"
10+
"log"
11+
"strconv"
1012
)
1113

1214
const (
13-
maxFrequency = "255"
15+
// Max execution time on lambda is 300 seconds currently
16+
lambdaMaxFrequencySeconds = 290
17+
lambdaMinMemorySize = 128
18+
lambdaMaxMemorySize = 1536
19+
lambdaDefaultMemorySize = lambdaMinMemorySize
1420
)
1521

1622
func main() {
17-
regionsPtr := flag.String("regions", "us-west-2", "Regions to run proxy (e.g. us-west-2) (can be comma separated list)")
18-
frequencyPtr := flag.Int( "frequency", 180, "Frequency in seconds to execute Lambda function. If multiple regions are specified, this will cause traffic to rotate round robin at the interval specified here")
19-
proxyTypePtr := flag.String("proxy-type", "http", "Proxy type to setup: 'http' or 'socks'")
20-
proxyPortPtr := flag.String("proxy-port", "8080", "Port to listen for proxy connections")
21-
tunnelPortPtr := flag.String("tunnel-port", "8081", "Port to listen for reverse connection from Lambda")
23+
user, err := user.Current()
24+
if err != nil {
25+
log.Println("Failed to get current username")
26+
os.Exit(1)
27+
}
28+
29+
lambdaRegionsPtr := flag.String("lambda-regions", "us-west-2", "Regions to run proxy " +
30+
"(e.g. us-west-2) (can be comma separated list)")
31+
lambdaFrequencyPtr := flag.Int("lambda-frequency", lambdaMaxFrequencySeconds, "Frequency in " +
32+
"seconds to execute Lambda function. Max=" + strconv.Itoa(lambdaMaxFrequencySeconds) + ". If multiple " +
33+
"lambda-regions are specified, this will cause traffic to rotate round robin at the interval " +
34+
"specified here")
35+
lambdaMemorySizePtr := flag.Int("lambda-memory", lambdaDefaultMemorySize, "Memory size in MB "+
36+
"for Lambda function. Higher memory may allow for faster network throughput.")
37+
sshUserPtr := flag.String("ssh-user", user.Username, "SSH user for tunnel connections from Lambda")
38+
sshPortPtr := flag.String("ssh-port", "22", "SSH port for tunnel connections from Lambda")
39+
proxyPortPtr := flag.String("proxy-port", "8080", "Port to listen for client socks proxy "+
40+
"connections")
41+
proxyUsernamePtr := flag.String("proxy-username", "admin", "Username for proxy authentication")
42+
proxyPasswordPtr := flag.String("proxy-password", "admin", "Password for proxy authentication")
2243
flag.Parse()
2344

2445
if *proxyPortPtr == "" {
2546
flag.PrintDefaults()
2647
os.Exit(1)
2748
}
28-
if *tunnelPortPtr == "" {
49+
if *lambdaRegionsPtr == "" {
50+
flag.PrintDefaults()
51+
os.Exit(1)
52+
}
53+
if *sshUserPtr == "" {
54+
flag.PrintDefaults()
55+
os.Exit(1)
56+
}
57+
if *sshPortPtr == "" {
2958
flag.PrintDefaults()
3059
os.Exit(1)
3160
}
32-
if *regionsPtr == "" {
61+
if *proxyUsernamePtr == "" {
3362
flag.PrintDefaults()
3463
os.Exit(1)
3564
}
36-
if *proxyTypePtr != "http" && *proxyTypePtr != "socks" {
65+
if *proxyPasswordPtr == "" {
3766
flag.PrintDefaults()
3867
os.Exit(1)
3968
}
4069

41-
// handle frequency
42-
if *frequencyPtr > 255 {
43-
fmt.Println("Maximum freqency is " + maxFrequency + " seconds")
70+
// check memory
71+
if *lambdaMemorySizePtr > lambdaMaxMemorySize {
72+
log.Println("Maximum lambda memory size is " + strconv.Itoa(lambdaMaxMemorySize) + " MB")
73+
os.Exit(1)
74+
}
75+
if *lambdaMemorySizePtr < lambdaMinMemorySize {
76+
log.Println("Minimum lambda memory size is " + strconv.Itoa(lambdaMinMemorySize) + " MB")
77+
os.Exit(1)
78+
}
79+
lambdaMemorySize := int64(*lambdaMemorySizePtr)
80+
81+
// check frequency
82+
if *lambdaFrequencyPtr > lambdaMaxFrequencySeconds {
83+
log.Println("Maximum lambda frequency is " + strconv.Itoa(lambdaMaxFrequencySeconds) + " seconds")
4484
os.Exit(1)
4585
}
46-
frequencySeconds := time.Second * time.Duration(*frequencyPtr)
86+
lambdaFrequencySeconds := time.Second * time.Duration(*lambdaFrequencyPtr)
87+
lambdaExecutionTimeout := int64(lambdaFrequencySeconds.Seconds()) + int64(10)
4788

48-
// handle aws env variables
89+
// check for required aws keys
4990
access := os.Getenv("AWS_ACCESS_KEY_ID")
5091
if access == "" {
51-
fmt.Println("Must specify environment variable AWS_ACCESS_KEY_ID")
92+
log.Println("Must specify environment variable AWS_ACCESS_KEY_ID")
5293
os.Exit(1)
5394
}
5495
secret := os.Getenv("AWS_SECRET_ACCESS_KEY")
5596
if secret == "" {
56-
fmt.Println("Must specify environment variable AWS_SECRET_ACCESS_KEY")
97+
log.Println("Must specify environment variable AWS_SECRET_ACCESS_KEY")
5798
os.Exit(1)
5899
}
59100

60-
regions := strings.Split(*regionsPtr, ",")
61-
awslambdaproxy.ServerInit(*proxyPortPtr, *tunnelPortPtr, regions, frequencySeconds, *proxyTypePtr)
62-
}
101+
// handle regions
102+
lambdaRegions := strings.Split(*lambdaRegionsPtr, ",")
63103

104+
awslambdaproxy.ServerInit(*proxyPortPtr, *sshUserPtr, *sshPortPtr, *proxyUsernamePtr, *proxyPasswordPtr,
105+
lambdaRegions, lambdaMemorySize, lambdaFrequencySeconds, lambdaExecutionTimeout)
106+
}

data/lambda/main.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ def handler(event, context):
99
logger.info("Event: {}".format(event))
1010
logger.info("Context: {}".format(context))
1111
address = event['ConnectBackAddress']
12-
proxy_type = event['ProxyType']
12+
ssh_port = event['SSHPort']
13+
ssh_key = event['SSHKey']
14+
ssh_user = event['SSHUser']
15+
proxy_username = event['ProxyUsername']
16+
proxy_password = event['ProxyPassword']
1317

14-
command = "./awslambdaproxy-lambda -address {} -proxy-type {}".format(address, proxy_type)
18+
key_filename = "/tmp/privatekey"
19+
with open(key_filename, 'w') as key_file:
20+
key_file.write(ssh_key)
21+
22+
command = "./awslambdaproxy-lambda -address {} -ssh-port {} -ssh-private-key {} -ssh-user {} -proxy-username {} -proxy-password {}".format(address, ssh_port, key_filename, ssh_user, proxy_username, proxy_password)
1523
logger.info("Running: {}".format(command))
1624
try:
1725
proc = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)

glide.lock

Lines changed: 12 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glide.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import:
88
- aws/session
99
- service/iam
1010
- service/lambda
11-
- package: github.com/elazarl/goproxy
12-
version: ^1.0.0
1311
- package: github.com/hashicorp/yamux
1412
- package: github.com/pkg/errors
1513
version: ^0.8.0

images/how-it-works.png

29.5 KB
Loading

images/overview.gif

-1.81 MB
Loading

infrastructure.go

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package awslambdaproxy
44

55
import (
66
"time"
7+
"log"
78

89
"github.com/pkg/errors"
910
"github.com/aws/aws-sdk-go/aws"
@@ -20,13 +21,13 @@ const (
2021
lambdaFunctionIamRole = "awslambdaproxy-role"
2122
lambdaFunctionIamRolePolicyName = "awslambdaproxy-role-policy"
2223
lambdaFunctionZipLocation = "data/lambda.zip"
23-
lambdaFunctionMemory = 128
2424
)
2525

2626
type LambdaInfrastructure struct {
27-
config *aws.Config
28-
regions []string
29-
timeout int64
27+
config *aws.Config
28+
regions []string
29+
lambdaTimeout int64
30+
lambdaMemorySize int64
3031
}
3132

3233
func (infra *LambdaInfrastructure) setup() error {
@@ -39,19 +40,21 @@ func (infra *LambdaInfrastructure) setup() error {
3940
return errors.Wrap(err, "Could not read ZIP file: " + lambdaFunctionZipLocation)
4041
}
4142
for _, region := range infra.regions {
43+
log.Println("Setting up Lambda function in region: " + region)
4244
err = infra.createOrUpdateLambdaFunction(region, roleArn, zip)
4345
if err != nil {
44-
return errors.Wrap(err, "Could not create/update Lambda function")
46+
return errors.Wrap(err, "Could not create Lambda function in region " + region)
4547
}
4648
}
4749
return nil
4850
}
4951

50-
func setupLambdaInfrastructure(regions []string, timeout int64) (error) {
52+
func setupLambdaInfrastructure(regions []string, memorySize int64, timeout int64) (error) {
5153
infra := LambdaInfrastructure{
52-
regions: regions,
53-
config: &aws.Config{},
54-
timeout: timeout,
54+
regions: regions,
55+
config: &aws.Config{},
56+
lambdaTimeout: timeout,
57+
lambdaMemorySize: memorySize,
5558
}
5659
if err := infra.setup(); err != nil {
5760
return errors.Wrap(err, "Could not setup Lambda Infrastructure")
@@ -64,22 +67,30 @@ func (infra *LambdaInfrastructure) createOrUpdateLambdaFunction(region, roleArn
6467
svc := lambda.New(session.New(), config)
6568

6669
exists, err := lambdaExists(svc)
67-
6870
if err != nil {
6971
return err
7072
}
7173

7274
if exists {
73-
aliasExists, err := lambdaAliasExists(svc)
74-
if err != nil || aliasExists {
75+
err := infra.deleteLambdaFunction(svc)
76+
if err != nil {
7577
return err
7678
}
77-
return infra.updateLambdaFunction(svc, roleArn, payload)
7879
}
7980

8081
return infra.createLambdaFunction(svc, roleArn, payload)
8182
}
8283

84+
func (infra *LambdaInfrastructure) deleteLambdaFunction(svc *lambda.Lambda) error {
85+
_, err := svc.DeleteFunction(&lambda.DeleteFunctionInput{
86+
FunctionName: aws.String(lambdaFunctionName),
87+
})
88+
if err != nil {
89+
return err
90+
}
91+
return nil
92+
}
93+
8394
func (infra *LambdaInfrastructure) createLambdaFunction(svc *lambda.Lambda, roleArn string, payload []byte) error {
8495
function, err := svc.CreateFunction(&lambda.CreateFunctionInput{
8596
Code: &lambda.FunctionCode{
@@ -89,9 +100,9 @@ func (infra *LambdaInfrastructure) createLambdaFunction(svc *lambda.Lambda, role
89100
Handler: aws.String(lambdaFunctionHandler),
90101
Role: aws.String(roleArn),
91102
Runtime: aws.String(lambdaFunctionRuntime),
92-
MemorySize: aws.Int64(lambdaFunctionMemory),
103+
MemorySize: aws.Int64(infra.lambdaMemorySize),
93104
Publish: aws.Bool(true),
94-
Timeout: aws.Int64(infra.timeout),
105+
Timeout: aws.Int64(infra.lambdaTimeout),
95106
})
96107
if err != nil {
97108
if awsErr, ok := err.(awserr.Error); ok {
@@ -143,24 +154,6 @@ func createLambdaAlias(svc *lambda.Lambda, functionVersion *string) error {
143154
return err
144155
}
145156

146-
func lambdaAliasExists(svc *lambda.Lambda) (bool, error) {
147-
_, err := svc.GetAlias(&lambda.GetAliasInput{
148-
FunctionName: aws.String(lambdaFunctionName),
149-
Name: aws.String(LambdaVersion()),
150-
})
151-
152-
if err != nil {
153-
if awsErr, ok := err.(awserr.Error); ok {
154-
if awsErr.Code() == "ResourceNotFoundException" {
155-
return false, nil
156-
}
157-
}
158-
return false, err
159-
}
160-
161-
return true, nil
162-
}
163-
164157
func (infra *LambdaInfrastructure) createIAMLambdaRole(roleName string) (arn string, err error) {
165158
svc := iam.New(session.New(), infra.config)
166159

0 commit comments

Comments
 (0)