Skip to content

Commit 605e830

Browse files
KCarrettogoogle-labs-jules[bot]cmp5987hultoclaude
authored
[feature] Portals! (#1484)
* initial stubbing and renaming * feat: implement tavern/portals/stream package (#1462) This commit introduces a new package `stream` in `tavern/portals` which provides utilities for handling ordered streams of `portalpb.Mote` messages. Key features: - `payloadSequencer`: Handles atomic sequence ID generation and mote creation. - `OrderedWriter`: Wraps a sender function (like a gRPC stream Send) to automatically sequence and write messages. - `OrderedReader`: Wraps a receiver function (like a gRPC stream Recv) to reorder incoming messages, handling out-of-order delivery with configurable buffering and stale stream detection. This package is designed to support both client and server sides of the portal stream. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * refactor reader to use functional API * Implement PubSub Multiplexer (Mux) (#1466) * Implement PubSub Multiplexer (Mux) for Portals - Created `Mux` package in `tavern/internal/portals/mux`. - Implemented dual-mode operation: In-Memory (for dev) and GCP PubSub (for prod). - Implemented `CreatePortal` and `OpenPortal` lifecycle methods with resource provisioning. - Implemented `Publish` and `Subscribe` logic with local broadcasting (fast path) and global PubSub (slow path). - Added `HistoryBuffer` for message replay. - Added intelligent topic caching to handle `mempubsub` quirks and improve performance. - Added Prometheus metrics for observability. - Verified with comprehensive unit tests using `enttest` and `mempubsub`. * Implement PubSub Multiplexer (Mux) for Portals - **Mux Package:** Created `tavern/internal/portals/mux` to route messages between local streams and global PubSub. - **Dual-Mode Operation:** Implemented support for In-Memory (dev) and GCP PubSub (prod) drivers via `gocloud.dev/pubsub`. - **Portal Lifecycle:** Added `CreatePortal` and `OpenPortal` methods managing resource provisioning, database records, and subscription lifecycles. - **Message Routing:** Implemented `Publish` (fast-path local dispatch, slow-path global send) and `Subscribe` (local channel registration). - **History Management:** Added `HistoryBuffer` in `history.go` for message replay to new subscribers. - **Concurrency & Robustness:** - Handled race conditions in shared subscription creation (`OpenPortal`). - Managed `receiveLoop` lifecycle using stored `cancelFuncs`. - Added `AlreadyExists` error handling for resource creation. - Used `sync.RWMutex` for thread safety (`activeSubs`, `subscribers`, `histMu`). - **Observability:** Added Prometheus metrics for message counts. - **Testing:** Comprehensive unit tests covering In-Memory flow, Portal creation, and Portal opening. * Refactor Mux and Address PR Feedback - **Structure:** Refactored `Mux` to use composed structs (`SubscriptionManager`, `SubscriberRegistry`, `TopicManager`, `HistoryManager`) for clearer locking semantics. - **Safety:** - Handled race conditions in `CreatePortal` and `OpenPortal` by re-checking state after lock acquisition. - Updated `teardown` logic to use `client.Portal.UpdateOneID` for reliability. - Standardized on `context.Background()` for shutdown operations to prevent context leaks. - **Features:** - Added `WithSubscriberBufferSize` to configure channel buffers. - Added `WithHistoryReplay` option to `Subscribe` for optional history. - Added `mux_messages_dropped_total` metric. - **Concurrency:** Moved global lock handling into granular manager structs to reduce contention. - **Correctness:** Fixed `CreatePortal` to use task-based lookup for dependencies and removed invalid `portalID` parameter usage in logic (though kept signature for now as per instructions). Tests passed. * Implement PubSub Multiplexer (Mux) for Portals - **Mux Package:** Created `tavern/internal/portals/mux` to route messages between local streams and global PubSub. - **Dual-Mode Operation:** Implemented support for In-Memory (dev) and GCP PubSub (prod) drivers via `gocloud.dev/pubsub`. - **Portal Lifecycle:** Added `CreatePortal` and `OpenPortal` methods managing resource provisioning, database records, and subscription lifecycles. - **Message Routing:** Implemented `Publish` (fast-path local dispatch, slow-path global send) and `Subscribe` (local channel registration). - **History Management:** Added `HistoryBuffer` in `history.go` for message replay to new subscribers. - **Concurrency & Robustness:** - Handled race conditions in shared subscription creation (`OpenPortal`). - Managed `receiveLoop` lifecycle using stored `cancelFuncs`. - Added `AlreadyExists` error handling for resource creation. - Used composed structs (`SubscriptionManager`, `SubscriberRegistry`) for granular locking. - **Observability:** Added Prometheus metrics for message counts and dropped messages. - **Testing:** Comprehensive unit tests covering In-Memory flow, Portal creation, Portal opening, and Benchmarks. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * both grpc endpoints * stub out portal-stream package * Implement portal-stream crate (#1470) * Implement portal-stream crate with sequencer, reader, and writer logic - Added `implants/lib/portals/portal-stream` crate. - Implemented `PayloadSequencer` for atomic sequence ID generation. - Implemented `OrderedReader` for reordering incoming messages with timeout and buffer handling. - Implemented `OrderedWriter` for sequencing outgoing messages. - Added comprehensive unit tests for all components. - Added crate to `implants` workspace. * Switch to anyhow for error handling in portal-stream - Replaced `thiserror` with `anyhow` in `portal-stream`. - Updated `Cargo.toml` to use `anyhow` from workspace. - Updated `reader.rs` and tests to use `anyhow::Result` and `anyhow!`. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Implement SOCKS5 Proxy using tavern/portals/stream (#1467) * Implement SOCKS5 proxy with gRPC tunneling - Added bin/socks5/proxy.go implementing a SOCKS5 proxy server. - Implemented tunneling over stream.OrderedWriter/Reader. - Supported TCP CONNECT and UDP ASSOCIATE commands. - Implemented robust lifecycle management and cleanup. - Added benchmarks in bin/socks5/proxy_test.go demonstrating high throughput. * Address PR comments: Refactor writes, defaults, and shutdown tracking - Refactored raw `conn.Write` calls into named helper functions. - Changed default upstream port to 8000. - Added `sync.WaitGroup` to track connection lifecycle. - Added logging for dropped motes in dispatcher. - Defined `maxStreamBufferedMessages` constant. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Implement Portal Infrastructure in ImixV2 (#1471) * Implement portal infrastructure in imixv2 - Added `portal-stream` dependency to `imixv2`. - Updated `Transport` trait to include `create_portal` (async). - Implemented `create_portal` in `grpc` transport. - Updated `Agent` trait to include `create_portal`. - Created `imixv2/src/portal/` module with TCP, UDP, and Bytes support using `portal-stream`. - Implemented `create_portal` in `ImixAgent`. - Exposed `create_portal` via `eldritch-libpivot`. - Updated `portal-stream` to support async writers. * Implement portal infrastructure in imixv2 - Added `portal-stream` dependency to `imixv2`. - Updated `Transport` trait to include `create_portal` (async). - Implemented `create_portal` in `grpc` transport. - Updated `Agent` trait to include `create_portal`. - Created `imixv2/src/portal/` module with TCP, UDP, and Bytes support using `portal-stream`. - Implemented `create_portal` in `ImixAgent`. - Exposed `create_portal` via `eldritch-libpivot`. - Updated `portal-stream` to support async writers. - Updated `run_create_portal` to send initial registration message. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * some changes * Add integration test * Added TRACE Bytes kind * added trace protos * Implement Trace Motes for Portals (#1473) * feat: implement end-to-end trace motes Implements application-level tracing for Portals infrastructure using the new `tracepb` definitions. * **CLI (`bin/socks5`)**: * Added `trace` subcommand to generate trace motes, send them to the server, and print a latency report. * Refactored `proxy.go` to support subcommands. * Added `addTraceEvent` helper for modifying trace motes. * **Server (`tavern`)**: * Instrumented `api_open_portal.go` and `api_create_portal.go` to inject trace events at key checkpoints (Recv, Pub, Sub, Send). * Created `trace_helper.go` to share event injection logic. * **Agent (`imixv2`)**: * Updated `run.rs` to intercept `BYTES_PAYLOAD_KIND_TRACE` motes. * Implemented logic to add `AGENT_RECV` and `AGENT_SEND` events and immediately echo the mote back. * added retry to trace --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: KCarretto <[email protected]> * update flag to --portal * update buf size * Update portal API to send keepalive motes (#1472) - Added keepalive ticker to sendPortalInput loop in api_create_portal.go - Sends a BYTES_PAYLOAD_KIND_KEEPALIVE mote at regular intervals - Prevents connection timeouts similar to the reverse shell implementation Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: KCarretto <[email protected]> * add tokio-console support for imixv2 * Fix SOCKS5 proxy cold start hang by avoiding BiLock on TcpStream (#1476) Replaced `tokio::io::split(stream)` with `stream.into_split()` in `implants/imixv2/src/portal/tcp.rs`. The former uses a `BiLock` which can cause deadlocks when the read and write halves are accessed concurrently in separate tasks, specifically causing the "cold start" hang where the initial payload might be blocked. `into_split()` returns owned halves that operate independently. Added a regression test `implants/imixv2/src/tests/repro_issue.rs` to verify the fix. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: KCarretto <[email protected]> * socks proxy sends registration message now * socks5 trace must send registration message * Add E2E Portals Workflow and Playwright Test (#1478) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: KCarretto <[email protected]> * cargo fmt * remove spammy log line * fix some tests * add create_portal fake impl * revert some changes to grpc.rs * remove grpc explicit sizes * update socks5 proxy to support auth and portals to support gcp pubsub * oops * minor cleanup * fix tests * Fix e2e workflow * use env var for auth * wait for socks to start before continuing * Add benchmark tests for cryptocodec (#1485) Added a new test file `tavern/internal/cryptocodec/cryptocodec_bench_test.go` to measure the throughput of `Encrypt` and `Decrypt` methods in `CryptoSvc`. Includes benchmarks for both encryption and decryption operations using standard payload sizes. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Fix race condition in TestPortalIntegration (#1487) The `TestPortalIntegration` test was flaky and often hung because the Agent would publish messages to the portal before the User had successfully subscribed to the output topic. This resulted in messages being dropped (as evidenced by "message sent to topic with no subscribers" warnings from the in-memory pubsub) and the User reader blocking indefinitely. This commit replaces the arbitrary `time.Sleep` with a deterministic synchronization mechanism. The User now sends a "ping" message to the Agent immediately after opening the portal. The Agent waits to receive this ping before proceeding. This ensures that the User's portal connection (and thus the underlying pubsub subscription) is fully established before the Agent attempts to send any data. This reduces test execution time and eliminates the race condition. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix proxy upstream address parsing * minor fixes & cleanup * fix collapsible if * cargo fmt * fix portal workflow * Add Tavern User Guide for Authentication and API Token (#1501) * Add documentation for TAVERN_API_TOKEN and portal auth flow This commit adds a new user guide page for Tavern (`docs/_docs/user-guide/tavern.md`) detailing the purpose of `TAVERN_API_TOKEN`. It clarifies the distinction between this token and the web OAuth token and explains the "portal auth flow" for users SSH'd into remote environments (e.g., Kali VMs) where standard auth port forwarding is not feasible. * Update TAVERN_API_TOKEN docs: remove non-existent Portal Auth Flow Per code review feedback, the "Portal Auth Flow" feature does not exist yet. This commit removes that section from the documentation, leaving the explanation of what the token is and when to use it (SSH scenarios). --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * cleanup * Merge origin/main, resolve conflicts, and fix go generate (#1515) - Merged changes from origin/main, resolving conflicts in app.go, server.go, and mock transport. - Updated `golang.org/x/tools` to fix `ent` generation failure ("context without types"). - Re-ran `go generate ./...` to update generated protobuf and ent code. - Fixed compilation errors in tests due to renamed protobuf enum constants (ActiveTransport_TRANSPORT_HTTP1). Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Add offline filters (#1505) * feat(online-offline): Online Offline in progress * fix(online-offline): Change text * fix(query): Simplify query * fix(build): Build UI * fix(create-quest): Hide filter from create quest * Add Link entity and update CDN to use link-based file access (#1444) * Add Link entity and update CDN to use link-based file access This commit implements a new Link entity system for the CDN: - Add Link entity schema with: - path (unique, default UUIDv4) - active_before (timestamp, default epoch 0) - active_clicks (int, default 0) - edge to File entity (one-to-many relationship) - Update CDN upload handler to: - Create a new Link entity for each uploaded file - Return both file ID and link path in response - Add new CDN link download handler to: - Serve files using Link entity path instead of file name - Check active_clicks and active_before before serving - Decrement active_clicks when file is served - Return 404 if link is not active - Replace CDN route to use link-based downloads: - Changes /cdn/ endpoint from file name access to link path access - Removes ability to serve tome assets via direct file name - Maintains FetchAsset gRPC API for agent communication - Update dependencies: - Upgrade entgo.io/ent from v0.14.1 to v0.14.5 - Update related dependencies (atlas, sqlite3, tools) * go generate * Add mutations * Clarify docs * typeable random short string * Don't create link on upload * Resolve feedback --------- Co-authored-by: Claude <[email protected]> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Squidli <[email protected]> Co-authored-by: Hulto <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent f74a1fb commit 605e830

File tree

120 files changed

+15947
-807
lines changed

Some content is hidden

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

120 files changed

+15947
-807
lines changed

.github/workflows/e2e-portals.yml

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
name: E2E Portals Test 🌀
2+
3+
on:
4+
workflow_dispatch: ~
5+
push:
6+
branches: [ main ]
7+
pull_request:
8+
branches: [ main ]
9+
10+
jobs:
11+
e2e_portals_test:
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 30
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: ⚡ Setup Golang
17+
uses: actions/setup-go@v5
18+
with:
19+
go-version-file: go.mod
20+
cache: true
21+
22+
- name: Setup Rust
23+
uses: dtolnay/rust-toolchain@master
24+
with:
25+
toolchain: 'stable'
26+
default: true
27+
profile: minimal
28+
components: rustfmt, clippy
29+
30+
- name: ⚡ Setup Node.js
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: '20'
34+
cache: 'npm'
35+
cache-dependency-path: tests/e2e/package-lock.json
36+
37+
- name: 📦 Install System Dependencies
38+
run: |
39+
sudo apt-get update
40+
sudo apt-get install -y protobuf-compiler libssl-dev jq
41+
42+
- name: 🔨 Build Tavern & Socks5
43+
run: |
44+
go mod download
45+
go build -v -o tavern_bin ./tavern
46+
go build -v -o socks5_bin ./bin/socks5
47+
48+
- name: 🚀 Run Tavern
49+
env:
50+
HTTP_LISTEN_ADDR: ":8000"
51+
run: |
52+
./tavern_bin > tavern.log 2>&1 &
53+
echo "Waiting for Tavern to start..."
54+
# Wait for port 8000
55+
timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' localhost 8000
56+
57+
- name: 🤖 Build & Run Agent
58+
working-directory: implants/imixv2
59+
env:
60+
IMIX_CALLBACK_URI: "http://localhost:8000"
61+
IMIX_CALLBACK_INTERVAL: 1
62+
run: |
63+
# Fetch the pubkey and verify it's not empty
64+
PUBKEY=$(curl -s http://localhost:8000/status | jq -r .Pubkey)
65+
if [ -z "$PUBKEY" ] || [ "$PUBKEY" == "null" ]; then
66+
echo "Error: Could not fetch Pubkey from Tavern"
67+
exit 1
68+
fi
69+
export IMIX_SERVER_PUBKEY=$PUBKEY
70+
echo "Got pubkey: $IMIX_SERVER_PUBKEY"
71+
72+
echo "Building imixv2..."
73+
cargo build --bin imixv2 --target-dir ./build
74+
# Run agent and pipe logs to a file
75+
./build/debug/imixv2 > agent.log 2>&1 &
76+
77+
# Give the agent a moment to perform the initial handshake
78+
echo "Agent started. Waiting for initial callback..."
79+
sleep 5
80+
81+
- name: 🎭 Install Playwright
82+
working-directory: tests/e2e
83+
run: |
84+
npm ci
85+
npx playwright install --with-deps chromium
86+
87+
- name: 🧪 Run Portal Provisioning Test
88+
working-directory: tests/e2e
89+
run: |
90+
npx playwright test tests/portal.spec.ts
91+
92+
- name: 🔍 Retrieve Portal ID
93+
id: get_portal_id
94+
run: |
95+
QUERY='query { portals { edges { node { id } } } }'
96+
# Send GraphQL query to Tavern
97+
RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" \
98+
-d "{\"query\": \"$QUERY\"}" http://localhost:8000/graphql)
99+
100+
echo "GraphQL Response: $RESPONSE"
101+
102+
PORTAL_ID=$(echo $RESPONSE | jq -r '.data.portals.edges[0].node.id')
103+
104+
if [ -z "$PORTAL_ID" ] || [ "$PORTAL_ID" == "null" ]; then
105+
echo "Error: Could not retrieve Portal ID"
106+
exit 1
107+
fi
108+
109+
echo "PORTAL_ID=$PORTAL_ID" >> $GITHUB_ENV
110+
echo "Retrieved Portal ID: $PORTAL_ID"
111+
112+
- name: 🌐 Start Socks5 Proxy
113+
env:
114+
TAVERN_API_TOKEN: ${{ secrets.TEST_TAVERN_API_TOKEN }}
115+
run: |
116+
./socks5_bin -portal $PORTAL_ID -listen 127.0.0.1:1080 -upstream 127.0.0.1:8000 &
117+
echo "Socks5 Proxy started on port 1080"
118+
# Wait for port 1080
119+
timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' localhost 1080
120+
121+
- name: 📄 Prepare Mock Data & Server
122+
run: |
123+
# Generate 1MB random file
124+
dd if=/dev/urandom of=test_data.bin bs=1M count=1
125+
sha256sum test_data.bin > original_checksum.txt
126+
echo "Original Checksum:"
127+
cat original_checksum.txt
128+
129+
# Start Python HTTP Server on port 9000
130+
python3 -m http.server 9000 &
131+
echo "HTTP Server started on port 9000"
132+
sleep 2
133+
134+
- name: 🔄 Transfer & Verification
135+
run: |
136+
# Download through proxy
137+
curl -v -x socks5://127.0.0.1:1080 -o downloaded_data.bin http://127.0.0.1:9000/test_data.bin
138+
139+
sha256sum downloaded_data.bin > downloaded_checksum.txt
140+
echo "Downloaded Checksum:"
141+
cat downloaded_checksum.txt
142+
143+
# Compare hashes
144+
ORIGINAL=$(awk '{print $1}' original_checksum.txt)
145+
DOWNLOADED=$(awk '{print $1}' downloaded_checksum.txt)
146+
147+
if [ "$ORIGINAL" != "$DOWNLOADED" ]; then
148+
echo "Error: Checksums do not match!"
149+
exit 1
150+
fi
151+
echo "Success: Data integrity verified."
152+
153+
- name: 📂 Upload Service Logs
154+
if: always()
155+
uses: actions/upload-artifact@v4
156+
with:
157+
name: e2e-portal-logs
158+
path: |
159+
tavern.log
160+
implants/imixv2/agent.log

.github/workflows/e2e-repl-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
- name: 🧪 Run E2E Tests
8383
working-directory: tests/e2e
8484
run: |
85-
npx playwright test
85+
npx playwright test tests/repl.spec.ts
8686
- name: 📂 Upload Service Logs
8787
if: always() # Runs even if tests fail
8888
uses: actions/upload-artifact@v4

bin/socks5/auth.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"strings"
9+
10+
"google.golang.org/grpc/metadata"
11+
"realm.pub/tavern/cli/auth"
12+
)
13+
14+
// EnvAPIToken is the name of the environment variable to optionally provide an API token
15+
const EnvAPIToken = "TAVERN_API_TOKEN"
16+
17+
func getAuthToken(ctx context.Context, tavernURL, cachePath string) (auth.Token, error) {
18+
if token := os.Getenv(EnvAPIToken); token != "" {
19+
return auth.Token(token), nil
20+
}
21+
22+
tokenData, err := os.ReadFile(cachePath)
23+
if os.IsNotExist(err) {
24+
token, err := auth.Authenticate(
25+
ctx,
26+
auth.BrowserFunc(
27+
func(url string) error {
28+
fmt.Printf("\n\nTavern Authentication URL: %s\n\n", url)
29+
return nil
30+
},
31+
),
32+
tavernURL,
33+
)
34+
if err != nil {
35+
return auth.Token(""), err
36+
}
37+
38+
if err := os.WriteFile(cachePath, []byte(token), 0640); err != nil {
39+
log.Printf("[WARN] Failed to save token to credential cache (%q): %v", cachePath, err)
40+
}
41+
return token, nil
42+
}
43+
if err != nil {
44+
return auth.Token(""), fmt.Errorf("failed to read credential cache (%q): %v", cachePath, err)
45+
}
46+
47+
log.Printf("Loaded authentication credentials from %q", cachePath)
48+
return auth.Token(tokenData), nil
49+
}
50+
51+
func authGRPCContext(ctx context.Context, upstream string, authCachePath string) context.Context {
52+
// Default to http if no scheme provided
53+
if !strings.HasPrefix(upstream, "http://") && !strings.HasPrefix(upstream, "https://") {
54+
upstream = fmt.Sprintf("http://%s", upstream)
55+
}
56+
57+
token, err := getAuthToken(ctx, upstream, authCachePath)
58+
if err != nil {
59+
log.Fatalf("authentication failed: %v", err)
60+
}
61+
62+
return metadata.AppendToOutgoingContext(ctx,
63+
auth.HeaderAPIAccessToken, string(token),
64+
)
65+
}

bin/socks5/connect.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package main
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
"net/url"
7+
"strings"
8+
9+
"google.golang.org/grpc"
10+
"google.golang.org/grpc/credentials"
11+
"google.golang.org/grpc/credentials/insecure"
12+
)
13+
14+
func connect(upstream string) (*grpc.ClientConn, error) {
15+
// Default to http if no scheme set
16+
if !strings.HasPrefix(upstream, "https://") && !strings.HasPrefix(upstream, "http://") {
17+
upstream = fmt.Sprintf("http://%s", upstream)
18+
}
19+
20+
// Parse host:port to determine if TLS should be used
21+
url, err := url.Parse(upstream)
22+
if err != nil {
23+
return nil, fmt.Errorf("failed to parse upstream address: %v", err)
24+
}
25+
26+
// Default to TLS on 443
27+
tc := credentials.NewTLS(&tls.Config{})
28+
29+
// If scheme is http or unset, use insecure credentials and default to port 80
30+
if url.Scheme == "http" || url.Scheme == "" {
31+
tc = insecure.NewCredentials()
32+
}
33+
34+
conn, err := grpc.NewClient(url.Host, grpc.WithTransportCredentials(tc), grpc.WithWriteBufferSize(maxBuffSize), grpc.WithReadBufferSize(maxBuffSize))
35+
if err != nil {
36+
return nil, fmt.Errorf("failed to connect to upstream: %w", err)
37+
}
38+
39+
return conn, nil
40+
}

bin/socks5/main.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"log"
6+
"os"
7+
8+
"realm.pub/tavern/portals/portalpb"
9+
)
10+
11+
const (
12+
authCachePath = ".tavern-auth"
13+
)
14+
15+
func main() {
16+
if len(os.Args) > 1 && os.Args[1] == "trace" {
17+
traceCommand(os.Args[2:])
18+
return
19+
}
20+
21+
proxyCommand()
22+
}
23+
24+
func proxyCommand() {
25+
// Re-parse flags for proxy command since flag.Parse() uses os.Args by default
26+
// and we might have skipped "trace".
27+
// But since we checked os.Args[1] manually, standard flag.Parse() will fail if we don't adjust os.Args
28+
// or create a new FlagSet.
29+
// Since the original code used the default flag set, let's stick to that but we need to ensure
30+
// trace command doesn't interfere.
31+
32+
// Actually, best to just use a custom FlagSet for proxy too, or reset os.Args?
33+
// The original code used global flags.
34+
35+
// Let's manually define the proxy flags again using a FlagSet to avoid conflict with `trace` flags if we were to define them globally.
36+
fs := flag.NewFlagSet("proxy", flag.ExitOnError)
37+
portalID := fs.Int64("portal", 0, "Portal ID")
38+
listenAddr := fs.String("listen", "127.0.0.1:1080", "SOCKS5 Listen Address")
39+
upstreamAddr := fs.String("upstream", "127.0.0.1:8000", "Upstream gRPC Address")
40+
41+
// Parse remaining args
42+
// If main was called without subcommands, os.Args is [prog, flags...]
43+
// If called with `trace`, we handled it.
44+
// So just parse os.Args[1:]
45+
fs.Parse(os.Args[1:])
46+
47+
if *portalID == 0 {
48+
log.Fatal("portal is required")
49+
}
50+
51+
p := &Proxy{
52+
portalID: *portalID,
53+
listenAddr: *listenAddr,
54+
upstreamAddr: *upstreamAddr,
55+
streams: make(map[string]chan *portalpb.Mote),
56+
}
57+
58+
if err := p.Run(); err != nil {
59+
log.Fatalf("Proxy failed: %v", err)
60+
}
61+
}

0 commit comments

Comments
 (0)