Skip to content

Commit

Permalink
Implement proxy support (#149)
Browse files Browse the repository at this point in the history
* Initial proxy implementation

* Fixes and updates

* Refactor proxy layout

* Remove unneded comments

* Return back entitlement check

* Support both Proxy-Authorization and simple Authorization headers

* Make token extraction more flexible

* Add connection limitiong (dumb version)

* Refactor connlimits

* Refactor proxy handling, use middleware

* Replace custom code with go-httpproxy

* Implement extracting token from Basic auth header

* Fix missing brace

* Rewrite proxy implementation

* Update gomod

* Disable httpv2 when proxy used, multiple fixes

* Remove noisy logging

* Reorganize code, fix docker warnings

* Add comment regarding disabling httpv2

* Refactor proxy startup

* Change instance fill order

* Rename conns to query

* Reduce server read timeout to 10 seconds

* Replace obsoleted code with errors.As

* Replase proxy auth error with warning
  • Loading branch information
RandoMan70 committed Jul 10, 2024
1 parent 2fef8ef commit 93d2a21
Show file tree
Hide file tree
Showing 15 changed files with 567 additions and 39 deletions.
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
FROM golang:1.21-alpine3.18 as toolset
FROM golang:1.21-alpine3.18 AS toolset

RUN apk add gcc make git musl-dev


FROM node:16-alpine3.14 as nodejs
FROM node:16-alpine3.14 AS nodejs

COPY ./frontend /app/
WORKDIR /app
RUN npm install && npm run build

from toolset as gomodules
FROM toolset AS gomodules
RUN apk add openssh-client
COPY go.mod /build/
COPY .gitconfig /root/
WORKDIR /build
RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh GOPRIVATE=github.com/vpnhouse go mod download

FROM gomodules as builder
FROM gomodules AS builder

COPY . /build
COPY --from=nodejs /app/dist /build/internal/frontend/dist/
Expand Down
26 changes: 26 additions & 0 deletions cmd/tunnel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/vpnhouse/tunnel/internal/ipdiscover"
"github.com/vpnhouse/tunnel/internal/iprose"
"github.com/vpnhouse/tunnel/internal/manager"
"github.com/vpnhouse/tunnel/internal/proxy"
"github.com/vpnhouse/tunnel/internal/runtime"
"github.com/vpnhouse/tunnel/internal/settings"
"github.com/vpnhouse/tunnel/internal/storage"
Expand Down Expand Up @@ -152,6 +153,23 @@ func initServices(runtime *runtime.TunnelRuntime) error {
}
}

// Create proxy server
var proxyServer *proxy.Instance
if runtime.Features.WithProxy() && runtime.Settings.Proxy != nil {
proxyServer, err = proxy.New(
runtime.Settings.Proxy,
jwtAuthorizer,
append(
runtime.Settings.Domain.ExtraNames,
runtime.Settings.Domain.PrimaryName,
))
if err != nil {
return err
}

runtime.Services.RegisterService("proxy", proxyServer)
}

// Prepare tunneling HTTP API
tunnelAPI := httpapi.NewTunnelHandlers(runtime, sessionManager, adminJWT, jwtAuthorizer, dataStorage, keyStore, ipv4am)

Expand All @@ -163,7 +181,15 @@ func initServices(runtime *runtime.TunnelRuntime) error {
if runtime.Settings.HTTP.CORS {
xhttpOpts = append([]xhttp.Option{xhttp.WithCORS()}, xhttpOpts...)
}
if proxyServer != nil {
xhttpOpts = append([]xhttp.Option{
xhttp.WithMiddleware(proxyServer.ProxyHandler),
xhttp.WithDisableHTTPv2(), // see task 97304 (fix http over httpv2 proxy issue)
}, xhttpOpts...)
}

// assume that config validation does not pass
// the SSL enabled without the domain name configuration
if runtime.Settings.SSL != nil {
redirectOnly := xhttp.NewRedirectToSSL(runtime.Settings.Domain.PrimaryName)
// we must start the redirect-only server before passing its Router
Expand Down
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/miekg/dns v1.1.55
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021
github.com/oschwald/maxminddb-golang v1.8.0
github.com/posener/h2conn v0.0.0-20231204025407-3997deeca0f0
github.com/prometheus/client_golang v1.12.1
github.com/rubenv/sql-migrate v1.0.0
github.com/slok/go-http-metrics v0.10.0
Expand All @@ -34,8 +35,8 @@ require (
go.etcd.io/etcd/client/v3 v3.5.2
go.uber.org/multierr v1.10.0
go.uber.org/zap v1.25.0
golang.org/x/net v0.2.0
golang.org/x/sys v0.11.0
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211230205640-daad0b7ba671
google.golang.org/grpc v1.44.0
google.golang.org/protobuf v1.27.1
Expand Down Expand Up @@ -93,11 +94,12 @@ require (
github.com/ziutek/mymysql v1.5.4 // indirect
go.etcd.io/etcd/api/v3 v3.5.2 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sync v0.6.0
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
golang.org/x/tools v0.3.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wireguard v0.0.0-20211129173154-2dd424e2d808 // indirect
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c // indirect
Expand Down
36 changes: 18 additions & 18 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,8 @@ github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/h2conn v0.0.0-20231204025407-3997deeca0f0 h1:zZg03nifrj6ayWNaDO8tNj57tqrOIKDwiBaLkhtK7Kk=
github.com/posener/h2conn v0.0.0-20231204025407-3997deeca0f0/go.mod h1:bblJa8QcHntareAJYfLJUzLj42sUFBKCBeTDK5LyUrw=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
Expand Down Expand Up @@ -814,8 +816,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7Zo
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vpnhouse/api v0.0.0-20240207081757-572ff9ef5b97 h1:FwO9Yeqikw7MxnUESAtb5Lpd/KEIMNy+fHRI7vYsj6o=
github.com/vpnhouse/api v0.0.0-20240207081757-572ff9ef5b97/go.mod h1:qeAZBOFAiz7FiTG49UremjHQExCUPui2tTdn1NMDn1s=
github.com/vpnhouse/iprose-go v0.1.0-rc15 h1:kd1RFQv3F4cmaJLpEppBYCVMBGW4zJ54aq31tS2iFbA=
github.com/vpnhouse/iprose-go v0.1.0-rc15/go.mod h1:BYBBO1eTgM9Vl/mLD2cCL0JF/X2dzsDtSrwDaHSoIWc=
github.com/vpnhouse/iprose-go v0.1.0-rc18 h1:GIujbCYM4FVTtcq72f1LeR2rrVcBZghh4Eh5CCp/TpY=
github.com/vpnhouse/iprose-go v0.1.0-rc18/go.mod h1:JPDz6koxs2qX0X5vxA1r3Kv1RgpSgT1drek47C+jrWc=
github.com/vultr/govultr/v2 v2.7.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU=
Expand Down Expand Up @@ -899,8 +899,8 @@ golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -937,8 +937,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -1001,8 +1001,8 @@ golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -1024,8 +1024,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -1121,12 +1121,12 @@ golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand All @@ -1136,8 +1136,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -1209,8 +1209,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
1 change: 1 addition & 0 deletions internal/authorizer/entitlements.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
Any EntitlementType = ""
Wireguard EntitlementType = "wireguard"
IPRose EntitlementType = "iprose"
Proxy EntitlementType = "proxy"
)

type jwtAuthorizerEntitlement struct {
Expand Down
35 changes: 35 additions & 0 deletions internal/proxy/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package proxy

import (
"encoding/base64"
"net/http"
"strings"

"github.com/vpnhouse/tunnel/pkg/xhttp"
"go.uber.org/zap"
)

const (
headerProxyAuthorization = "Proxy-Authorization"
authTypeBasic = "basic"
)

func extractProxyAuthToken(r *http.Request) (string, bool) {
authType, authInfo := xhttp.ExtractAuthorizationInfo(r, headerProxyAuthorization)
if authInfo == "" {
return "", false
}

if strings.ToLower(authType) != authTypeBasic {
zap.L().Debug("Invalid authentication type")
return "", false
}

userpass, err := base64.StdEncoding.DecodeString(authInfo)
if err != nil {
zap.L().Debug("Failed to extract authentication token", zap.Error(err))
return "", false
}

return string(userpass[:len(userpass)-1]), true
}
142 changes: 142 additions & 0 deletions internal/proxy/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package proxy

import (
"net/http"
"strings"
"sync/atomic"
"time"

"github.com/vpnhouse/tunnel/internal/authorizer"
"github.com/vpnhouse/tunnel/pkg/auth"
"github.com/vpnhouse/tunnel/pkg/xerror"
"github.com/vpnhouse/tunnel/pkg/xhttp"
)

type Config struct {
ConnLimit int `yaml:"conn_limit"`
ConnTimeout time.Duration `yaml:"conn_timeout"`
MarkHeaderPrefix string `yaml:"mark_header_prefix"`
MarkHeaderRandomLength uint `yaml:"mark_header_random_length"`
}

type Instance struct {
config *Config
authorizer authorizer.JWTAuthorizer
users *userStorage
myDomains map[string]struct{}
proxyMarkHeader string
terminated atomic.Bool
}

func New(config *Config, jwtAuthorizer authorizer.JWTAuthorizer, myDomains []string) (*Instance, error) {
if config == nil {
return nil, xerror.EInternalError("No configuration", nil)
}

domains := make(map[string]struct{})
for _, domain := range myDomains {
domains[domain] = struct{}{}
}

markHeaderLength := config.MarkHeaderRandomLength
if markHeaderLength == 0 {
markHeaderLength = 8
}

return &Instance{
config: config,
authorizer: authorizer.WithEntitlement(jwtAuthorizer, authorizer.Proxy),
users: newUserStorage(config.ConnLimit),
myDomains: domains,
proxyMarkHeader: config.MarkHeaderPrefix + randomString(markHeaderLength),
}, nil
}

func (instance *Instance) Shutdown() error {
if instance.terminated.Swap(true) {
return xerror.EInternalError("Double proxy shutdown", nil)
}

return nil
}

func (instance *Instance) Running() bool {
return instance.terminated.Load()
}

func (instance *Instance) isMyRequest(r *http.Request) bool {
hostParts := strings.Split(r.Host, ":")
_, myDomain := instance.myDomains[hostParts[0]]
return myDomain
}

func (instance *Instance) cycledProxy(r *http.Request) bool {
_, cycled := r.Header[instance.proxyMarkHeader]
return cycled
}

func (instance *Instance) ProxyHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if (instance.isMyRequest(r) || instance.cycledProxy(r)) && (r.Method != http.MethodConnect) {
next.ServeHTTP(w, r)
} else {
instance.doProxy(w, r)
}
})
}

func (instance *Instance) doAuth(r *http.Request) (string, error) {
userToken, ok := extractProxyAuthToken(r)
if !ok {
return "", xerror.WAuthenticationFailed("proxy", "no auth token", nil)
}

token, err := instance.authorizer.Authenticate(userToken, auth.AudienceTunnel)
if err != nil {
return "", err
}

return token.UserId, nil
}

func (instance *Instance) doProxy(w http.ResponseWriter, r *http.Request) {
userId, err := instance.doAuth(r)
if err != nil {
w.Header()["Proxy-Authenticate"] = []string{"Basic realm=\"proxy\""}
w.WriteHeader(http.StatusProxyAuthRequired)
w.Write([]byte("Proxy authentication required"))
return
}

user, err := instance.users.acquire(r.Context(), userId)
if err != nil {
http.Error(w, "Limit exceeded", http.StatusTooManyRequests)
xhttp.WriteJsonError(w, err)
return
}
defer instance.users.release(userId, user)

query := &ProxyQuery{
userId: userId,
userInfo: user,
id: queryCounter.Add(1),
proxyInstance: instance,
}

if r.Method == "CONNECT" {
if r.ProtoMajor == 1 {
query.handleV1Connect(w, r)
return
}

if r.ProtoMajor == 2 {
query.handleV2Connect(w, r)
return
}

http.Error(w, "Unsupported protocol version", http.StatusHTTPVersionNotSupported)
return
} else {
query.handleProxy(w, r)
}
}
Loading

0 comments on commit 93d2a21

Please sign in to comment.