diff --git a/go.mod b/go.mod index 82d5890..6f44247 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,8 @@ require ( github.com/DeRuina/timberjack v1.4.2 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/awnumar/memcall v0.4.0 // indirect + github.com/awnumar/memguard v0.23.0 // indirect github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect @@ -37,9 +39,11 @@ require ( github.com/coder/websocket v1.8.14 // indirect github.com/coreos/go-iptables v0.7.0 // indirect github.com/creack/pty v1.1.24 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gliderlabs/ssh v0.3.8 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -53,8 +57,10 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/huin/goupnp v1.2.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kardianos/service v1.2.3-0.20240613133416-becf2eb62b83 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/koron/go-ssdp v0.0.4 // indirect @@ -68,6 +74,7 @@ require ( github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/socket v0.5.1 // indirect + github.com/mdp/qrterminal/v3 v3.2.1 // indirect github.com/mholt/acmez/v2 v2.0.1 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect @@ -93,6 +100,8 @@ require ( github.com/shoenig/go-m1cpu v0.2.1 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/things-go/go-socks5 v0.0.4 // indirect github.com/ti-mo/conntrack v0.5.1 // indirect github.com/ti-mo/netfilter v0.5.2 // indirect @@ -112,6 +121,7 @@ require ( golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.43.0 // indirect @@ -124,6 +134,7 @@ require ( google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gvisor.dev/gvisor v0.0.0-20260219192049-0f2374377e89 // indirect + rsc.io/qr v0.2.0 // indirect ) replace github.com/dexidp/dex => github.com/netbirdio/dex v0.244.1-0.20260512110716-8d70ad8647c1 diff --git a/go.sum b/go.sum index 201a1fa..287fc7d 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,10 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g= +github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w= +github.com/awnumar/memguard v0.23.0 h1:sJ3a1/SWlcuKIQ7MV+R9p0Pvo9CWsMbGZvcZQtmc68A= +github.com/awnumar/memguard v0.23.0/go.mod h1:olVofBrsPdITtJ2HgxQKrEYEMyIBAIciVG4wNnZhW9M= github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk= github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= @@ -104,6 +108,7 @@ github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d4 github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -165,6 +170,8 @@ github.com/go-webauthn/webauthn v0.16.4 h1:R9jqR/cYZa7hRquFF7Za/8qoH/K/TIs1/Q/4C github.com/go-webauthn/webauthn v0.16.4/go.mod h1:SU2ljAgToTV/YLPI0C05QS4qn+e04WpB5g1RMfcZfS4= github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA= github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -231,6 +238,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -254,6 +263,8 @@ github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7X github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kardianos/service v1.2.3-0.20240613133416-becf2eb62b83 h1:q+kfN+Jh0AAUp1QPSBPxPEQrS5rkFr6b5qilQ6pEfk8= +github.com/kardianos/service v1.2.3-0.20240613133416-becf2eb62b83/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -298,6 +309,8 @@ github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0 github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= +github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= @@ -426,6 +439,7 @@ github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks= github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= @@ -446,6 +460,10 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EE github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -531,6 +549,7 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= goauthentik.io/api/v3 v3.2023051.3 h1:NebAhD/TeTWNo/9X3/Uj+rM5fG1HaiLOlKTNLQv9Qq4= goauthentik.io/api/v3 v3.2023051.3/go.mod h1:nYECml4jGbp/541hj8GcylKQG1gVBsKppHy4+7G8u4U= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -585,6 +604,7 @@ golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -686,3 +706,5 @@ gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gvisor.dev/gvisor v0.0.0-20260219192049-0f2374377e89 h1:mGJaeA61P8dEHTqdvAgc70ZIV3QoUoJcXCRyyjO26OA= gvisor.dev/gvisor v0.0.0-20260219192049-0f2374377e89/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index fcb5da3..0428f6a 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -25,6 +25,17 @@ import ( "github.com/netbirdio/netbird/shared/management/http/api" ) +const ( + AcceptHeader = "Accept" + AcceptEncodingHeader = "Accept-Encoding" + ContentLengthHeader = "Content-Length" + ContentTypeHeader = "Content-Type" + UserAgentHeader = "User-Agent" + AuthorizationHeader = "Authorization" + ImpersonateUserHeader = "Impersonate-User" + ImpersonateGroupHeader = "Impersonate-Group" +) + type PeerLister interface { List(ctx context.Context, opts ...netbird.PeersListOption) ([]api.Peer, error) } @@ -38,15 +49,8 @@ func Server(embedClient *embed.Client, peerLister PeerLister, kubeAPIServerURL * if err != nil { return nil, err } + handler := proxyHandler(peerLister, kubeAPIServerURL, certPool, bearerToken) - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = &tls.Config{ - RootCAs: certPool, - } - proxy := &httputil.ReverseProxy{ - Transport: transport, - Rewrite: rewriteHandler(peerLister, kubeAPIServerURL, bearerToken), - } stat, err := embedClient.Status() if err != nil { return nil, err @@ -60,21 +64,23 @@ func Server(embedClient *embed.Client, peerLister PeerLister, kubeAPIServerURL * Certificates: []tls.Certificate{proxyCert}, MinVersion: tls.VersionTLS12, }, - Handler: proxy, + Handler: handler, ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, } return &srv, nil } -func rewriteHandler(peerLister PeerLister, kubeAPIServerURL *url.URL, bearerToken string) func(*httputil.ProxyRequest) { - return func(pr *httputil.ProxyRequest) { +func proxyHandler(peerLister PeerLister, kubeAPIServerURL *url.URL, certPool *x509.CertPool, bearerToken string) http.HandlerFunc { + type peerCtxKey struct{} + + rewrite := func(pr *httputil.ProxyRequest) { allowedHeaders := map[string]any{ - "Accept": nil, - "Accept-Encoding": nil, - "Content-Length": nil, - "Content-Type": nil, - "User-Agent": nil, + AcceptHeader: nil, + AcceptEncodingHeader: nil, + ContentLengthHeader: nil, + ContentTypeHeader: nil, + UserAgentHeader: nil, } for k := range pr.Out.Header { if _, ok := allowedHeaders[k]; !ok { @@ -82,27 +88,47 @@ func rewriteHandler(peerLister PeerLister, kubeAPIServerURL *url.URL, bearerToke } } - remoteIP, _, err := net.SplitHostPort(pr.In.RemoteAddr) - if err != nil { + peer, ok := pr.In.Context().Value(peerCtxKey{}).(api.Peer) + if !ok { return } - listCtx, listCancel := context.WithTimeout(pr.In.Context(), 10*time.Second) + pr.Out.Header.Set(ImpersonateUserHeader, peer.UserId) + for _, group := range peer.Groups { + pr.Out.Header.Add(ImpersonateGroupHeader, group.Name) + } + pr.Out.Header.Set(AuthorizationHeader, "Bearer "+bearerToken) + pr.SetURL(kubeAPIServerURL) + } + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = &tls.Config{ + RootCAs: certPool, + MinVersion: tls.VersionTLS12, + } + proxy := &httputil.ReverseProxy{ + Transport: transport, + Rewrite: rewrite, + } + + return func(rw http.ResponseWriter, req *http.Request) { + remoteIP, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + return + } + listCtx, listCancel := context.WithTimeout(req.Context(), 10*time.Second) defer listCancel() peers, err := peerLister.List(listCtx, netbird.PeerIPFilter(remoteIP)) if err != nil { + rw.WriteHeader(http.StatusInternalServerError) return } if len(peers) != 1 { + rw.WriteHeader(http.StatusUnauthorized) return } - peer := peers[0] - pr.Out.Header.Set("Impersonate-User", peer.UserId) - for _, group := range peer.Groups { - pr.Out.Header.Add("Impersonate-Group", group.Name) - } - pr.Out.Header.Set("Authorization", "Bearer "+bearerToken) - pr.SetURL(kubeAPIServerURL) + peerCtx := context.WithValue(req.Context(), peerCtxKey{}, peers[0]) + proxy.ServeHTTP(rw, req.WithContext(peerCtx)) } } diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index dc12eec..fb32d4e 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -6,10 +6,11 @@ import ( "context" "crypto/x509" "fmt" + "io" "net/http" "net/http/httptest" - "net/http/httputil" "net/url" + "strings" "testing" "github.com/go-openapi/testify/v2/require" @@ -34,14 +35,14 @@ func (p *mockPeerLister) List(ctx context.Context, opts ...netbird.PeersListOpti if ip != "" { peer, ok := p.peers[ip] if !ok { - return nil, fmt.Errorf("peer with ip %s could not be found", ip) + return nil, nil } return []api.Peer{peer}, nil } return nil, nil } -func TestRewriteHandler(t *testing.T) { +func TestProxyHandler(t *testing.T) { t.Parallel() peerLister := &mockPeerLister{ @@ -50,27 +51,88 @@ func TestRewriteHandler(t *testing.T) { UserId: "foo", Groups: []api.GroupMinimum{ { - Name: "bar", + Name: "group1", + }, + { + Name: "group2", }, }, }, }, } - serverURL, err := url.Parse("https://internal.name") - require.NoError(t, err) bearerToken := "foobar" - handler := rewriteHandler(peerLister, serverURL, bearerToken) + srv := httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + token, _ := strings.CutPrefix(req.Header.Get("Authorization"), "Bearer ") + if token != bearerToken { + rw.WriteHeader(http.StatusUnauthorized) + return + } + if req.URL.Path != "/version" { + rw.WriteHeader(http.StatusNotFound) + return + } + body := fmt.Sprintf("%s %s %s", req.Header[AuthorizationHeader], req.Header[ImpersonateUserHeader], req.Header[ImpersonateGroupHeader]) + // nolint: errcheck + rw.Write([]byte(body)) + })) + t.Cleanup(func() { + srv.Close() + }) + certPool := srv.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs + kubeAPIServerURL, err := url.Parse(srv.URL) + require.NoError(t, err) - req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/version", nil) - pr := &httputil.ProxyRequest{ - In: req, - Out: req.Clone(t.Context()), + tests := []struct { + name string + remoteAddr string + headers map[string]string + expectedStatus int + expectedBody string + }{ + { + name: "valid peer", + headers: nil, + expectedStatus: http.StatusOK, + expectedBody: "[Bearer foobar] [foo] [group1 group2]", + }, + { + name: "valid peer with bearer token", + headers: map[string]string{ + AuthorizationHeader: "Bearer testtest", + }, + expectedStatus: http.StatusOK, + expectedBody: "[Bearer foobar] [foo] [group1 group2]", + }, + { + name: "no peer found", + remoteAddr: "192.0.2.2:123", + headers: nil, + expectedStatus: http.StatusUnauthorized, + expectedBody: "", + }, } - handler(pr) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - require.EqualT(t, "example.com", pr.In.Host) - require.EqualT(t, "https://internal.name/version", pr.Out.URL.String()) + req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/version", nil) + if tt.remoteAddr != "" { + req.RemoteAddr = tt.remoteAddr + } + for k, v := range tt.headers { + req.Header.Add(k, v) + } + rec := httptest.NewRecorder() + handler := proxyHandler(peerLister, kubeAPIServerURL, certPool, bearerToken) + handler(rec, req) + b, err := io.ReadAll(rec.Result().Body) + require.NoError(t, err) + + require.EqualT(t, tt.expectedStatus, rec.Result().StatusCode) + require.EqualT(t, tt.expectedBody, string(b)) + }) + } } func TestGenerateSelfSignedCert(t *testing.T) { diff --git a/main.go b/main.go index 3aedf84..bbb7427 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "golang.org/x/sync/errgroup" + "github.com/netbirdio/netbird/client/cmd" "github.com/netbirdio/netbird/client/embed" netbird "github.com/netbirdio/netbird/shared/management/client/rest" @@ -67,7 +68,7 @@ func run(ctx context.Context, kubeAPIServer, mgmtURL, apiKey, setupKey, instance SetupKey: setupKey, DeviceName: instanceName, LogOutput: io.Discard, - DNSLabels: []string{clusterName + "." + "netbird-kubeapi-proxy"}, + DNSLabels: []string{clusterName + "." + cmd.KubernetesDNSSuffix}, } embedClient, err := embed.New(opts) if err != nil {