Skip to content

Commit 44a9428

Browse files
committed
Intial version
1 parent c28efdb commit 44a9428

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+5733
-10
lines changed

.gitignore

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,68 @@
1-
# If you prefer the allow list template instead of the deny list, see community template:
2-
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3-
#
1+
/bin
2+
/dist
3+
/target
4+
/.cr-release-packages
5+
/vendor
6+
/reverse-http
7+
/*.pem
8+
/*.b64
9+
10+
/*.tar
11+
/*.tgz
12+
13+
# Intellij
14+
.idea/
15+
out/
16+
*.iml
17+
418
# Binaries for programs and plugins
519
*.exe
6-
*.exe~
720
*.dll
821
*.so
922
*.dylib
1023

11-
# Test binary, built with `go test -c`
24+
# Test binary, build with `go test -c`
1225
*.test
1326

1427
# Output of the go coverage tool, specifically when used with LiteIDE
1528
*.out
1629

17-
# Dependency directories (remove the comment below to include it)
18-
# vendor/
30+
# Compiled Object files, Static and Dynamic libs (Shared Objects)
31+
*.o
32+
33+
# Folders
34+
_obj
35+
_test
36+
37+
# Architecture specific extensions/prefixes
38+
*.[568vq]
39+
[568vq].out
40+
41+
*.cgo1.go
42+
*.cgo2.c
43+
_cgo_defun.c
44+
_cgo_gotypes.go
45+
_cgo_export.*
46+
47+
_testmain.go
48+
49+
*.prof
50+
51+
# coverage
52+
.coverprofile
53+
gover.coverprofile
54+
55+
# Swap
56+
[._]*.s[a-v][a-z]
57+
[._]*.sw[a-p]
58+
[._]s[a-v][a-z]
59+
[._]sw[a-p]
60+
61+
# Session
62+
Session.vim
1963

20-
# Go workspace file
21-
go.work
64+
# Temporary
65+
.netrwhist
66+
*~
67+
# Auto-generated tag files
68+
tags

.golangci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# options for analysis running
2+
run:
3+
# exit code when at least one issue was found, default is 1
4+
issues-exit-code: 1
5+
6+
# which dirs to skip: they won't be analyzed;
7+
# can use regexp here: generated.*, regexp is applied on full path;
8+
# default value is empty list, but next dirs are always skipped independently
9+
# from this option's value:
10+
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
11+
skip-dirs:
12+
- vendor
13+
14+
linters:
15+
enable:
16+
- errcheck
17+
- goconst
18+
- godot
19+
- gofmt
20+
- goimports
21+
- gosimple
22+
- govet
23+
- ineffassign
24+
- staticcheck
25+
- typecheck
26+
- unparam
27+
- unused
28+
- exportloopref
29+
30+
issues:
31+
exclude-rules:
32+
- path: _test\.go
33+
linters:
34+
- unparam

Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM golang:1.21-alpine3.19 AS builder
2+
# hadolint ignore=DL3018
3+
RUN apk add --no-cache alpine-sdk ca-certificates curl
4+
5+
WORKDIR /app
6+
COPY go.* ./
7+
RUN go mod download
8+
COPY . .
9+
RUN make vendor build
10+
11+
FROM alpine:3.19
12+
13+
COPY --from=builder /app/reverse-http /reverse-http
14+
15+
USER 65532:65532
16+
ENTRYPOINT ["/reverse-http"]

Makefile

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
.DEFAULT_GOAL := help
2+
3+
.PHONY: clean build fmt test
4+
5+
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
6+
7+
BUILD_FLAGS ?=
8+
VERSION = "0.0.1"
9+
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
10+
REVISION = $(shell git describe --tags --always --dirty)
11+
BUILD_DATE = $(shell date +'%Y.%m.%d-%H:%M:%S')
12+
LDFLAGS ?= -w -s
13+
BINARY = reverse-http
14+
15+
TEST_AGENT_ID = 4711
16+
TEST_AUTH = ha-tls
17+
TEST_STORE_TYPE = none
18+
19+
default: help
20+
21+
.PHONY: help
22+
help:
23+
@grep -E '^[a-zA-Z%_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
24+
25+
build: ## Build executable
26+
@CGO_ENABLED=0 GO111MODULE=on go build -mod=vendor -o $(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
27+
28+
test: ## Test
29+
@GO111MODULE=on go test -count=1 -mod=vendor -v ./...
30+
31+
fmt: ## Go format
32+
go fmt ./...
33+
34+
vet: ## Go vet
35+
go vet ./...
36+
37+
clean: ## Clean
38+
@rm -rf $(BINARY)
39+
40+
lint: ## Lint
41+
@golangci-lint run
42+
43+
.PHONY: deps
44+
deps: ## Get dependencies
45+
GO111MODULE=on go get ./...
46+
47+
.PHONY: vendor
48+
vendor: ## Go vendor
49+
GO111MODULE=on go mod vendor
50+
51+
.PHONY: tidy
52+
tidy: ## Go tidy
53+
GO111MODULE=on go mod tidy
54+
55+
##### Testing
56+
57+
docker-compose.build:
58+
docker-compose -f $(ROOT_DIR)/docker-compose.${TEST_AUTH}.yml build
59+
60+
docker-compose.up:
61+
docker-compose -f $(ROOT_DIR)/docker-compose.${TEST_AUTH}.yml up --remove-orphans
62+
63+
docker-compose.down:
64+
docker-compose -f $(ROOT_DIR)/docker-compose.${TEST_AUTH}.yml down --remove-orphans
65+
66+
docker-compose.run: docker-compose.build docker-compose.up
67+
68+
start-proxy: build
69+
@export QUIC_GO_LOG_LEVEL_=debug && ${ROOT_DIR}/reverse-http proxy --store.type="${TEST_STORE_TYPE}" --agent-server.listen-address=":4242" \
70+
--http-proxy.listen-address=":3128" --agent-server.tls.file.key=tests/cfssl/certs/proxy-key.pem --agent-server.tls.file.cert=tests/cfssl/certs/proxy.pem
71+
72+
start-proxy-tls: build
73+
@export QUIC_GO_LOG_LEVEL_=debug && ${ROOT_DIR}/reverse-http proxy --store.type="${TEST_STORE_TYPE}" --agent-server.listen-address=":4242" \
74+
--http-proxy.listen-address=":3128" --agent-server.tls.file.key=tests/cfssl/certs/proxy-key.pem --agent-server.tls.file.cert=tests/cfssl/certs/proxy.pem \
75+
--http-proxy.tls.enable --http-proxy.tls.file.key=tests/cfssl/certs/proxy-key.pem --http-proxy.tls.file.cert=tests/cfssl/certs/proxy.pem
76+
77+
start-proxy2: build
78+
@${ROOT_DIR}/reverse-http proxy --store.type="memcached" --agent-server.listen-address=":4243" --http-proxy.listen-address=":3127" \
79+
--store.http-proxy-address="localhost:3127" --agent-server.tls.file.key=tests/cfssl/certs/proxy-key.pem --agent-server.tls.file.cert=tests/cfssl/certs/proxy.pem
80+
81+
start-agent: build
82+
@export QUIC_GO_LOG_LEVEL_=debug && ${ROOT_DIR}/reverse-http agent --auth.noauth.agent-id="4711" --agent-client.server-address="localhost:4242" --agent-client.tls.file.root-ca=tests/cfssl/certs/ca.pem
83+
84+
start-agent2: build
85+
@${ROOT_DIR}/reverse-http agent --auth.noauth.agent-id="4712" --agent-client.server-address="localhost:4243" --agent-client.tls.insecure-skip-verify
86+
87+
start-lb: build
88+
@${ROOT_DIR}/reverse-http lb --http-proxy.listen-address=":3129" --store.type="${TEST_STORE_TYPE}"
89+
90+
curl-proxy:
91+
curl -x "http://${TEST_AGENT_ID}:noauth@localhost:3128" https://httpbin.org/ip
92+
93+
curl-proxy-tls:
94+
curl -x "https://${TEST_AGENT_ID}:noauth@localhost:3128" https://httpbin.org/ip --proxy-cacert tests/cfssl/certs/ca.pem
95+
96+
curl-lb:
97+
curl -x "http://${TEST_AGENT_ID}:noauth@localhost:3129" https://httpbin.org/ip
98+
99+
jwt-keys: build
100+
@${ROOT_DIR}/reverse-http auth key private --out=${ROOT_DIR}/tests/jwt/auth-key-private.pem
101+
@${ROOT_DIR}/reverse-http auth key public --out=${ROOT_DIR}/tests/jwt/auth-key-public.pem --in=${ROOT_DIR}/tests/jwt/auth-key-private.pem
102+
@${ROOT_DIR}/reverse-http auth jwt token --duration=87600h --agent-id="4711" --role "client" --out ${ROOT_DIR}/tests/jwt/auth-client-jwt-4711.b64 --in=${ROOT_DIR}/tests/jwt/auth-key-private.pem
103+
@${ROOT_DIR}/reverse-http auth jwt token --duration=87600h --agent-id="4711" --role "agent" --out ${ROOT_DIR}/tests/jwt/auth-agent-jwt-4711.b64 --in=${ROOT_DIR}/tests/jwt/auth-key-private.pem
104+
@${ROOT_DIR}/reverse-http auth jwt token --duration=87600h --agent-id="4712" --role "client" --out ${ROOT_DIR}/tests/jwt/auth-client-jwt-4712.b64 --in=${ROOT_DIR}/tests/jwt/auth-key-private.pem
105+
@${ROOT_DIR}/reverse-http auth jwt token --duration=87600h --agent-id="4712" --role "agent" --out ${ROOT_DIR}/tests/jwt/auth-agent-jwt-4712.b64 --in=${ROOT_DIR}/tests/jwt/auth-key-private.pem
106+
107+
start-proxy-jwt: build
108+
@${ROOT_DIR}/reverse-http proxy --auth.type="jwt" --auth.jwt.public-key=tests/jwt/auth-key-public.pem \
109+
--agent-server.listen-address=":4242" --http-proxy.listen-address=":3128" --agent-server.tls.file.key=tests/cfssl/certs/proxy-key.pem --agent-server.tls.file.cert=tests/cfssl/certs/proxy.pem
110+
111+
start-agent-jwt: build
112+
@$(eval JWT_TOKEN=$(shell cat tests/jwt/auth-agent-jwt-${TEST_AGENT_ID}.b64))
113+
@${ROOT_DIR}/reverse-http agent --auth.type="jwt" --agent-client.server-address="localhost:4242" --agent-client.tls.file.root-ca=tests/cfssl/certs/ca.pem --auth.jwt.token="file:tests/jwt/auth-agent-jwt-${TEST_AGENT_ID}.b64"
114+
115+
curl-proxy-jwt:
116+
@$(eval JWT_TOKEN=$(shell cat tests/jwt/auth-client-jwt-${TEST_AGENT_ID}.b64))
117+
curl -x "http://${TEST_AGENT_ID}:${JWT_TOKEN}@localhost:3128" https://httpbin.org/ip

README.md

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,107 @@
11
# reverse-http
2-
Reverse HTTP proxy over QUIC protocol
2+
3+
Reverse HTTP proxy over QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000)).
4+
5+
## Architecture
6+
7+
### Standalone
8+
9+
<p style="text-align: center;"><img src="docs/reverse-http-arch.svg" alt="Architecture"></p>
10+
11+
* Agent connection process
12+
* An agent initiates a connection to the proxy server utilizing the `QUIC` protocol.
13+
* The connection between the agent and the proxy is persistent
14+
* Upon connection, the proxy server performs an agent authentication
15+
* The proxy keeps track of agents' connections
16+
* Each agent is uniquely identified by an `agentID`
17+
* Multiple agents can simultaneously connect to the proxy.
18+
* Only one connection per `agentID` is allowed.
19+
20+
* Client connection process
21+
* Clients establish a connection with the HTTP proxy by issuing an `HTTP CONNECT` request. This standard method allows the client to specify the desired destination.
22+
* During the connection process, the proxy authenticates the connecting client using basic `Proxy-Authorization`, where the `username` is utilized to specify the `agentID` that the client wishes to connect to.
23+
* Once authenticated, the proxy server locates the corresponding agent's `QUIC` connection that is already being tracked.
24+
* Proxy opens a new `QUIC` stream to the agent and sends all subsequent data through it
25+
* The agent proceeds with the `CONNECT` procedure by establishing a new TCP connection to the requested destination.
26+
27+
### HA setup
28+
29+
<p style="text-align: center;"><img src="docs/reverse-http-ha.svg" alt="HA"></p>
30+
31+
* Agent connection process
32+
* An agent initiates a connection to the UDP load balancer, which in turn establishes a connection with one of the proxy servers
33+
* Upon establishing a connection, the proxy server records an entry in `memcached` for an agentID along with its own HTTP proxy address.
34+
* Client connection process
35+
* Clients connect to the TCP load balancer, which then establishes a connection with one of the LB servers.
36+
* Upon connection, the LB server retrieves the HTTP proxy address and an agentID from Memcached.
37+
* The LB server then sends an `HTTP CONNECT` request to the proxy.
38+
39+
## Build
40+
### build binary
41+
42+
make clean build
43+
44+
## Quick requirements
45+
46+
https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes
47+
48+
```bash
49+
sudo bash -c 'echo net.core.rmem_max=2500000 >> /etc/sysctl.conf'
50+
sudo bash -c 'echo net.core.wmem_max=2500000 >> /etc/sysctl.conf'
51+
sudo sysctl -p
52+
```
53+
54+
55+
56+
## Local test standalone
57+
58+
### no auth
59+
60+
```bash
61+
make start-proxy
62+
make start-agent
63+
curl -x "http://4711:noauth@localhost:3128" https://httpbin.org/ip
64+
```
65+
66+
### jwt auth
67+
68+
```bash
69+
make start-proxy-jwt
70+
make start-agent-jwt
71+
make curl-proxy-jwt
72+
```
73+
74+
## Local test docker-compose
75+
76+
```bash
77+
make TEST_AUTH=noauth docker-compose.run
78+
make TEST_AGENT_ID=4711 curl-proxy
79+
make TEST_AGENT_ID=4712 curl-proxy
80+
```
81+
82+
## Whitelisting patterns
83+
84+
```
85+
localhost
86+
localhost:80
87+
localhost:1000-2000
88+
*.zone
89+
*.zone:80
90+
*.zone:1000-2000
91+
127.0.0.1
92+
127.0.0.1:80
93+
127.0.0.1:1000-2000
94+
10.0.0.1/8
95+
10.0.0.1/8:80
96+
10.0.0.1/8:1000-2000
97+
1000::/16
98+
1000::/16:80
99+
1000::/16:1000-2000
100+
[2001:db8::1]/64
101+
[2001:db8::1]/64:80
102+
[2001:db8::1]/64:1000-2000
103+
2001:db8::1
104+
[2001:db8::1]
105+
[2001:db8::1]:80
106+
[2001:db8::1]:1000-2000
107+
```

0 commit comments

Comments
 (0)