79 Commits

Author SHA1 Message Date
8229879db8 Merge pull request 'chore(deps): update golang docker tag to v1.25.7' (#82) from renovate/golang-1.x into main
SonarQube Scan / SonarQube Trigger (push) Successful in 2m13s
2026-02-05 02:03:49 +07:00
7015e7f4de chore(deps): update golang docker tag to v1.25.7
Tests / Run Tests (pull_request) Successful in 1m11s
2026-02-04 19:03:47 +00:00
03c6b44fa2 Merge pull request 'fix(deps): update module github.com/charmbracelet/bubbles to v0.21.1' (#81) from renovate/github.com-charmbracelet-bubbles-0.x into main
SonarQube Scan / SonarQube Trigger (push) Successful in 3m15s
2026-02-03 15:05:34 +07:00
3af3fdbc9c fix(deps): update module github.com/charmbracelet/bubbles to v0.21.1
Tests / Run Tests (pull_request) Successful in 1m18s
2026-02-03 08:05:29 +00:00
6dc4bb58ea Merge pull request 'chore(deps): update actions/checkout action to v6' (#80) from renovate/actions-checkout-6.x into main
SonarQube Scan / SonarQube Trigger (push) Successful in 4m25s
Reviewed-on: #80
2026-01-28 01:16:08 +07:00
bd2b843e5d chore(deps): update actions/checkout action to v6
Tests / Run Tests (pull_request) Successful in 1m9s
2026-01-27 18:11:54 +00:00
5b05723e93 ci: refactor workflows for SonarQube, tag-only Docker builds, and global testing
SonarQube Scan / SonarQube Trigger (push) Successful in 4m41s
Docker Build and Push / Run Tests (push) Successful in 1m59s
Docker Build and Push / Build and Push Docker Image (push) Successful in 8m22s
- Run SonarQube scans only on main, staging, and feat/* branches
- Build and push Docker images only on semantic version tags
- Add test job that runs on all events
2026-01-28 01:06:29 +07:00
22ad935299 Merge pull request 'chore(deps): update actions/checkout action to v6' (#75) from renovate/actions-checkout-6.x into main
SonarQube Scan / SonarQube Trigger (push) Successful in 6m25s
Reviewed-on: #75
2026-01-27 18:36:31 +07:00
ebd915e18e chore(deps): update actions/checkout action to v6
SonarQube Scan / SonarQube Trigger (pull_request) Has been cancelled
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
2026-01-27 11:35:15 +00:00
728691d119 Update .gitea/workflows/sonarqube.yml
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
2026-01-27 18:31:10 +07:00
1344afd1b2 Merge pull request 'fix(deps): update module github.com/stretchr/testify to v1.11.1' (#79) from renovate/github.com-stretchr-testify-1.x into main
Docker Build and Push / build-and-push-tags (push) Has been skipped
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
Docker Build and Push / build-and-push-branches (push) Has been cancelled
2026-01-27 18:19:53 +07:00
4cbee5079c fix(deps): update module github.com/stretchr/testify to v1.11.1
SonarQube Scan / SonarQube Trigger (pull_request) Has been cancelled
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
2026-01-27 11:19:47 +00:00
0b071dfde7 Merge pull request 'chore(deps): update dependency go to v1.25.6' (#78) from renovate/go-1.x into main
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
2026-01-27 18:19:40 +07:00
6062c2e11d chore(deps): update dependency go to v1.25.6
SonarQube Scan / SonarQube Trigger (pull_request) Has been cancelled
SonarQube Scan / SonarQube Trigger (push) Has been cancelled
2026-01-27 11:19:34 +00:00
2a2d484e91 Merge pull request 'staging' (#77) from staging into main
SonarQube Scan / SonarQube Trigger (push) Successful in 6m4s
Docker Build and Push / build-and-push-tags (push) Has been skipped
Docker Build and Push / build-and-push-branches (push) Successful in 23m12s
Reviewed-on: #77
2026-01-27 18:08:36 +07:00
9377233515 feat(testing): comprehensive test coverage and quality improvements (#76)
SonarQube Scan / SonarQube Trigger (push) Successful in 3m32s
Docker Build and Push / build-and-push-branches (push) Successful in 48m34s
Docker Build and Push / build-and-push-tags (push) Has been skipped
SonarQube Scan / SonarQube Trigger (pull_request) Successful in 6m12s
- Added unit tests for all core components (interaction, forwarder, stream, lifecycle, session, config, transport, middleware, etc.)
- Migrated to Testify framework for testing
- Integrated SonarQube for code quality monitoring
- Reduced cognitive complexity across multiple modules
- Fixed buffer handling, serialization, and error handling issues
- Set up automated CI/CD pipeline with coverage reporting

Reviewed-on: #76
2026-01-27 16:36:40 +07:00
fab625e13a docs: show CI/CD status badge and mascot in README
SonarQube Scan / SonarQube Trigger (push) Successful in 3m32s
SonarQube Scan / SonarQube Trigger (pull_request) Successful in 3m26s
2026-01-27 16:28:20 +07:00
1ed845bf2d test(interaction): add unit tests for interaction behavior 2026-01-27 16:28:20 +07:00
67378aabda refactor(dockerfile): split long ldflags line 2026-01-27 16:28:20 +07:00
a26d1672d9 refactor(interaction): reduce cognitive complexity and centralize color constants 2026-01-27 16:28:20 +07:00
7f44cc7bc0 fix: ensure proper buffer reuse with pointer handling in sync.Pool 2026-01-27 16:28:20 +07:00
a3f6baa6ae test: check and handle error for testing 2026-01-27 16:28:20 +07:00
6def82a095 ci: add project source and test path for sonarqube 2026-01-27 16:28:20 +07:00
354da27424 test(forwarder): add unit tests for forwarder behavior 2026-01-27 16:28:20 +07:00
ee1dc3c3cd chore(tests): migrate to Testify for mocking and assertions 2026-01-27 16:28:20 +07:00
65df01fee5 refactor(forwarder): remove CreateForwardedTCPIPPayload method
- OpenForwardedChannel now privately calls CreateForwardedTCPIPPayload
- Removed an unused function
2026-01-27 16:28:20 +07:00
79fd292a77 feat(http): add http header size limit for initial request 2026-01-27 16:28:20 +07:00
4041681be6 refactor(header): NewRequest to accept only []byte 2026-01-27 16:28:20 +07:00
2ee24c8d51 test(config): add test for keyloc and header size 2026-01-27 16:28:20 +07:00
384bb98f48 test(stream): migrate mocking to testify 2026-01-27 16:28:20 +07:00
9785a97973 refactor: remove duplicate channel management helpers from HTTP handler 2026-01-27 16:28:20 +07:00
b8c6359820 refactor: remove custom parsing functions and use ssh.Marshal/ssh.Unmarshal for serialization 2026-01-27 16:28:20 +07:00
8fee8bf92e test(server): add unit test for handleConnection 2026-01-27 16:28:20 +07:00
04c9ddbc13 test(lifecycle): add unit tests for lifecycle behavior 2026-01-27 16:28:20 +07:00
211745dc26 test(slug): add unit tests for slug behavior 2026-01-27 16:28:20 +07:00
09aa92a0ae fix: properly initialize tlsStoragePath in config load 2026-01-27 16:28:20 +07:00
1ed9f3631f fix: correct buffer pool usage to avoid type assertion error 2026-01-27 16:28:20 +07:00
bd826d6d06 refactor(transport): reduce cognitive complexity and clean up public API 2026-01-27 16:28:20 +07:00
2f5c44ff01 test(bootstrap): add unit tests for initial bootstrap behavior 2026-01-27 16:28:20 +07:00
d0e052524c refactor: decouple application startup logic from main 2026-01-27 16:28:20 +07:00
24b9872aa4 fix: corrected defer usage to pass buffer pointer 2026-01-27 16:28:20 +07:00
8b84373036 fix: remove unnecessary use of fmt.Sprintf 2026-01-27 16:28:20 +07:00
e796ab5328 fix: handle error return values for privateKeyFile.Close and pubKeyFile.Close 2026-01-27 16:28:20 +07:00
efdfc4ce95 chore: remove unused headerBuf variable 2026-01-27 16:28:20 +07:00
1dc929cc25 ci: sonarqube add linting 2026-01-27 16:28:20 +07:00
14abac6579 test(session): add unit tests for session behavior 2026-01-27 16:28:20 +07:00
21179da4b5 refactor(session): reduce function parameters 2026-01-27 16:28:20 +07:00
32f8be2891 test(version): add unit tests for version behavior 2026-01-27 16:28:20 +07:00
5af7af3139 test(client): add unit tests for grpc client behavior 2026-01-27 16:28:20 +07:00
f4848e9754 fix(client): reduce cognitive complexity and fix typo (go:S3776) 2026-01-27 16:28:20 +07:00
d2e508c8ef test(key): add unit tests for key behavior 2026-01-27 16:28:20 +07:00
5499b7d08a ci: update SonarQube action configuration 2026-01-27 16:28:20 +07:00
58f1fdabe1 test(server): add unit tests for server startup behavior 2026-01-27 16:28:20 +07:00
c1fb588cf4 test(config): add unit tests for config behavior 2026-01-27 16:28:20 +07:00
3029996773 test(stream): add unit tests for stream behavior
- Fix duplicating EOF error when closing SSH connection
- Add new SessionStatusCLOSED type
2026-01-27 16:28:20 +07:00
3fd179d32b test(header): add unit tests for header behavior 2026-01-27 16:28:20 +07:00
a598a10e94 update: exclude local test coverage 2026-01-27 16:28:20 +07:00
29cabe42d3 test(transport): add unit tests for transport behavior using Testify 2026-01-27 16:28:20 +07:00
e534972abc test(random): add unit tests for random behavior
- Added unit tests to cover random string generation and error handling.
- Introduced Random interface and random struct for better abstraction.
- Updated server, session, and interaction packages to require Random interface for dependency injection.
2026-01-27 16:28:20 +07:00
a55ff5f6ab test(port): add unit tests for port behavior 2026-01-27 16:28:20 +07:00
50b4127cb3 test(middleware): add unit tests for middleware behavior
- remove redundant check on registry.Update and check if slug exist before locking the mutex
- Update SonarQube action to not use Go cache when setting up Go
2026-01-27 16:28:20 +07:00
7e635721fb ci: automate Go tests and Sonar coverage reporting 2026-01-27 16:28:20 +07:00
016df9caee test(registry): add unit tests for registry behavior 2026-01-27 16:28:20 +07:00
d91eecb2a0 chore: Refactor and optimize project architecture
Docker Build and Push / build-and-push-tags (push) Has been skipped
SonarQube Scan / SonarQube Trigger (push) Successful in 54s
Docker Build and Push / build-and-push-branches (push) Successful in 12m17s
- Fix: Resolve goroutine deadlock on early connection close
- Refactor: Simplify Start() method, unify forwarding logic, and enhance HTTP handler modularity
- Improve: Connection handling, header parsing, and resource management
- Refactor: Centralize environment loading, enforce typed access, and cleanup config structure
- Enhance: SonarQube scan integration for CI
- Chore: Reorganize project layout and simplify lifecycle management
- Define reusable constants for registry errors

Reviewed-on: #74
2026-01-22 22:16:33 +07:00
961a905542 chore(restructure): refactor architecture, config, and lifecycle management
Docker Build and Push / build-and-push-tags (push) Has been skipped
SonarQube Scan / SonarQube Trigger (push) Successful in 44s
Docker Build and Push / build-and-push-branches (push) Successful in 11m16s
SonarQube Scan / SonarQube Trigger (pull_request) Successful in 46s
- Reorganized internal packages and overall project structure
- Moved HTTP/HTTPS/TCP servers into the transport layer
- Decoupled server initialization from HTTP/HTTPS/TCP startup logic
- Separated HTTP parsing, streaming, middleware, and session registry concerns
- Refactored session and forwarder responsibilities for clearer ownership
- Centralized environment loading with validated, typed config access
- Made config immutable after initialization and normalized enum naming
- Improved resource lifecycle handling and error aggregation on shutdown
- Introduced reusable, package-level registry errors
- Added SonarQube scanning to CI pipeline

Reviewed-on: #73
2026-01-22 00:48:40 +07:00
634c8321ef refactor(registry): define reusable constant errors
SonarQube Scan / SonarQube Trigger (push) Successful in 52s
SonarQube Scan / SonarQube Trigger (pull_request) Successful in 46s
- Introduced package-level error variables in registry to replace repeated fmt.Errorf calls
- Added errors like ErrSessionNotFound, ErrSlugInUse, ErrInvalidSlug, ErrForbiddenSlug, ErrSlugChangeNotAllowed, and ErrSlugUnchanged
2026-01-22 00:39:28 +07:00
9f4c24a3f3 refactor(lifecycle): reorder resource closing and simplify Close()
SonarQube Scan / SonarQube Trigger (push) Successful in 53s
- Close channel and connection first, then remove session
- Close forwarded port and forwarder at the end for TCP tunnels
- Aggregate all errors using errors.Join instead of failing early
2026-01-21 21:59:59 +07:00
1408b80917 ci: add sonarqube scan
SonarQube Scan / SonarQube Trigger (push) Successful in 48s
2026-01-21 21:24:57 +07:00
2bc20dd991 refactor(config): centralize env loading and enforce typed access
- Centralize environment variable loading in config.MustLoad
- Parse and validate all env vars once at initialization
- Make config fields private and read-only
- Remove public Getenv usage in favor of typed accessors
- Improve validation and initialization order
- Normalize enum naming to be idiomatic and avoid constant collisions
2026-01-21 19:43:19 +07:00
1e12373359 chore(restructure): reorganize project layout
Docker Build and Push / build-and-push-branches (push) Has been skipped
Docker Build and Push / build-and-push-tags (push) Successful in 13m1s
- Reorganize internal packages and overall project structure
- Update imports and wiring to match the new layout
- Separate HTTP parsing and streaming from the server package
- Separate middleware from the server package
- Separate session registry from the session package
- Move HTTP, HTTPS, and TCP servers to the transport package
- Session package no longer starts the TCP server directly
- Server package no longer starts HTTP/HTTPS servers on initialization
- Forwarder no longer handles accepting TCP requests
- Move session details to the types package
- HTTP/HTTPS initialization is now the responsibility of main
2026-01-21 14:06:46 +07:00
9a4539cc02 refactor(httpheader): extract header parsing into dedicated package
Docker Build and Push / build-and-push-tags (push) Has been skipped
Docker Build and Push / build-and-push-branches (push) Successful in 11m19s
Moved HTTP header parsing and building logic from server package to internal/httpheader
2026-01-20 21:15:34 +07:00
e3ead4d52f refactor: optimize header parsing and remove factory naming
Docker Build and Push / build-and-push-tags (push) Has been skipped
Docker Build and Push / build-and-push-branches (push) Successful in 11m20s
- Remove factory naming
- Use direct byte indexing instead of bytes.TrimRight
- Extract parseStartLine and setRemainingHeaders helpers
2026-01-20 20:56:08 +07:00
aa1a465178 refactor(forwarder): improve connection handling and cleanup
Docker Build and Push / build-and-push-tags (push) Has been skipped
Docker Build and Push / build-and-push-branches (push) Has been cancelled
- Extract copyAndClose method for bidirectional data transfe
- Add closeWriter helper for graceful connection shutdown
- Add handleIncomingConnection helper
- Add openForwardedChannel helper
2026-01-20 19:01:15 +07:00
27f49879af refactor(server): enhance HTTP handler modularity and fix resource leak
Docker Build and Push / build-and-push-tags (push) Has been skipped
Docker Build and Push / build-and-push-branches (push) Successful in 11m43s
- Rename customWriter struct to httpWriter for clarity
- Add closeWriter field to properly close write side of connections
- Update all cw variable references to hw
- Merge handlerTLS into handler function to reduce code duplication
- Extract handler into smaller, focused methods
- Split Read/Write/forwardRequest into composable functions

Fixes resource leak where connections weren't properly closed on the
write side, matching the forwarder's CloseWrite() pattern.
2026-01-19 22:41:04 +07:00
adb0264bb5 refactor(session): simplify Start() and unify forwarding logic
- Extract helper functions from Start() for better code organization
- Eliminate duplication with finalizeForwarding() method
- Consolidate denial logic into denyForwardingRequest()
- Update all handler methods to return errors instead of logging internally
- Improve error handling consistency across all operations
2026-01-19 15:53:16 +07:00
8fb19af5a6 fix: resolve copy goroutine deadlock on early connection close
- Add proper CloseWrite handling to signal EOF to other goroutine
- Ensure both copy goroutines terminate when either side closes
- Prevent goroutine leaks for SSH forwarded-tcpip channels:
    - Use select with default when sending result to resultChan
    - Close unused SSH channels and discard requests if main goroutine has already timed out
2026-01-19 00:20:28 +07:00
41fdb5639c Merge pull request 'refactor: explicit initialization and dependency injection' (#70) from staging into main
Docker Build and Push / build-and-push-tags (push) Has been skipped
Docker Build and Push / build-and-push-branches (push) Successful in 9m49s
Reviewed-on: #70
2026-01-18 21:46:59 +07:00
44d224f491 refactor: explicit initialization and dependency injection
Docker Build and Push / build-and-push-branches (push) Has been skipped
Docker Build and Push / build-and-push-tags (push) Successful in 10m10s
- Replace init() with config.Load() function when loading env variables
- Inject portRegistry into session, server, and lifecycle structs
- Inject sessionRegistry directly into interaction and lifecycle
- Remove SetSessionRegistry function and global port variables
- Pass ssh.Conn directly to forwarder constructor instead of lifecycle interface
- Pass user and closeFunc callback to interaction constructor instead of lifecycle interface
- Eliminate circular dependencies between lifecycle, forwarder, and interaction
- Remove setter methods (SetLifecycle) from forwarder and interaction interfaces
2026-01-18 21:20:05 +07:00
cdfe81baed refactor: explicit initialization and dependency injection
- Replace init() with config.Load() function when loading env variables
- Inject portRegistry into session, server, and lifecycle structs
- Inject sessionRegistry directly into interaction and lifecycle
- Remove SetSessionRegistry function and global port variables
- Pass ssh.Conn directly to forwarder constructor instead of lifecycle interface
- Pass user and closeFunc callback to interaction constructor instead of lifecycle interface
- Eliminate circular dependencies between lifecycle, forwarder, and interaction
- Remove setter methods (SetLifecycle) from forwarder and interaction interfaces
2026-01-18 21:09:50 +07:00
11 changed files with 105 additions and 131 deletions
+2 -16
View File
@@ -1,7 +1,3 @@
git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0 h1:RhcBKUG41/om4jgN+iF/vlY/RojTeX1QhBa4p4428ec=
git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0 h1:tpJSKjaSmV+vxxbVx6qnStjxFVXjj2M0rygWXxLb99o=
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0 h1:3xszIhck4wo9CoeRq9vnkar4PhY7kz9QrR30qj2XszA= git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0 h1:3xszIhck4wo9CoeRq9vnkar4PhY7kz9QrR30qj2XszA=
git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0/go.mod h1:Weh6ZujgWmT8XxD3Qba7sJ6r5eyUMB9XSWynqdyOoLo= git.fossy.my.id/bagas/tunnel-please-grpc v1.5.0/go.mod h1:Weh6ZujgWmT8XxD3Qba7sJ6r5eyUMB9XSWynqdyOoLo=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -10,12 +6,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY= github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY=
github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY= github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw= github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw=
github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
@@ -118,8 +110,6 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
@@ -132,14 +122,10 @@ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
+3 -5
View File
@@ -1,19 +1,17 @@
package config package config
import ( import (
"log"
"os" "os"
"strconv" "strconv"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
func init() { func Load() error {
if _, err := os.Stat(".env"); err == nil { if _, err := os.Stat(".env"); err == nil {
if err := godotenv.Load(".env"); err != nil { return godotenv.Load(".env")
log.Printf("Warning: Failed to load .env file: %s", err)
}
} }
return nil
} }
func Getenv(key, defaultValue string) string { func Getenv(key, defaultValue string) string {
+9 -32
View File
@@ -3,53 +3,30 @@ package port
import ( import (
"fmt" "fmt"
"sort" "sort"
"strconv"
"strings"
"sync" "sync"
"tunnel_pls/internal/config"
) )
type Manager interface { type Registry interface {
AddPortRange(startPort, endPort uint16) error AddPortRange(startPort, endPort uint16) error
GetUnassignedPort() (uint16, bool) GetUnassignedPort() (uint16, bool)
SetPortStatus(port uint16, assigned bool) error SetPortStatus(port uint16, assigned bool) error
ClaimPort(port uint16) (claimed bool) ClaimPort(port uint16) (claimed bool)
} }
type manager struct { type registry struct {
mu sync.RWMutex mu sync.RWMutex
ports map[uint16]bool ports map[uint16]bool
sortedPorts []uint16 sortedPorts []uint16
} }
var Default Manager = &manager{ func New() Registry {
return &registry{
ports: make(map[uint16]bool), ports: make(map[uint16]bool),
sortedPorts: []uint16{}, sortedPorts: []uint16{},
}
} }
func init() { func (pm *registry) AddPortRange(startPort, endPort uint16) error {
rawRange := config.Getenv("ALLOWED_PORTS", "")
if rawRange == "" {
return
}
splitRange := strings.Split(rawRange, "-")
if len(splitRange) != 2 {
return
}
start, err := strconv.ParseUint(splitRange[0], 10, 16)
if err != nil {
return
}
end, err := strconv.ParseUint(splitRange[1], 10, 16)
if err != nil {
return
}
_ = Default.AddPortRange(uint16(start), uint16(end))
}
func (pm *manager) AddPortRange(startPort, endPort uint16) error {
pm.mu.Lock() pm.mu.Lock()
defer pm.mu.Unlock() defer pm.mu.Unlock()
@@ -68,7 +45,7 @@ func (pm *manager) AddPortRange(startPort, endPort uint16) error {
return nil return nil
} }
func (pm *manager) GetUnassignedPort() (uint16, bool) { func (pm *registry) GetUnassignedPort() (uint16, bool) {
pm.mu.Lock() pm.mu.Lock()
defer pm.mu.Unlock() defer pm.mu.Unlock()
@@ -80,7 +57,7 @@ func (pm *manager) GetUnassignedPort() (uint16, bool) {
return 0, false return 0, false
} }
func (pm *manager) SetPortStatus(port uint16, assigned bool) error { func (pm *registry) SetPortStatus(port uint16, assigned bool) error {
pm.mu.Lock() pm.mu.Lock()
defer pm.mu.Unlock() defer pm.mu.Unlock()
@@ -88,7 +65,7 @@ func (pm *manager) SetPortStatus(port uint16, assigned bool) error {
return nil return nil
} }
func (pm *manager) ClaimPort(port uint16) (claimed bool) { func (pm *registry) ClaimPort(port uint16) (claimed bool) {
pm.mu.Lock() pm.mu.Lock()
defer pm.mu.Unlock() defer pm.mu.Unlock()
+35 -3
View File
@@ -8,12 +8,14 @@ import (
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/signal" "os/signal"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"tunnel_pls/internal/config" "tunnel_pls/internal/config"
"tunnel_pls/internal/grpc/client" "tunnel_pls/internal/grpc/client"
"tunnel_pls/internal/key" "tunnel_pls/internal/key"
"tunnel_pls/internal/port"
"tunnel_pls/server" "tunnel_pls/server"
"tunnel_pls/session" "tunnel_pls/session"
"tunnel_pls/version" "tunnel_pls/version"
@@ -32,6 +34,12 @@ func main() {
log.Printf("Starting %s", version.GetVersion()) log.Printf("Starting %s", version.GetVersion())
err := config.Load()
if err != nil {
log.Fatalf("Failed to load configuration: %s", err)
return
}
mode := strings.ToLower(config.Getenv("MODE", "standalone")) mode := strings.ToLower(config.Getenv("MODE", "standalone"))
isNodeMode := mode == "node" isNodeMode := mode == "node"
@@ -41,7 +49,7 @@ func main() {
go func() { go func() {
pprofAddr := fmt.Sprintf("localhost:%s", pprofPort) pprofAddr := fmt.Sprintf("localhost:%s", pprofPort)
log.Printf("Starting pprof server on http://%s/debug/pprof/", pprofAddr) log.Printf("Starting pprof server on http://%s/debug/pprof/", pprofAddr)
if err := http.ListenAndServe(pprofAddr, nil); err != nil { if err = http.ListenAndServe(pprofAddr, nil); err != nil {
log.Printf("pprof server error: %v", err) log.Printf("pprof server error: %v", err)
} }
}() }()
@@ -53,7 +61,7 @@ func main() {
} }
sshKeyPath := "certs/ssh/id_rsa" sshKeyPath := "certs/ssh/id_rsa"
if err := key.GenerateSSHKeyIfNotExist(sshKeyPath); err != nil { if err = key.GenerateSSHKeyIfNotExist(sshKeyPath); err != nil {
log.Fatalf("Failed to generate SSH key: %s", err) log.Fatalf("Failed to generate SSH key: %s", err)
} }
@@ -107,9 +115,33 @@ func main() {
}() }()
} }
portManager := port.New()
rawRange := config.Getenv("ALLOWED_PORTS", "")
if rawRange != "" {
splitRange := strings.Split(rawRange, "-")
if len(splitRange) == 2 {
var start, end uint64
start, err = strconv.ParseUint(splitRange[0], 10, 16)
if err != nil {
log.Fatalf("Failed to parse start port: %s", err)
}
end, err = strconv.ParseUint(splitRange[1], 10, 16)
if err != nil {
log.Fatalf("Failed to parse end port: %s", err)
}
if err = portManager.AddPortRange(uint16(start), uint16(end)); err != nil {
log.Fatalf("Failed to add port range: %s", err)
}
log.Printf("PortRegistry range configured: %d-%d", start, end)
} else {
log.Printf("Invalid ALLOWED_PORTS format, expected 'start-end', got: %s", rawRange)
}
}
var app server.Server var app server.Server
go func() { go func() {
app, err = server.New(sshConfig, sessionRegistry, grpcClient) app, err = server.New(sshConfig, sessionRegistry, grpcClient, portManager)
if err != nil { if err != nil {
errChan <- fmt.Errorf("failed to start server: %s", err) errChan <- fmt.Errorf("failed to start server: %s", err)
return return
+7 -4
View File
@@ -9,6 +9,7 @@ import (
"time" "time"
"tunnel_pls/internal/config" "tunnel_pls/internal/config"
"tunnel_pls/internal/grpc/client" "tunnel_pls/internal/grpc/client"
"tunnel_pls/internal/port"
"tunnel_pls/session" "tunnel_pls/session"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@@ -21,11 +22,12 @@ type Server interface {
type server struct { type server struct {
listener net.Listener listener net.Listener
config *ssh.ServerConfig config *ssh.ServerConfig
sessionRegistry session.Registry
grpcClient client.Client grpcClient client.Client
sessionRegistry session.Registry
portRegistry port.Registry
} }
func New(sshConfig *ssh.ServerConfig, sessionRegistry session.Registry, grpcClient client.Client) (Server, error) { func New(sshConfig *ssh.ServerConfig, sessionRegistry session.Registry, grpcClient client.Client, portRegistry port.Registry) (Server, error) {
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", config.Getenv("PORT", "2200"))) listener, err := net.Listen("tcp", fmt.Sprintf(":%s", config.Getenv("PORT", "2200")))
if err != nil { if err != nil {
log.Fatalf("failed to listen on port 2200: %v", err) log.Fatalf("failed to listen on port 2200: %v", err)
@@ -50,8 +52,9 @@ func New(sshConfig *ssh.ServerConfig, sessionRegistry session.Registry, grpcClie
return &server{ return &server{
listener: listener, listener: listener,
config: sshConfig, config: sshConfig,
sessionRegistry: sessionRegistry,
grpcClient: grpcClient, grpcClient: grpcClient,
sessionRegistry: sessionRegistry,
portRegistry: portRegistry,
}, nil }, nil
} }
@@ -103,7 +106,7 @@ func (s *server) handleConnection(conn net.Conn) {
cancel() cancel()
} }
log.Println("SSH connection established:", sshConn.User()) log.Println("SSH connection established:", sshConn.User())
sshSession := session.New(sshConn, forwardingReqs, chans, s.sessionRegistry, user) sshSession := session.New(sshConn, forwardingReqs, chans, s.sessionRegistry, s.portRegistry, user)
err = sshSession.Start() err = sshSession.Start()
if err != nil { if err != nil {
log.Printf("SSH session ended with error: %v", err) log.Printf("SSH session ended with error: %v", err)
+6 -15
View File
@@ -35,26 +35,21 @@ type forwarder struct {
tunnelType types.TunnelType tunnelType types.TunnelType
forwardedPort uint16 forwardedPort uint16
slug slug.Slug slug slug.Slug
lifecycle Lifecycle conn ssh.Conn
} }
func New(slug slug.Slug) Forwarder { func New(slug slug.Slug, conn ssh.Conn) Forwarder {
return &forwarder{ return &forwarder{
listener: nil, listener: nil,
tunnelType: types.UNKNOWN, tunnelType: types.UNKNOWN,
forwardedPort: 0, forwardedPort: 0,
slug: slug, slug: slug,
lifecycle: nil, conn: conn,
} }
} }
type Lifecycle interface {
Connection() ssh.Conn
}
type Forwarder interface { type Forwarder interface {
SetType(tunnelType types.TunnelType) SetType(tunnelType types.TunnelType)
SetLifecycle(lifecycle Lifecycle)
SetForwardedPort(port uint16) SetForwardedPort(port uint16)
SetListener(listener net.Listener) SetListener(listener net.Listener)
Listener() net.Listener Listener() net.Listener
@@ -67,10 +62,6 @@ type Forwarder interface {
Close() error Close() error
} }
func (f *forwarder) SetLifecycle(lifecycle Lifecycle) {
f.lifecycle = lifecycle
}
func (f *forwarder) AcceptTCPConnections() { func (f *forwarder) AcceptTCPConnections() {
for { for {
conn, err := f.Listener().Accept() conn, err := f.Listener().Accept()
@@ -82,7 +73,7 @@ func (f *forwarder) AcceptTCPConnections() {
continue continue
} }
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { if err = conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
log.Printf("Failed to set connection deadline: %v", err) log.Printf("Failed to set connection deadline: %v", err)
if closeErr := conn.Close(); closeErr != nil { if closeErr := conn.Close(); closeErr != nil {
log.Printf("Failed to close connection: %v", closeErr) log.Printf("Failed to close connection: %v", closeErr)
@@ -100,7 +91,7 @@ func (f *forwarder) AcceptTCPConnections() {
resultChan := make(chan channelResult, 1) resultChan := make(chan channelResult, 1)
go func() { go func() {
channel, reqs, err := f.lifecycle.Connection().OpenChannel("forwarded-tcpip", payload) channel, reqs, err := f.conn.OpenChannel("forwarded-tcpip", payload)
resultChan <- channelResult{channel, reqs, err} resultChan <- channelResult{channel, reqs, err}
}() }()
@@ -114,7 +105,7 @@ func (f *forwarder) AcceptTCPConnections() {
continue continue
} }
if err := conn.SetDeadline(time.Time{}); err != nil { if err = conn.SetDeadline(time.Time{}); err != nil {
log.Printf("Failed to clear connection deadline: %v", err) log.Printf("Failed to clear connection deadline: %v", err)
} }
+1 -1
View File
@@ -109,7 +109,7 @@ func (m *model) dashboardView() string {
MarginBottom(boxMargin). MarginBottom(boxMargin).
Width(boxMaxWidth) Width(boxMaxWidth)
authenticatedUser := m.interaction.lifecycle.User() authenticatedUser := m.interaction.user
userInfoStyle := lipgloss.NewStyle(). userInfoStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#FAFAFA")). Foreground(lipgloss.Color("#FAFAFA")).
+14 -24
View File
@@ -17,20 +17,9 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
type Lifecycle interface {
Close() error
User() string
}
type SessionRegistry interface {
Update(user string, oldKey, newKey types.SessionKey) error
}
type Interaction interface { type Interaction interface {
Mode() types.Mode Mode() types.Mode
SetChannel(channel ssh.Channel) SetChannel(channel ssh.Channel)
SetLifecycle(lifecycle Lifecycle)
SetSessionRegistry(registry SessionRegistry)
SetMode(m types.Mode) SetMode(m types.Mode)
SetWH(w, h int) SetWH(w, h int)
Start() Start()
@@ -38,17 +27,23 @@ type Interaction interface {
Send(message string) error Send(message string) error
} }
type SessionRegistry interface {
Update(user string, oldKey, newKey types.SessionKey) error
}
type Forwarder interface { type Forwarder interface {
Close() error Close() error
TunnelType() types.TunnelType TunnelType() types.TunnelType
ForwardedPort() uint16 ForwardedPort() uint16
} }
type CloseFunc func() error
type interaction struct { type interaction struct {
channel ssh.Channel channel ssh.Channel
slug slug.Slug slug slug.Slug
forwarder Forwarder forwarder Forwarder
lifecycle Lifecycle closeFunc CloseFunc
user string
sessionRegistry SessionRegistry sessionRegistry SessionRegistry
program *tea.Program program *tea.Program
ctx context.Context ctx context.Context
@@ -80,28 +75,21 @@ func (i *interaction) SetWH(w, h int) {
} }
} }
func New(slug slug.Slug, forwarder Forwarder) Interaction { func New(slug slug.Slug, forwarder Forwarder, sessionRegistry SessionRegistry, user string, closeFunc CloseFunc) Interaction {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &interaction{ return &interaction{
channel: nil, channel: nil,
slug: slug, slug: slug,
forwarder: forwarder, forwarder: forwarder,
lifecycle: nil, closeFunc: closeFunc,
sessionRegistry: nil, user: user,
sessionRegistry: sessionRegistry,
program: nil, program: nil,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
} }
} }
func (i *interaction) SetSessionRegistry(registry SessionRegistry) {
i.sessionRegistry = registry
}
func (i *interaction) SetLifecycle(lifecycle Lifecycle) {
i.lifecycle = lifecycle
}
func (i *interaction) SetChannel(channel ssh.Channel) { func (i *interaction) SetChannel(channel ssh.Channel) {
i.channel = channel i.channel = channel
} }
@@ -262,7 +250,9 @@ func (i *interaction) Start() {
} }
i.program.Kill() i.program.Kill()
i.program = nil i.program = nil
if err := m.interaction.lifecycle.Close(); err != nil { if i.closeFunc != nil {
if err := i.closeFunc(); err != nil {
log.Printf("Cannot close session: %s \n", err) log.Printf("Cannot close session: %s \n", err)
} }
}
} }
+1 -1
View File
@@ -28,7 +28,7 @@ func (m *model) slugUpdate(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, tea.Batch(tea.ClearScreen, textinput.Blink) return m, tea.Batch(tea.ClearScreen, textinput.Blink)
case "enter": case "enter":
inputValue := m.slugInput.Value() inputValue := m.slugInput.Value()
if err := m.interaction.sessionRegistry.Update(m.interaction.lifecycle.User(), types.SessionKey{ if err := m.interaction.sessionRegistry.Update(m.interaction.user, types.SessionKey{
Id: m.interaction.slug.String(), Id: m.interaction.slug.String(),
Type: types.HTTP, Type: types.HTTP,
}, types.SessionKey{ }, types.SessionKey{
+11 -9
View File
@@ -28,41 +28,43 @@ type lifecycle struct {
conn ssh.Conn conn ssh.Conn
channel ssh.Channel channel ssh.Channel
forwarder Forwarder forwarder Forwarder
sessionRegistry SessionRegistry
slug slug.Slug slug slug.Slug
startedAt time.Time startedAt time.Time
sessionRegistry SessionRegistry
portRegistry portUtil.Registry
user string user string
} }
func New(conn ssh.Conn, forwarder Forwarder, slugManager slug.Slug, user string) Lifecycle { func New(conn ssh.Conn, forwarder Forwarder, slugManager slug.Slug, port portUtil.Registry, sessionRegistry SessionRegistry, user string) Lifecycle {
return &lifecycle{ return &lifecycle{
status: types.INITIALIZING, status: types.INITIALIZING,
conn: conn, conn: conn,
channel: nil, channel: nil,
forwarder: forwarder, forwarder: forwarder,
slug: slugManager, slug: slugManager,
sessionRegistry: nil,
startedAt: time.Now(), startedAt: time.Now(),
sessionRegistry: sessionRegistry,
portRegistry: port,
user: user, user: user,
} }
} }
func (l *lifecycle) SetSessionRegistry(registry SessionRegistry) {
l.sessionRegistry = registry
}
type Lifecycle interface { type Lifecycle interface {
Connection() ssh.Conn Connection() ssh.Conn
Channel() ssh.Channel Channel() ssh.Channel
PortRegistry() portUtil.Registry
User() string User() string
SetChannel(channel ssh.Channel) SetChannel(channel ssh.Channel)
SetSessionRegistry(registry SessionRegistry)
SetStatus(status types.Status) SetStatus(status types.Status)
IsActive() bool IsActive() bool
StartedAt() time.Time StartedAt() time.Time
Close() error Close() error
} }
func (l *lifecycle) PortRegistry() portUtil.Registry {
return l.portRegistry
}
func (l *lifecycle) User() string { func (l *lifecycle) User() string {
return l.user return l.user
} }
@@ -116,7 +118,7 @@ func (l *lifecycle) Close() error {
l.sessionRegistry.Remove(key) l.sessionRegistry.Remove(key)
if tunnelType == types.TCP { if tunnelType == types.TCP {
if err := portUtil.Default.SetPortStatus(l.forwarder.ForwardedPort(), false); err != nil && firstErr == nil { if err := l.PortRegistry().SetPortStatus(l.forwarder.ForwardedPort(), false); err != nil && firstErr == nil {
firstErr = err firstErr = err
} }
} }
+13 -18
View File
@@ -54,16 +54,11 @@ type session struct {
var blockedReservedPorts = []uint16{1080, 1433, 1521, 1900, 2049, 3306, 3389, 5432, 5900, 6379, 8080, 8443, 9000, 9200, 27017} var blockedReservedPorts = []uint16{1080, 1433, 1521, 1900, 2049, 3306, 3389, 5432, 5900, 6379, 8080, 8443, 9000, 9200, 27017}
func New(conn *ssh.ServerConn, initialReq <-chan *ssh.Request, sshChan <-chan ssh.NewChannel, sessionRegistry Registry, user string) Session { func New(conn *ssh.ServerConn, initialReq <-chan *ssh.Request, sshChan <-chan ssh.NewChannel, sessionRegistry Registry, portRegistry portUtil.Registry, user string) Session {
slugManager := slug.New() slugManager := slug.New()
forwarderManager := forwarder.New(slugManager) forwarderManager := forwarder.New(slugManager, conn)
interactionManager := interaction.New(slugManager, forwarderManager) lifecycleManager := lifecycle.New(conn, forwarderManager, slugManager, portRegistry, sessionRegistry, user)
lifecycleManager := lifecycle.New(conn, forwarderManager, slugManager, user) interactionManager := interaction.New(slugManager, forwarderManager, sessionRegistry, user, lifecycleManager.Close)
interactionManager.SetLifecycle(lifecycleManager)
forwarderManager.SetLifecycle(lifecycleManager)
interactionManager.SetSessionRegistry(sessionRegistry)
lifecycleManager.SetSessionRegistry(sessionRegistry)
return &session{ return &session{
initialReq: initialReq, initialReq: initialReq,
@@ -135,7 +130,7 @@ func (s *session) Start() error {
tcpipReq := s.waitForTCPIPForward() tcpipReq := s.waitForTCPIPForward()
if tcpipReq == nil { if tcpipReq == nil {
err := s.interaction.Send(fmt.Sprintf("Port forwarding request not received. Ensure you ran the correct command with -R flag. Example: ssh %s -p %s -R 80:localhost:3000", config.Getenv("DOMAIN", "localhost"), config.Getenv("PORT", "2200"))) err := s.interaction.Send(fmt.Sprintf("PortRegistry forwarding request not received. Ensure you ran the correct command with -R flag. Example: ssh %s -p %s -R 80:localhost:3000", config.Getenv("DOMAIN", "localhost"), config.Getenv("PORT", "2200")))
if err != nil { if err != nil {
return err return err
} }
@@ -234,7 +229,7 @@ func (s *session) HandleGlobalRequest(GlobalRequest <-chan *ssh.Request) {
} }
func (s *session) HandleTCPIPForward(req *ssh.Request) { func (s *session) HandleTCPIPForward(req *ssh.Request) {
log.Println("Port forwarding request detected") log.Println("PortRegistry forwarding request detected")
fail := func(msg string) { fail := func(msg string) {
log.Println(msg) log.Println(msg)
@@ -262,13 +257,13 @@ func (s *session) HandleTCPIPForward(req *ssh.Request) {
} }
if rawPortToBind > 65535 { if rawPortToBind > 65535 {
fail(fmt.Sprintf("Port %d is larger than allowed port of 65535", rawPortToBind)) fail(fmt.Sprintf("PortRegistry %d is larger than allowed port of 65535", rawPortToBind))
return return
} }
portToBind := uint16(rawPortToBind) portToBind := uint16(rawPortToBind)
if isBlockedPort(portToBind) { if isBlockedPort(portToBind) {
fail(fmt.Sprintf("Port %d is blocked or restricted", portToBind)) fail(fmt.Sprintf("PortRegistry %d is blocked or restricted", portToBind))
return return
} }
@@ -340,7 +335,7 @@ func (s *session) HandleTCPForward(req *ssh.Request, addr string, portToBind uin
s.registry.Remove(*key) s.registry.Remove(*key)
} }
if port != 0 { if port != 0 {
if setErr := portUtil.Default.SetPortStatus(port, false); setErr != nil { if setErr := s.lifecycle.PortRegistry().SetPortStatus(port, false); setErr != nil {
log.Printf("Failed to reset port status: %v", setErr) log.Printf("Failed to reset port status: %v", setErr)
} }
} }
@@ -356,7 +351,7 @@ func (s *session) HandleTCPForward(req *ssh.Request, addr string, portToBind uin
} }
if portToBind == 0 { if portToBind == 0 {
unassigned, ok := portUtil.Default.GetUnassignedPort() unassigned, ok := s.lifecycle.PortRegistry().GetUnassignedPort()
if !ok { if !ok {
fail("No available port") fail("No available port")
return return
@@ -364,15 +359,15 @@ func (s *session) HandleTCPForward(req *ssh.Request, addr string, portToBind uin
portToBind = unassigned portToBind = unassigned
} }
if claimed := portUtil.Default.ClaimPort(portToBind); !claimed { if claimed := s.lifecycle.PortRegistry().ClaimPort(portToBind); !claimed {
fail(fmt.Sprintf("Port %d is already in use or restricted", portToBind)) fail(fmt.Sprintf("PortRegistry %d is already in use or restricted", portToBind))
return return
} }
log.Printf("Requested forwarding on %s:%d", addr, portToBind) log.Printf("Requested forwarding on %s:%d", addr, portToBind)
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", portToBind)) listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", portToBind))
if err != nil { if err != nil {
cleanup(fmt.Sprintf("Port %d is already in use or restricted", portToBind), portToBind, nil, nil) cleanup(fmt.Sprintf("PortRegistry %d is already in use or restricted", portToBind), portToBind, nil, nil)
return return
} }