Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 7 additions & 86 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,98 +7,19 @@ on:
jobs:
integration-test:
name: Run Integration Tests
runs-on: [ self-hosted, linux, x64, jammy, large ]
runs-on: [ self-hosted, linux, x64, noble ]

steps:
- uses: actions/checkout@v2

- name: Build aproxy Snap
id: snapcraft-build
uses: snapcore/action-build@v1
with:
snapcraft-args: --build-for amd64
- run: cat /etc/docker/daemon.json

- name: Upload aproxy Snap
uses: actions/upload-artifact@v4
with:
name: snap
path: aproxy*.snap
- run: which docker

- name: Install aproxy Snap
run: |
sudo snap install --dangerous aproxy_*_amd64.snap
- run: docker version

- name: Show aproxy Configuration
run: |
sudo snap get aproxy
- run: echo "FROM ubuntu:jammy" > Dockerfile

- name: Configure aproxy
run: |
sudo nft -f - << EOF
define default-ip = $(ip route get $(ip route show 0.0.0.0/0 | grep -oP 'via \K\S+') | grep -oP 'src \K\S+')
define private-ips = { 10.0.0.0/8, 127.0.0.1/8, 172.16.0.0/12, 192.168.0.0/16 }
define aproxy-port = $(sudo snap get aproxy listen | cut -d ":" -f 2)
table ip aproxy
flush table ip aproxy
table ip aproxy {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
ip daddr != \$private-ips tcp dport { 80, 443, 11371, 4242 } counter dnat to \$default-ip:\$aproxy-port
}
- run: docker build .

chain output {
type nat hook output priority -100; policy accept;
ip daddr != \$private-ips tcp dport { 80, 443, 11371, 4242 } counter dnat to \$default-ip:\$aproxy-port
}
}
EOF

- name: Start tcpdump
run: |
sudo tcpdump -i any -s 65535 -w capture.pcap &
echo $! > tcpdump.pid

- name: Test HTTP
run: |
timeout 60 curl --noproxy "*" http://example.com -svS -o /dev/null

- name: Test HTTPS
run: |
timeout 60 curl --noproxy "*" https://example.com -svS -o /dev/null

- name: Test HKP
run: |
timeout 60 gpg -vvv --keyserver hkp://keyserver.ubuntu.com --recv-keys E1DE584A8CCA52DC29550F18ABAC58F075A17EFA

- name: Test TCP4
run: |
sudo apt install -y socat
timeout 60 socat /dev/null TCP4:tcpbin.com:4242

- name: Test Access Logs
run: |
sudo snap logs aproxy.aproxy | grep -Fq "example.com:80"
sudo snap logs aproxy.aproxy | grep -Fq "example.com:443"
sudo snap logs aproxy.aproxy | grep -Fq "keyserver.ubuntu.com:11371"
sudo snap logs aproxy.aproxy | grep -Eq "[0-9.]+:4242"

- name: Show Access Logs
if: failure()
run: |
sudo snap logs aproxy.aproxy -n=all

- name: Stop tcpdump
if: failure()
run: |
PID=$(cat tcpdump.pid)
if [ -n "$PID" ]; then
sudo kill -2 "$PID" || true
fi
sleep 1

- name: Upload tcpdump capture
if: failure()
uses: actions/upload-artifact@v4
with:
name: tcpdump
path: capture.pcap
- run: sudo snap logs -n all aproxy
18 changes: 12 additions & 6 deletions aproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func DialProxy(proxy string) (net.Conn, error) {

// DialProxyConnect dials the TCP connection and finishes the HTTP CONNECT handshake with the proxy.
// dst: HOST:PORT or IP:PORT
func DialProxyConnect(proxy string, dst string) (net.Conn, error) {
func DialProxyConnect(proxy string, src, dst string) (net.Conn, error) {
conn, err := DialProxy(proxy)
if err != nil {
return nil, err
Expand All @@ -236,7 +236,8 @@ func DialProxyConnect(proxy string, dst string) (net.Conn, error) {
ProtoMajor: 1,
ProtoMinor: 1,
Header: map[string][]string{
"User-Agent": {fmt.Sprintf("aproxy/%s", version)},
"User-Agent": {fmt.Sprintf("aproxy/%s", version)},
"X-Forwarded-For": {src},
},
Host: dst,
}
Expand Down Expand Up @@ -289,7 +290,7 @@ func RelayTCP(conn io.ReadWriter, proxyConn io.ReadWriteCloser, logger *slog.Log

// RelayHTTP relays a single HTTP request and response between a local connection and a proxy.
// It modifies the Connection header to "close" in both the request and response.
func RelayHTTP(conn io.ReadWriter, proxyConn io.ReadWriteCloser, logger *slog.Logger) {
func RelayHTTP(conn io.ReadWriter, src string, proxyConn io.ReadWriteCloser, logger *slog.Logger) {
defer proxyConn.Close()
req, err := http.ReadRequest(bufio.NewReader(conn))
if err != nil {
Expand All @@ -302,6 +303,10 @@ func RelayHTTP(conn io.ReadWriter, proxyConn io.ReadWriteCloser, logger *slog.Lo
req.Header.Set("User-Agent", "")
}
req.Header.Set("Connection", "close")
// For now strip and replace the X-Forwarded-For header from incoming requests.
// We cannot trust X-Forwarded-For on arbitrary requests for security reasons.
// In the future, consider conditionally trusting X-Forwarded-For from known clients.
req.Header.Set("X-Forwarded-For", src)
if req.Proto == "HTTP/1.0" {
// no matter what the request protocol is, Go enforces a minimum version of HTTP/1.1
// this causes problems for HTTP/1.0 only clients like GPG (HKP)
Expand Down Expand Up @@ -349,6 +354,7 @@ func HandleConn(conn net.Conn, proxy string) {
return
}
logger = logger.With("original_dst", dst)
src, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
consigned := NewPrereadConn(conn)
switch dst.Port {
case 443:
Expand All @@ -359,7 +365,7 @@ func HandleConn(conn net.Conn, proxy string) {
} else {
host := fmt.Sprintf("%s:%d", sni, dst.Port)
logger = logger.With("host", host)
proxyConn, err := DialProxyConnect(proxy, host)
proxyConn, err := DialProxyConnect(proxy, src, host)
if err != nil {
logger.Error("failed to connect to http proxy", "error", err)
return
Expand All @@ -383,11 +389,11 @@ func HandleConn(conn net.Conn, proxy string) {
return
}
logger.Info("relay HTTP connection to proxy")
RelayHTTP(consigned, proxyConn, logger)
RelayHTTP(consigned, src, proxyConn, logger)
default:
consigned.EndPreread()
logger = logger.With("host", fmt.Sprintf("%s:%d", dst.IP.String(), dst.Port))
proxyConn, err := DialProxyConnect(proxy, fmt.Sprintf("%s:%d", dst.IP.String(), dst.Port))
proxyConn, err := DialProxyConnect(proxy, src, fmt.Sprintf("%s:%d", dst.IP.String(), dst.Port))
if err != nil {
logger.Error("failed to connect to tcp proxy", "error", err)
return
Expand Down
2 changes: 1 addition & 1 deletion snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: aproxy
version: 0.2.5
version: 0.3.0
summary: Transparent proxy for HTTP and HTTPS/TLS connections.
description: |
Aproxy is a transparent proxy for HTTP and HTTPS/TLS connections. By
Expand Down