├── .gitignore
├── README.md
├── gigaproxy.py
└── terraform
├── api-gateway.tf
├── eventbridge.tf
├── forwarder-lambda.tf
├── optional-proxy-terraform
├── README.md
├── main.tf
├── outputs.tf
├── proxy-instance-bootstrap.sh
└── variables.tf
├── outputs.tf
├── provider.tf
├── proxy-instance.tf
├── src
├── dependencies.zip
└── forwarder-function
│ └── lambda_function.py
├── terraform.tfvars.example
└── variables.tf
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .venv
3 | .vscode
4 | .idea
5 | .DS_Store
6 | .terraform/
7 | *.tfstate
8 | *.tfstate.backup
9 | *.tfvars
10 | builds/
11 |
12 | # GET YOUR OWN TF PROVIDER HASHES, FREELOADER
13 | .terraform.lock.hcl
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
gigaproxy
2 |
3 | The Giga Proxy
4 |
5 | ## Why?
6 | fireprox is great but has one major downside. You can only target a single host at a time.
7 |
8 | Gigaproxy solves this. Check out the blog post [One Proxy to Rule Them All](https://www.sprocketsecurity.com/resources/gigaproxy) for more details on how it works.
9 |
10 | ## Getting Started
11 |
12 | To use this project and the built-in `gigaproxy.py` script, you will need the following:
13 |
14 | - An AWS account with AWS credentials stored locally (AWS SSO session, static access keys, etc.)
15 | - HashiCorp [Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) installed
16 | - Python and [pipx](https://github.com/pypa/pipx) installed
17 | - [mitmproxy](https://docs.mitmproxy.org/stable/overview-installation/) installed and in your path
18 | - mitmproxy [certificates](https://docs.mitmproxy.org/stable/concepts-certificates/) installed and trusted on your system
19 |
20 |
21 | ## Build the Infrastructure
22 |
23 | First, optionally update the `terraform/variables.tf` with an API key that will be required to authenticate to the generated API gateway endpoint. **If you don't set this yourself, you must go into the AWS console and get it. We recommend specifying your own!**
24 |
25 | To build the infrastructure, you can use the following commands:
26 |
27 | ```bash
28 | cd terraform/
29 | terraform init
30 | terraform plan # optional: if you want to see what's going to be built before running, apply
31 | terraform apply
32 | ```
33 |
34 | Look for the output `api-endpoint` in your terminal after applying.
35 |
36 |
37 | ## Starting the Proxy
38 |
39 | The proxy is started via the command line with arguments to specify the API endpoint and an API key.
40 |
41 | ```bash
42 | mitmdump -s gigaproxy.py --set auth_token= --set proxy_endpoint=
43 | ```
44 |
45 | There are also a couple of secret options that you can use if you read the code.
46 |
47 | If you run this on a VPS somewhere, we recommend tossing it in a tmux or screen session because it will take over your terminal.
48 |
49 | Note that you can specify a custom port and host to listen on. By default, mitmdump will listen on 127.0.0.1:8080.
50 |
51 | For example:
52 |
53 | ```bash
54 | mitmdump -s gigaproxy.py --set auth_token= --set proxy_endpoint= --listen-host 0.0.0.0 --listen-port 8888
55 | ```
56 |
57 | ## Optional Proxy Instance
58 |
59 | If you run into issues with installing/configuring mitmproxy on a host, we provide the option to deploy an EC2 instance along with the rest of the Gigaproxy Terraform build that will install and run mitmproxy automatically. All you need to do is point to the public IP address of the EC2 host instead of `localhost` when proxying requests.
60 |
61 | To deploy this host, edit the `terraform/terraform.tfvars.example` file with the following changes:
62 | - remove the `.example` extension from the end of the filename i.e. `terraform.tfvars.example` -> `terraform.tfvars`
63 | - change `optional_proxy_instance` to `true`
64 | - put your own public IP address in for the value of `proxy_inbound_ip_allowed`, including the netmask (e.g. `"x.x.x.x/32"`)
65 | - this value is very important as it will control the security group that gives access to your proxy instance -- **IF YOU LEAVE THIS OPEN TO 0.0.0.0/0 AND SOMEONE FIGURES OUT IT'S A PROXY, THEY CAN ROUTE THEIR TRAFFIC THROUGH YOUR GIGAPROXY INFRA**
66 | - put the **public** key of an SSH key pair for the value of `proxy_public_ssh_key` (e.g. `ssh-rsa AAAA...`)
67 | - although if everything works as desired you *shouldn't* need to SSH into the proxy instance, this gives you the ability to troubleshoot/modify the host as you want
68 |
69 | After editing the above values appropriately, you can re-run `terraform apply` as in the above *Build The Infrastructure* section. The public IP address of the proxy EC2 instance will be displayed in your terminal output.
70 |
71 | **You will still need to install the mitmproxy certificate on client devices, or disable certificate/ssl/tls verification on your tooling.**
72 |
73 | example command run locally with cert validation disabled: `curl -x http://PUBLIC_IP_OF_PROXY_INSTANCE:8888 -k https://ipv4.rawrify.com/ip`
74 |
75 | example command run locally with normal parameters (cert successfully installed on client device): `curl -x http://PUBLIC_IP_OF_PROXY_INSTANCE:8888 https://ipv4.rawrify.com/ip`
76 |
77 | Some notes on the EC2 instance:
78 | - As mentioned above, access is controlled to the EC2 instance via a security group. While SSH access is secured by the SSH public key, proxy access is not.
79 | - The instance will run off the latest Ubuntu 22.04 LTS ARM-based AMI available in AWS *at the time of deployment*
80 | - After deployment, patching and maintenance of the instance is your responsibility and is not automatically handled.
81 | - To save cost, the instance runs as a `t4g.micro` EC2 instance type, which has about 2 vCPU and 1 GB of memory.
82 | - Launching this host will incur more cost since it's a persistently running server (until you shut it down or terminate it). As of this day (07/24/2024), t4g.micro instances cost about $0.0084USD/hour to run.
83 |
84 | You are free to inspect all of the proxy host's Terraform code in the `terraform/optional-proxy-terraform/` directory and the `terraform/proxy-instance.tf` Terraform file.
85 |
86 |
87 | ## Testing
88 |
89 | With mitmdump running, you can test if everything is working properly. First, make a file containing multiple public IP retrieval endpoints.
90 |
91 | ```txt
92 | https://ifconfig.me
93 | https://api.ipify.org
94 | https://ipv4.rawrify.com/ip
95 | ```
96 |
97 | Then run the following for loop:
98 |
99 | ```bash
100 | while true; do for i in $(cat endpoints.txt); do curl -s $i -x http://127.0.0.1:8080; done; done
101 | ```
102 |
103 | Every minute, your public IP should change.
104 |
105 |
106 | ## Examples
107 | Run nuclei with the following command:
108 |
109 | ```bash
110 | nuclei -l endpoints.txt -t /path/to/nuclei-templates/ -p http://127.0.0.1:8888
111 | ```
112 |
113 | Run ffuf with the following command:
114 |
115 | ```bash
116 | ffuf -u https://example.com/FUZZ -w ~/.ffufw/wordlists/misc/raft-large-words.txt -ac -x http://127.0.0.1:8888
117 | ```
118 |
119 | Proxy a specific CLI tool with exported environment variables.
120 |
121 | ```bash
122 | export http_proxy=http://127.0.0.1:8888
123 | export https_proxy=http://127.0.0.1:8888
124 |
125 | # Now run the command you want to proxy
126 | curl https://example.com
127 | ```
128 |
129 | ## TODO
130 |
131 | * Add multi-cloud support
132 | * Convert gigaproxy.py to a more portable CLI tool
133 | * Add more examples
134 | * Organize terraform code
135 |
136 | ## References & Thanks
137 |
138 | * [fireprox](https://github.com/ustayready/fireprox)
139 | * [This guy](https://github.com/Hogman-the-Intruder)
140 |
--------------------------------------------------------------------------------
/gigaproxy.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import logging
3 | import os
4 | import random
5 | from urllib.parse import urlparse
6 |
7 | from mitmproxy import ctx, http
8 |
9 | # Configure logging
10 | logging.basicConfig(level=logging.INFO)
11 |
12 |
13 | def load(l):
14 | l.add_option("auth_token", str, "", "Authorization token for the proxy")
15 | l.add_option(
16 | "proxy_endpoint", str, "", "Target AWS API gateway endpoint to forward to"
17 | )
18 | l.add_option("rotate_user_agent", bool, False, "Rotate the user agent")
19 | l.add_option("debug", bool, False, "Enable debug mode")
20 |
21 |
22 | user_agents = [
23 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
24 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15",
25 | "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0",
26 | "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
27 | "Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Mobile Safari/537.36",
28 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.59",
29 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
30 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
31 | "Mozilla/5.0 (iPad; CPU OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
32 | "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Mobile Safari/537.36",
33 | ]
34 |
35 |
36 | def request(flow: http.HTTPFlow) -> None:
37 |
38 | # Access options using ctx.options
39 | auth_token = ctx.options.auth_token
40 | proxy_endpoint = ctx.options.proxy_endpoint
41 | rotate_user_agent = ctx.options.rotate_user_agent
42 | debug = ctx.options.debug
43 |
44 | try:
45 | # Adding new required headers for the proxy
46 | flow.request.headers["x-api-key"] = auth_token
47 | flow.request.headers["x-forward-me-to"] = flow.request.pretty_url
48 |
49 | # Optional rotating user agent
50 | if rotate_user_agent:
51 | flow.request.headers["User-Agent"] = random.choice(user_agents)
52 |
53 | # Debug logs in AWS will be generated
54 | if debug:
55 | flow.request.headers["X-DEBUG"] = "SPRKTWASHERE"
56 |
57 | # Setting the target proxy
58 | flow.request.host = urlparse(proxy_endpoint).hostname
59 | flow.request.path = urlparse(proxy_endpoint).path
60 | flow.request.port = 443
61 | flow.request.scheme = "https"
62 |
63 | except Exception as e:
64 | logging.error(f"Error processing request: {e}")
65 | pass
66 |
--------------------------------------------------------------------------------
/terraform/api-gateway.tf:
--------------------------------------------------------------------------------
1 | #####################################
2 | # REST API - SEND YOUR TRAFFIC HERE #
3 | #####################################
4 |
5 | resource "aws_api_gateway_rest_api" "gigaproxy-api" {
6 | name = "${var.project_identifier}-api"
7 |
8 | description = "${var.project_identifier} REST API"
9 |
10 | # Body defined as OpenAPI v3 JSON -- can use Swagger too
11 | body = jsonencode(
12 | {
13 | "openapi" : "3.0.1",
14 | "paths" : {
15 | "/gigaproxy-forwarder-function" : {
16 | "x-amazon-apigateway-any-method" : {
17 | "responses" : {
18 | "200" : {
19 | "description" : "200 response",
20 | "content" : {}
21 | }
22 | },
23 | "security" : [{
24 | "api_key" : []
25 | }],
26 | "x-amazon-apigateway-integration" : {
27 | "httpMethod" : "POST",
28 | "uri" : "arn:aws:apigateway:${data.aws_region.current-region.name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${data.aws_region.current-region.name}:${data.aws_caller_identity.current-account.account_id}:function:${var.project_identifier}-forwarder-function/invocations",
29 | "responses" : {
30 | "default" : {
31 | "statusCode" : "200"
32 | }
33 | },
34 | "passthroughBehavior" : "when_no_templates",
35 | "contentHandling" : "CONVERT_TO_TEXT",
36 | "type" : "aws_proxy"
37 | }
38 | }
39 | }
40 | },
41 | "components" : {
42 | "securitySchemes" : {
43 | "api_key" : {
44 | "type" : "apiKey",
45 | "name" : "x-api-key",
46 | "in" : "header"
47 | }
48 | }
49 | },
50 | "x-amazon-apigateway-binary-media-types" : ["image/jpg", "image/gif", "image/png"]
51 | }
52 | )
53 |
54 | endpoint_configuration {
55 | types = ["REGIONAL"] # Can set to edge-optimized if you want, but only really affects inbound latency to API Gateway from clients geographically dispersed across the world. Doesn't really affect IP rotation
56 | }
57 | }
58 |
59 | resource "aws_api_gateway_stage" "v1-stage" {
60 | stage_name = "v1"
61 | rest_api_id = aws_api_gateway_rest_api.gigaproxy-api.id
62 | deployment_id = aws_api_gateway_deployment.v1-deployment.id
63 | }
64 |
65 | resource "aws_api_gateway_deployment" "v1-deployment" {
66 | rest_api_id = aws_api_gateway_rest_api.gigaproxy-api.id
67 |
68 | triggers = {
69 | redeployment = sha1(jsonencode(aws_api_gateway_rest_api.gigaproxy-api.body))
70 | }
71 |
72 | lifecycle {
73 | create_before_destroy = true
74 | }
75 | }
76 |
77 | # Create an AWS API Gateway REST API API key for auth and usage control
78 | resource "aws_api_gateway_api_key" "proxy-api-key" {
79 | name = "${var.project_identifier}-api-key"
80 | value = var.api_key == "" ? null : var.api_key
81 | }
82 |
83 | resource "aws_api_gateway_usage_plan" "proxy-usage-plan" {
84 | name = "${var.project_identifier}-usage-plan"
85 | description = "Controls access to and usage for the ${var.project_identifier} proxy"
86 |
87 | api_stages {
88 | api_id = aws_api_gateway_rest_api.gigaproxy-api.id
89 | stage = aws_api_gateway_stage.v1-stage.stage_name
90 | }
91 |
92 | quota_settings {
93 | limit = var.api_monthly_quota # By default, limit to 10 million requests per month. Can be modified or fully removed for no cap
94 | period = "MONTH"
95 | }
96 | }
97 |
98 | resource "aws_api_gateway_usage_plan_key" "link-proxy-key" {
99 | key_id = aws_api_gateway_api_key.proxy-api-key.id
100 | key_type = "API_KEY"
101 | usage_plan_id = aws_api_gateway_usage_plan.proxy-usage-plan.id
102 | }
--------------------------------------------------------------------------------
/terraform/eventbridge.tf:
--------------------------------------------------------------------------------
1 | # We need to do some re-parsing since jsonencode will escape < and >
2 | locals {
3 | schedule_input = jsonencode({
4 | "FunctionName" : aws_lambda_function.forwarder-function.function_name
5 | "Description" : "redeployed by eventbridge at "
6 | })
7 |
8 | corrected_schedule_input = replace(replace(local.schedule_input, "\\u003c", "<"), "\\u003e", ">")
9 | }
10 |
11 | #####################################
12 | # EVENTBRIDGE SCHEDULE TO ROTATE IP #
13 | #####################################
14 |
15 | resource "aws_scheduler_schedule" "cycle-lambda-ips" {
16 | name = "${var.project_identifier}-redeploy-ip-rotation"
17 | group_name = "default"
18 |
19 | flexible_time_window {
20 | mode = "OFF"
21 | }
22 |
23 | schedule_expression = "rate(1 minutes)"
24 |
25 | target {
26 | arn = "arn:aws:scheduler:::aws-sdk:lambda:updateFunctionConfiguration"
27 | role_arn = aws_iam_role.eventbridge-role.arn
28 |
29 | input = local.corrected_schedule_input
30 | }
31 | }
32 |
33 |
34 | #######################################
35 | # IAM POLICY FOR EVENTBRIDGE SCHEDULE #
36 | #######################################
37 | data "aws_iam_policy_document" "eventbridge-assume-role" {
38 | statement {
39 | effect = "Allow"
40 | principals {
41 | type = "Service"
42 | identifiers = [
43 | "scheduler.amazonaws.com"
44 | ]
45 | }
46 | actions = [
47 | "sts:AssumeRole"
48 | ]
49 | }
50 | }
51 |
52 | resource "aws_iam_role" "eventbridge-role" {
53 | name = "${var.project_identifier}-eventbridge-cron-role"
54 | assume_role_policy = data.aws_iam_policy_document.eventbridge-assume-role.json
55 | }
56 |
57 | data "aws_iam_policy_document" "eventbridge-permissions" {
58 | statement {
59 | sid = "AllowUpdateLambda"
60 | effect = "Allow"
61 | actions = [
62 | "lambda:UpdateFunctionCode",
63 | "lambda:UpdateFunctionConfiguration"
64 | ]
65 | resources = [
66 | "arn:aws:lambda:*:${data.aws_caller_identity.current-account.account_id}:function:${var.project_identifier}-*"
67 | ]
68 | }
69 | }
70 |
71 | resource "aws_iam_policy" "eventbridge-policy" {
72 | name = "${var.project_identifier}-eventbridge-cron-policy"
73 | policy = data.aws_iam_policy_document.eventbridge-permissions.json
74 | }
75 |
76 | resource "aws_iam_role_policy_attachment" "eventbridge-policy-attach" {
77 | role = aws_iam_role.eventbridge-role.name
78 | policy_arn = aws_iam_policy.eventbridge-policy.arn
79 | }
--------------------------------------------------------------------------------
/terraform/forwarder-lambda.tf:
--------------------------------------------------------------------------------
1 | data "archive_file" "lambda-function-zip" {
2 | type = "zip"
3 | source_file = "${path.module}/src/forwarder-function/lambda_function.py"
4 | output_path = "${path.module}/builds/lambda_function_payload.zip"
5 | }
6 |
7 | ##########################################################################
8 | # LAMBDA FUNCTION THAT FORWARDS YOUR TRAFFIC - THIS IS WHERE IT GOES OUT #
9 | ##########################################################################
10 |
11 | resource "aws_lambda_function" "forwarder-function" {
12 | function_name = "${var.project_identifier}-forwarder-function"
13 | description = "Forwarding function for the ${var.project_identifier} API"
14 |
15 | role = aws_iam_role.forwarder-function-role.arn
16 |
17 | handler = "lambda_function.lambda_handler"
18 |
19 | filename = "${path.module}/builds/lambda_function_payload.zip"
20 | source_code_hash = data.archive_file.lambda-function-zip.output_base64sha256
21 | runtime = "python3.12"
22 |
23 | timeout = 10
24 | memory_size = 128
25 |
26 | layers = [
27 | aws_lambda_layer_version.dependencies-layer.arn
28 | ]
29 | }
30 |
31 | ########################################
32 | # IAM POLICY STUFF FOR LAMBDA FUNCTION #
33 | ########################################
34 |
35 | data "aws_iam_policy_document" "lambda-assume-role" {
36 | statement {
37 | effect = "Allow"
38 |
39 | principals {
40 | type = "Service"
41 | identifiers = ["lambda.amazonaws.com"]
42 | }
43 |
44 | actions = ["sts:AssumeRole"]
45 | }
46 | }
47 |
48 | resource "aws_iam_role" "forwarder-function-role" {
49 | name = "${var.project_identifier}-function-role"
50 | assume_role_policy = data.aws_iam_policy_document.lambda-assume-role.json
51 | }
52 |
53 | # Attach the basic AWS Lambda execution role, which allows the Lambda to log to Cloudwatch
54 | resource "aws_iam_role_policy_attachment" "attach-basic-lambda-policy" {
55 | count = var.enable_function_logging ? 1 : 0
56 |
57 | role = aws_iam_role.forwarder-function-role.name
58 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
59 | }
60 |
61 | resource "aws_lambda_permission" "invoke-from-api" {
62 | statement_id = "AllowAPIInvoke"
63 | action = "lambda:InvokeFunction"
64 | function_name = aws_lambda_function.forwarder-function.function_name
65 | principal = "apigateway.amazonaws.com"
66 | source_arn = "${aws_api_gateway_rest_api.gigaproxy-api.execution_arn}/*"
67 | }
68 |
69 | # Package dependencies for the forwarder function
70 | resource "aws_lambda_layer_version" "dependencies-layer" {
71 | layer_name = "${var.project_identifier}-dependencies-layer"
72 | filename = "${path.module}/src/dependencies.zip"
73 |
74 | source_code_hash = filebase64sha256("${path.module}/src/dependencies.zip")
75 |
76 | compatible_architectures = ["arm64"]
77 | compatible_runtimes = ["python3.12"]
78 | }
79 |
80 | #####################################
81 | # CLOUDWATCH LOG GROUP FOR FUNCTION #
82 | #####################################
83 |
84 | # Create a log group and set default retention to 14 days so we don't get a crapload of logs just sitting there
85 | resource "aws_cloudwatch_log_group" "lambda-function-logs" {
86 | count = var.enable_function_logging ? 1 : 0
87 |
88 | name = "/aws/lambda/${aws_lambda_function.forwarder-function.function_name}"
89 |
90 | retention_in_days = var.log_retention_period
91 | }
--------------------------------------------------------------------------------
/terraform/optional-proxy-terraform/README.md:
--------------------------------------------------------------------------------
1 | ### HEY
2 | This should only be considered as a temporary, optional launching off point to help you in getting started with Gigaproxy. Don't rely on this proxy instance for long-term or production testing! One issue with it is it saves your API Gateway endpoint and auth token in the userdata of the EC2 instance, which can easily be extracted if someone gets access to your AWS account or dumps them from the instance metadata service on the instance (the 169.254.169.254 endpoint). You've been warned.
3 |
4 | Also, you will still need to install the mitmproxy cert locally unless you're fine with disabling cert validation on your tools. (e.g. passing `-k` with curl commands)
--------------------------------------------------------------------------------
/terraform/optional-proxy-terraform/main.tf:
--------------------------------------------------------------------------------
1 | ###########################
2 | # OPTIONAL PROXY INSTANCE #
3 | ###########################
4 |
5 | /*
6 | Can't get the mitmproxy stuff working? You can optionally use this to launch a persistent EC2 instance that'll
7 | */
8 |
9 | # Use latest ARM-based Ubuntu 22.04
10 | data "aws_ami" "latest-ubuntu-arm" {
11 | most_recent = true
12 | filter {
13 | name = "name"
14 | values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-arm64-server-*"]
15 | }
16 |
17 | owners = ["099720109477"]
18 | }
19 |
20 | resource "aws_key_pair" "ssh-keypair" {
21 | key_name = "${var.project_identifier}-temp-proxy-instance-key"
22 | public_key = var.ssh_public_key
23 | }
24 |
25 |
26 | resource "aws_instance" "proxy-instance" {
27 |
28 | ami = data.aws_ami.latest-ubuntu-arm.id
29 | instance_type = "t4g.micro"
30 |
31 | source_dest_check = false
32 |
33 | vpc_security_group_ids = [aws_security_group.proxy-sg.id]
34 | subnet_id = module.proxy-vpc.public_subnets[0]
35 |
36 | key_name = aws_key_pair.ssh-keypair.id
37 |
38 | user_data_base64 = base64encode(
39 | templatefile("${path.module}/proxy-instance-bootstrap.sh",
40 | {
41 | "AUTH_TOKEN" = var.gigaproxy_api_token,
42 | "API_ENDPOINT" = var.gigaproxy_endpoint
43 | }
44 | )
45 | )
46 |
47 | root_block_device {
48 | encrypted = true
49 | }
50 |
51 | tags = {
52 | Name = "${var.project_identifier}-temporary-proxy-instance"
53 | }
54 | }
55 |
56 |
57 | #################################
58 | # NETWORKING FOR OPTIONAL PROXY #
59 | #################################
60 |
61 | module "proxy-vpc" {
62 | source = "terraform-aws-modules/vpc/aws"
63 | version = "5.9.0"
64 |
65 | name = "${var.project_identifier}-vpc"
66 | cidr = var.vpc_cidr
67 |
68 | azs = ["${var.aws_region}a", "${var.aws_region}b"]
69 | public_subnets = ["10.99.0.0/24", "10.99.1.0/24"]
70 |
71 | map_public_ip_on_launch = true
72 | }
73 |
74 | resource "aws_security_group" "proxy-sg" {
75 | name = "${var.project_identifier}-sg"
76 | description = "For the ${var.project_identifier} proxy EC2 instance"
77 | vpc_id = module.proxy-vpc.vpc_id
78 | }
79 |
80 | resource "aws_vpc_security_group_egress_rule" "proxy-sg-egress" {
81 | security_group_id = aws_security_group.proxy-sg.id
82 | description = "Allow outbound"
83 |
84 | ip_protocol = "-1"
85 | cidr_ipv4 = "0.0.0.0/0"
86 | }
87 |
88 | resource "aws_vpc_security_group_ingress_rule" "proxy-sg-inbound-ssh" {
89 | security_group_id = aws_security_group.proxy-sg.id
90 | description = "Allow inbound ssh"
91 |
92 | ip_protocol = "tcp"
93 | from_port = 22
94 | to_port = 22
95 | cidr_ipv4 = var.proxy_inbound_ip_allowed
96 | }
97 |
98 | resource "aws_vpc_security_group_ingress_rule" "proxy-sg-inbound" {
99 | security_group_id = aws_security_group.proxy-sg.id
100 | description = "Allow inbound traffic to proxy"
101 |
102 | ip_protocol = "tcp"
103 | from_port = 8888
104 | to_port = 8888
105 | cidr_ipv4 = var.proxy_inbound_ip_allowed
106 | }
--------------------------------------------------------------------------------
/terraform/optional-proxy-terraform/outputs.tf:
--------------------------------------------------------------------------------
1 | output "proxy-public-ip" {
2 | value = aws_instance.proxy-instance.public_ip
3 | }
--------------------------------------------------------------------------------
/terraform/optional-proxy-terraform/proxy-instance-bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | export DEBIAN_FRONTEND=noninteractive
5 |
6 | apt update -y
7 |
8 | apt install git python3 python3-venv python3-pip libffi-dev libssl-dev ca-certificates screen -y
9 |
10 | pip install --upgrade pip
11 | pip install pipx
12 | pipx install mitmproxy # Very important to install mitmproxy via pipx and NOT apt or single binary. Weird issues with single binary installs not picking up the cert updates below when connected to gigaproxy.
13 | pipx ensurepath
14 |
15 | export PATH=$PATH:~/.local/bin
16 |
17 | mitmproxy --version
18 |
19 | # Start mitmproxy in a screen session for a short period of time to generate certificates
20 | screen -dmS mitmproxy_bootstrap bash -c "mitmproxy; sleep 10; exit"
21 |
22 | sleep 15
23 |
24 | screen -S mitmproxy_bootstrap -X quit
25 |
26 | openssl x509 -in /root/.mitmproxy/mitmproxy-ca-cert.cer -out /root/.mitmproxy/mitmproxy-ca-cert.pem
27 |
28 | MITMPROXY_CERT_PATH="/root/.mitmproxy/mitmproxy-ca-cert.pem"
29 |
30 | if [ -f "$MITMPROXY_CERT_PATH" ]; then
31 | cp "$MITMPROXY_CERT_PATH" /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
32 |
33 | update-ca-certificates
34 |
35 | echo "mitmproxy certificate installed into the system's certificate trust store."
36 | else
37 | echo "mitmproxy certificate not found at $MITMPROXY_CERT_PATH."
38 | fi
39 |
40 | cd /root
41 | git clone https://github.com/Sprocket-Security/gigaproxy
42 | cd gigaproxy
43 |
44 | screen -dmS mitmproxy_session mitmdump -s gigaproxy.py --set auth_token="${AUTH_TOKEN}" --set proxy_endpoint="${API_ENDPOINT}v1/gigaproxy-forwarder-function" --listen-host 0.0.0.0 --listen-port 8888 --set block_global=false
--------------------------------------------------------------------------------
/terraform/optional-proxy-terraform/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project_identifier" {
2 | default = "gigaproxy"
3 | description = "Prefix value to add to resources and as a tag to relevant AWS resources."
4 | type = string
5 | }
6 |
7 | variable "proxy_inbound_ip_allowed" {
8 | description = "IP range to allow on inbound connections, including netmask. Highly recommended to set this to your own public IP address, which can be retrieved at a site like https://ipv4.rawrify.com/ip -- if you really must allow all inbound, use 0.0.0.0/0"
9 | type = string
10 | }
11 |
12 | variable "aws_region" {
13 | description = "Region to deploy to. Should be passed automatically from parent Terraform code."
14 | type = string
15 | }
16 |
17 | variable "vpc_cidr" {
18 | description = "CIDR range to use for the VPC. Doesn't affect much except the private IP address assigned to the EC2 instance, which doesn't really matter unless you're launching other services."
19 | type = string
20 | default = "10.99.0.0/16"
21 | }
22 |
23 | variable "ssh_public_key" {
24 | description = "Full content of the SSH public key pair to set on the instance. Generate an SSH key on your machine and simply supply the public keypair."
25 | type = string
26 | }
27 |
28 | variable "gigaproxy_api_token" {
29 | description = "API token generated for gigaproxy. Highly recommended to generate a new one after you're done with the proxy instance and destroy it."
30 | type = string
31 | sensitive = true
32 | }
33 |
34 | variable "gigaproxy_endpoint" {
35 | description = "The API gateway execution endpoint that we can reach our gigaproxy instance on."
36 | type = string
37 | }
--------------------------------------------------------------------------------
/terraform/outputs.tf:
--------------------------------------------------------------------------------
1 | output "api-endpoint" {
2 | value = "${aws_api_gateway_stage.v1-stage.invoke_url}/gigaproxy-forwarder-function"
3 | }
--------------------------------------------------------------------------------
/terraform/provider.tf:
--------------------------------------------------------------------------------
1 | # Minimal AWS provider definition, really only for enforcing default tags
2 | # Use AWS environment variables (e.g. AWS_PROFILE, AWS_REGION) for auth config
3 | provider "aws" {
4 | default_tags {
5 | tags = {
6 | Project = var.project_identifier
7 | Terraform = true
8 | }
9 | }
10 | }
11 |
12 | data "aws_caller_identity" "current-account" {
13 |
14 | }
15 |
16 | data "aws_region" "current-region" {
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/terraform/proxy-instance.tf:
--------------------------------------------------------------------------------
1 | ###########################
2 | # OPTIONAL PROXY INSTANCE #
3 | ###########################
4 |
5 | # READ THE README in optional-proxy-terraform/README.md
6 |
7 | module "optional-proxy-instance" {
8 | source = "./optional-proxy-terraform"
9 |
10 | # Don't deploy this unless optional_proxy_instance var is set to true
11 | count = var.optional_proxy_instance ? 1 : 0
12 |
13 | proxy_inbound_ip_allowed = var.proxy_inbound_ip_allowed
14 | aws_region = data.aws_region.current-region.name
15 | ssh_public_key = var.proxy_public_ssh_key
16 | gigaproxy_api_token = aws_api_gateway_api_key.proxy-api-key.value
17 | gigaproxy_endpoint = aws_api_gateway_deployment.v1-deployment.invoke_url
18 | }
19 |
20 | output "proxy-public-ip" {
21 | value = var.optional_proxy_instance ? "Proxy instance public IP: ${module.optional-proxy-instance[0].proxy-public-ip}" : null
22 | }
--------------------------------------------------------------------------------
/terraform/src/dependencies.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sprocket-Security/gigaproxy/fd4d865596a14264eccee786913d15c267ee0863/terraform/src/dependencies.zip
--------------------------------------------------------------------------------
/terraform/src/forwarder-function/lambda_function.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 | import os
4 | import base64
5 |
6 | # Determine if the content should be treated as binary
7 | # Note there's some extra shit you have to do in API Gateway to support binary output, and even then it can sometimes be a crapshoot
8 | binary_content_types = [
9 | 'image/', 'application/pdf', 'application/octet-stream'
10 | ]
11 |
12 | # Format incoming requests to drop headers we don't want to send out
13 | def drop_incoming_headers(event):
14 | if "Host" in event["headers"].keys():
15 | event["headers"].pop("Host")
16 |
17 | if "X-Amzn-Trace-Id" in event["headers"].keys():
18 | event["headers"].pop("X-Amzn-Trace-Id")
19 |
20 | if "X-Forwarded-For" in event["headers"].keys():
21 | event["headers"].pop("X-Forwarded-For")
22 |
23 | if "x-api-key" in event["headers"].keys():
24 | event["headers"].pop("x-api-key")
25 |
26 |
27 | def lambda_handler(event, context):
28 | # print incoming event to log if x-debug-call header is sent
29 | if "x-debug-call" in event["headers"].keys():
30 | print(event)
31 |
32 | url = event["headers"].pop("x-forward-me-to")
33 |
34 | if not url:
35 | return {
36 | "statusCode": 400,
37 | "body": "Request did not contain necessary header."
38 | }
39 |
40 | # Extract request details
41 | method = event["httpMethod"]
42 | drop_incoming_headers(event)
43 | headers = event["headers"]
44 | body = event["body"]
45 | params = event["queryStringParameters"]
46 |
47 | # Forward the request to the destination server
48 | response = requests.request(method, url, headers=headers, data=body, params=params, verify=False)
49 |
50 | # Read the response content fully -- need to do this, especially if responses are chunked
51 | response_content = response.content
52 |
53 | # Determine the content type of the response
54 | content_type = response.headers.get('Content-Type', 'application/json')
55 |
56 | # Handle binary content or not, depending on Content-Type header
57 | if any(content_type.startswith(bct) for bct in binary_content_types):
58 | response_body = base64.b64encode(response_content).decode('utf-')
59 | is_base64 = True
60 | else:
61 | response_body = response_content.decode('utf-8')
62 | is_base64 = False
63 |
64 | # Return the response in the required format
65 | return {
66 | 'statusCode': response.status_code,
67 | 'headers': {
68 | 'Content-Type': content_type
69 | },
70 | 'isBase64Encoded': is_base64,
71 | 'body': response_body
72 | }
73 |
--------------------------------------------------------------------------------
/terraform/terraform.tfvars.example:
--------------------------------------------------------------------------------
1 | # Enables function logs in Cloudwatch
2 | enable_function_logging = false
3 |
4 | # optional proxy vars -- refer to README
5 | optional_proxy_instance = false
6 | proxy_inbound_ip_allowed = ""
7 | proxy_public_ssh_key = ""
--------------------------------------------------------------------------------
/terraform/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project_identifier" {
2 | default = "gigaproxy"
3 | description = "Prefix value to add to resources and as a tag to relevant AWS resources."
4 | type = string
5 | }
6 |
7 | variable "api_key" {
8 | description = "Value to set for API key for authentication. If not set, AWS sets it automatically (and can be retrieved from API Gateway console)."
9 | sensitive = true
10 | type = string
11 | default = ""
12 | }
13 |
14 | variable "enable_function_logging" {
15 | description = "Whether or not to enable logging on the forwarder Lambda function. Defaults to false (no logging)"
16 | type = bool
17 | default = false
18 | }
19 |
20 | variable "log_retention_period" {
21 | description = "Duration for how long logs should be kept for the forwarder function in days. Defaults to 14 days."
22 | type = number
23 | default = 14
24 | }
25 |
26 | variable "api_monthly_quota" {
27 | description = "Number of requests to allow per month to the proxy API. Defaults to 10 million. Don't rely on this too much as a hard cap, there can be some wiggle room in how this is enforced (per AWS documentation)"
28 | type = number
29 | default = 10000000
30 | }
31 |
32 | ##############
33 | # PROXY VARS #
34 | ##############
35 |
36 | /*
37 | ONLY set these vars if you need the proxy instance. Otherwise, can ignore, they will do nothing
38 | */
39 |
40 | variable "optional_proxy_instance" {
41 | description = "Whether or not to launch an AWS EC2 instance and launch mitmproxy on that instead of locally. Useful for troubleshooting."
42 | type = bool
43 | default = false
44 | }
45 |
46 | variable "proxy_inbound_ip_allowed" {
47 | description = "IP range to allow on inbound connections, including netmask. Highly recommended to set this to your own public IP address, which can be retrieved at a site like https://ipv4.rawrify.com/ip -- if you really must allow all inbound, use 0.0.0.0/0"
48 | type = string
49 | default = "127.0.0.1/32" # Purposefully incorrect value that you must override to actually get this to work. Acts as a failsafe so you don't launch a publicly open, completely unprotected proxy instance to the internet
50 | }
51 |
52 | variable "proxy_public_ssh_key" {
53 | description = "Full content of the SSH public key pair to set on the instance. Generate an SSH key on your machine and simply supply the public keypair."
54 | type = string
55 | default = "Didn't set the SSH key properly"
56 | }
--------------------------------------------------------------------------------