-
Notifications
You must be signed in to change notification settings - Fork 0
Develop #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Develop #4
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
9856642
Merge pull request #1 from gravitl/dependabot-yaml
abhishek9686 8c83b63
Merge pull request #2 from gravitl/master
abhishek9686 273ae2f
NM-295: organise the code, add L7 HTTP CONECT proxy
abhishek9686 c85c08b
Merge pull request #3 from gravitl/NM-295-L7
abhishek9686 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # To get started with Dependabot version updates, you'll need to specify which | ||
| # package ecosystems to update and where the package manifests are located. | ||
| # Please see the documentation for all configuration options: | ||
| # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file | ||
|
|
||
|
|
||
| version: 2 | ||
| updates: | ||
| # Enable version updates for netmaker | ||
| - package-ecosystem: "gomod" | ||
| directory: "/" | ||
| # Check for updates every day (weekdays) | ||
| schedule: | ||
| interval: "weekly" | ||
| target-branch: "develop" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,29 @@ | ||
| # proxy | ||
|
|
||
| WireGuard TCP/TLS uplink transport for Netmaker-style relay paths. | ||
| Netmaker proxy libraries (one Go module, feature packages). | ||
|
|
||
| ## Library | ||
| ## Packages | ||
|
|
||
| Import path: `github.com/gravitl/proxy` | ||
| | Import | Role | | ||
| |--------|------| | ||
| | [`github.com/gravitl/proxy/uplink`](uplink/) | Phase 1: TCP/TLS framed WireGuard uplink (C ↔ relay/gateway B) | | ||
| | [`github.com/gravitl/proxy/l7`](l7/) | L7: HTTP CONNECT forward proxy for app-domain egress | | ||
|
|
||
| There is **no** root package API — import the subpackage you need. | ||
|
|
||
| ### Uplink | ||
|
|
||
| - **Client**: TCP + TLS + framed `MsgData` carrying WireGuard packet bytes to the relay. | ||
| - **Server** (relay / gateway, also a WireGuard peer): terminates TLS, authenticates `ClientHello`, registers sessions, and supports `SendToPeer` for reverse traffic. | ||
|
|
||
| See package documentation and `example_test.go` for wiring patterns. | ||
| See [`uplink/example_test.go`](uplink/example_test.go) and [docs/PROXY_PHASE1_ARCHITECTURE.md](docs/PROXY_PHASE1_ARCHITECTURE.md). | ||
|
|
||
| Detailed Phase 1 plan and architecture: [docs/PROXY_PHASE1_ARCHITECTURE.md](docs/PROXY_PHASE1_ARCHITECTURE.md). | ||
| ### L7 | ||
|
|
||
| Name-based egress via HTTP CONNECT over the mesh (underlay remains WireGuard). See [docs/PROXY_L7_EGRESS.md](docs/PROXY_L7_EGRESS.md). | ||
|
|
||
| ## Develop | ||
|
|
||
| ```bash | ||
| go test ./... -race | ||
| ``` | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| // Package proxy provides a TCP/TLS framed transport for carrying WireGuard packet | ||
| // payloads between a relay-attached peer and its relay/gateway (Phase 1 uplink). | ||
| // Module github.com/gravitl/proxy hosts Netmaker proxy libraries as subpackages. | ||
| // | ||
| // It owns connection setup, TLS, framing, session lifecycle, keepalive, and | ||
| // peer→session registration for reverse traffic. It does not implement routing policy, | ||
| // relay selection, or Netmaker control-plane logic—integrate those in a separate adapter. | ||
| // Import paths: | ||
| // | ||
| // github.com/gravitl/proxy/uplink — TCP/TLS framed WireGuard uplink transport | ||
| // github.com/gravitl/proxy/l7 — HTTP CONNECT app-domain egress proxy | ||
| // | ||
| // The module root has no public API; use the packages above. | ||
| package proxy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # L7 egress proxy — HTTP CONNECT for app domains | ||
|
|
||
| **Package:** `github.com/gravitl/proxy/l7` | ||
| **Status:** HTTP CONNECT MVP implemented (listen, ACL, dial, bidirectional tunnel). | ||
|
|
||
| ## Problem | ||
|
|
||
| Egress “app domains” today often resolve domain → IPs, publish ranges, and install WireGuard routes. That is brittle (CDN churn, shared IPs, wildcards). | ||
|
|
||
| ## Approach | ||
|
|
||
| Keep the **WireGuard underlay** to the egress gateway. For listed domains, the **client** directs traffic to an L7 proxy on the gateway (proxy settings / PAC / netclient-managed). The gateway dials by **hostname**. | ||
|
|
||
| ```text | ||
| App --HTTPS--> client proxy settings | ||
| --CONNECT api.foo.com:443--> GW_mesh_IP:l7_port (over WG) | ||
| --GW dials api.foo.com--> internet | ||
| ``` | ||
|
|
||
| | Role | Responsibility | | ||
| |------|----------------| | ||
| | Client / control plane | Which domains use egress L7 | | ||
| | WireGuard | Path from client to egress GW | | ||
| | `l7.Server` on GW | CONNECT + domain ACL + dial-out | | ||
|
|
||
| CIDR / network egress remains L3. L7 is additive for named apps. | ||
|
|
||
| ## Package API | ||
|
|
||
| - `DomainMatcher` / `Allowlist` / `AllowAll` — exact and `*.suffix` domain rules | ||
| - `ServerOptions` — `ListenAddr`, `Matcher` (required), optional `Dialer`, timeouts, logger | ||
| - `Server.Start` / `Stop` / `Addr` — TCP listen and CONNECT handling | ||
| - Responses: `200 Connection Established`, `403` deny, `400` bad request, `502` dial failure | ||
|
|
||
| ## Non-goals (for now) | ||
|
|
||
| - SOCKS5, transparent TPROXY, TLS MITM | ||
| - PAC generation, UI, control-plane domain publishing (netclient follow-up) | ||
| - Sharing code with `uplink` framed WG transport | ||
|
|
||
| ## Relation to `uplink` | ||
|
|
||
| | `uplink` | `l7` | | ||
| |----------|------| | ||
| | Framed WG ciphertext C↔B | HTTP CONNECT to egress GW | | ||
| | Userspace Bind inject | `net.Dial` to internet hostnames | | ||
| | Same module, separate import | `github.com/gravitl/proxy/l7` | | ||
|
|
||
| ## Example (gateway side) | ||
|
|
||
| ```go | ||
| srv, err := l7.NewServer(l7.ServerOptions{ | ||
| ListenAddr: "0.0.0.0:3128", // prefer mesh IP in production | ||
| Matcher: l7.Allowlist{Domains: []string{"api.foo.com", "*.saas.com"}}, | ||
| }) | ||
| // srv.Start(ctx) … srv.Stop(ctx) | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package l7 | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "fmt" | ||
| "io" | ||
| "net" | ||
| "net/http" | ||
| "strings" | ||
| ) | ||
|
|
||
| const maxConnectHeaderBytes = 64 << 10 | ||
|
|
||
| // parseConnect reads one HTTP request from br and returns the CONNECT target. | ||
| // Only Method CONNECT is accepted. The remainder of br (if any) must be forwarded | ||
| // to the upstream after a successful tunnel setup. | ||
| func parseConnect(br *bufio.Reader) (ConnectTarget, *http.Request, error) { | ||
| req, err := http.ReadRequest(br) | ||
| if err != nil { | ||
| return ConnectTarget{}, nil, fmt.Errorf("%w: %v", ErrBadRequest, err) | ||
| } | ||
| if req.Method != http.MethodConnect { | ||
| return ConnectTarget{}, req, fmt.Errorf("%w: method %s", ErrBadRequest, req.Method) | ||
| } | ||
| hostPort := req.Host | ||
| if hostPort == "" && req.URL != nil { | ||
| hostPort = req.URL.Host | ||
| } | ||
| hostPort = strings.TrimSpace(hostPort) | ||
| if hostPort == "" { | ||
| return ConnectTarget{}, req, fmt.Errorf("%w: missing host", ErrBadRequest) | ||
| } | ||
| host, port, err := net.SplitHostPort(hostPort) | ||
| if err != nil { | ||
| // CONNECT without port — default 443 for HTTPS-style use. | ||
| host = hostPort | ||
| port = "443" | ||
| if strings.Contains(host, ":") { | ||
| return ConnectTarget{}, req, fmt.Errorf("%w: invalid host %q", ErrBadRequest, hostPort) | ||
| } | ||
| } | ||
| if host == "" || port == "" { | ||
| return ConnectTarget{}, req, fmt.Errorf("%w: invalid host %q", ErrBadRequest, hostPort) | ||
| } | ||
| return ConnectTarget{Host: host, Port: port}, req, nil | ||
| } | ||
|
|
||
| func writeConnectResponse(w io.Writer, status int, reason string) error { | ||
| _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n\r\n", status, reason) | ||
| return err | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // Package l7 provides an HTTP CONNECT forward proxy for name-based egress. | ||
| // | ||
| // Product intent: clients direct listed app domains to an egress gateway over the | ||
| // mesh (WireGuard underlay). The gateway runs this server, matches CONNECT targets | ||
| // against a domain policy, and dials the internet by hostname — avoiding brittle | ||
| // domain→IP→route collection used for L3 egress ranges. | ||
| // | ||
| // This package does not own Netmaker control-plane config, PAC generation, or | ||
| // WireGuard. See docs/PROXY_L7_EGRESS.md. | ||
| package l7 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package l7 | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
| ) | ||
|
|
||
| // DomainMatcher decides whether a CONNECT destination is allowed. | ||
| // Implementations are supplied by the integrator (e.g. netclient from control-plane lists). | ||
| type DomainMatcher interface { | ||
| // Allow returns nil if host (and optional port) may be dialed; otherwise a reason error. | ||
| Allow(host, port string) error | ||
| } | ||
|
|
||
| // Allowlist matches exact hostnames and optional "*.suffix" wildcards (one or more labels). | ||
| // Matching is case-insensitive. Empty Allowlist denies all hosts. | ||
| type Allowlist struct { | ||
| // Domains are exact names (example.com) or wildcards (*.example.com). | ||
| Domains []string | ||
| } | ||
|
|
||
| // Allow implements DomainMatcher. | ||
| func (a Allowlist) Allow(host, port string) error { | ||
| _ = port | ||
| host = strings.ToLower(strings.TrimSpace(host)) | ||
| if host == "" { | ||
| return fmt.Errorf("%w: empty host", ErrBadRequest) | ||
| } | ||
| for _, raw := range a.Domains { | ||
| pat := strings.ToLower(strings.TrimSpace(raw)) | ||
| if pat == "" { | ||
| continue | ||
| } | ||
| if strings.HasPrefix(pat, "*.") { | ||
| suf := pat[1:] // ".example.com" | ||
| if strings.HasSuffix(host, suf) && len(host) > len(suf) { | ||
| return nil | ||
| } | ||
| continue | ||
| } | ||
| if host == pat { | ||
| return nil | ||
| } | ||
| } | ||
| return fmt.Errorf("%w: %s", ErrForbidden, host) | ||
| } | ||
|
|
||
| // AllowAll permits any non-empty host (useful for tests; not for production egress). | ||
| type AllowAll struct{} | ||
|
|
||
| // Allow implements DomainMatcher. | ||
| func (AllowAll) Allow(host, port string) error { | ||
| _ = port | ||
| if strings.TrimSpace(host) == "" { | ||
| return fmt.Errorf("%w: empty host", ErrBadRequest) | ||
| } | ||
| return nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.