Merge pull request 'staging' (#46) from staging into main
Reviewed-on: #46
This commit was merged in pull request #46.
This commit is contained in:
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitea/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
tmp/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
certs/
|
||||||
|
id_rsa*
|
||||||
|
*.pub
|
||||||
|
|
||||||
|
README.md
|
||||||
|
LICENSE.md
|
||||||
|
*.md
|
||||||
|
|
||||||
|
renovate.json
|
||||||
|
renovate-config.js
|
||||||
|
|
||||||
|
*_test.go
|
||||||
|
testdata/
|
||||||
|
|
||||||
|
app
|
||||||
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ id_rsa*
|
|||||||
.env
|
.env
|
||||||
tmp
|
tmp
|
||||||
certs
|
certs
|
||||||
|
app
|
||||||
44
Dockerfile
44
Dockerfile
@@ -1,20 +1,48 @@
|
|||||||
FROM golang:1.25.5-alpine AS go_builder
|
FROM golang:1.25.5-alpine AS go_builder
|
||||||
|
|
||||||
|
RUN apk update && apk upgrade && \
|
||||||
|
apk add --no-cache ca-certificates tzdata git && \
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
go mod download && go mod verify
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN apk update && apk upgrade && apk add --no-cache ca-certificates tzdata
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
RUN update-ca-certificates
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
RUN go build -o ./tmp/main
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
||||||
|
go build -trimpath \
|
||||||
|
-ldflags="-w -s" \
|
||||||
|
-o /app/tunnel_pls \
|
||||||
|
.
|
||||||
|
|
||||||
|
RUN adduser -D -u 10001 -g '' appuser && \
|
||||||
|
mkdir -p /app/certs/ssh /app/certs/tls && \
|
||||||
|
chown -R appuser:appuser /app
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
COPY --from=go_builder /usr/share/zoneinfo /usr/share/zoneinfo
|
COPY --from=go_builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
COPY --from=go_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=go_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=go_builder /src/tmp/main /src
|
COPY --from=go_builder /etc/passwd /etc/passwd
|
||||||
|
COPY --from=go_builder /etc/group /etc/group
|
||||||
|
COPY --from=go_builder --chown=appuser:appuser /app /app
|
||||||
|
|
||||||
ENV TZ Asia/Jakarta
|
WORKDIR /app
|
||||||
|
|
||||||
ENTRYPOINT ["./main"]
|
USER appuser
|
||||||
|
|
||||||
|
ENV TZ=Asia/Jakarta
|
||||||
|
|
||||||
|
EXPOSE 2200 8080 8443
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="Tunnel Please" \
|
||||||
|
org.opencontainers.image.description="SSH-based tunnel server"
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/tunnel_pls"]
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -22,15 +22,13 @@ The following environment variables can be configured in the `.env` file:
|
|||||||
|----------|-------------|---------|----------|
|
|----------|-------------|---------|----------|
|
||||||
| `DOMAIN` | Domain name for subdomain routing | `localhost` | No |
|
| `DOMAIN` | Domain name for subdomain routing | `localhost` | No |
|
||||||
| `PORT` | SSH server port | `2200` | No |
|
| `PORT` | SSH server port | `2200` | No |
|
||||||
|
| `HTTP_PORT` | HTTP server port | `8080` | No |
|
||||||
|
| `HTTPS_PORT` | HTTPS server port | `8443` | No |
|
||||||
| `TLS_ENABLED` | Enable TLS/HTTPS | `false` | No |
|
| `TLS_ENABLED` | Enable TLS/HTTPS | `false` | No |
|
||||||
| `TLS_REDIRECT` | Redirect HTTP to HTTPS | `false` | No |
|
| `TLS_REDIRECT` | Redirect HTTP to HTTPS | `false` | No |
|
||||||
| `CERT_LOC` | Path to TLS certificate | `certs/cert.pem` | No |
|
|
||||||
| `KEY_LOC` | Path to TLS private key | `certs/privkey.pem` | No |
|
|
||||||
| `CERT_STORAGE_PATH` | Path for CertMagic certificate storage | `certs/certmagic` | No |
|
|
||||||
| `ACME_EMAIL` | Email for Let's Encrypt registration | `admin@<DOMAIN>` | No |
|
| `ACME_EMAIL` | Email for Let's Encrypt registration | `admin@<DOMAIN>` | No |
|
||||||
| `CF_API_TOKEN` | Cloudflare API token for DNS-01 challenge | - | Yes (if auto-cert) |
|
| `CF_API_TOKEN` | Cloudflare API token for DNS-01 challenge | - | Yes (if auto-cert) |
|
||||||
| `ACME_STAGING` | Use Let's Encrypt staging server | `false` | No |
|
| `ACME_STAGING` | Use Let's Encrypt staging server | `false` | No |
|
||||||
| `SSH_PRIVATE_KEY` | Path to SSH private key (auto-generated if missing) | `certs/id_rsa` | No |
|
|
||||||
| `CORS_LIST` | Comma-separated list of allowed CORS origins | - | No |
|
| `CORS_LIST` | Comma-separated list of allowed CORS origins | - | No |
|
||||||
| `ALLOWED_PORTS` | Port range for TCP tunnels (e.g., 40000-41000) | `40000-41000` | No |
|
| `ALLOWED_PORTS` | Port range for TCP tunnels (e.g., 40000-41000) | `40000-41000` | No |
|
||||||
| `BUFFER_SIZE` | Buffer size for io.Copy operations in bytes (4096-1048576) | `32768` | No |
|
| `BUFFER_SIZE` | Buffer size for io.Copy operations in bytes (4096-1048576) | `32768` | No |
|
||||||
@@ -43,8 +41,14 @@ The following environment variables can be configured in the `.env` file:
|
|||||||
|
|
||||||
The server supports automatic TLS certificate generation and renewal using [CertMagic](https://github.com/caddyserver/certmagic) with Cloudflare DNS-01 challenge. This is required for wildcard certificate support (`*.yourdomain.com`).
|
The server supports automatic TLS certificate generation and renewal using [CertMagic](https://github.com/caddyserver/certmagic) with Cloudflare DNS-01 challenge. This is required for wildcard certificate support (`*.yourdomain.com`).
|
||||||
|
|
||||||
|
**Certificate Storage:**
|
||||||
|
- TLS certificates are stored in `certs/tls/` (relative to application directory)
|
||||||
|
- User-provided certificates: `certs/tls/cert.pem` and `certs/tls/privkey.pem`
|
||||||
|
- CertMagic automatic certificates: `certs/tls/certmagic/`
|
||||||
|
- SSH keys are stored separately in `certs/ssh/`
|
||||||
|
|
||||||
**How it works:**
|
**How it works:**
|
||||||
1. If user-provided certificates (`CERT_LOC`, `KEY_LOC`) exist and cover both `DOMAIN` and `*.DOMAIN`, they will be used
|
1. If user-provided certificates exist at `certs/tls/cert.pem` and `certs/tls/privkey.pem` and cover both `DOMAIN` and `*.DOMAIN`, they will be used
|
||||||
2. If certificates are missing, expired, expiring within 30 days, or don't cover the required domains, CertMagic will automatically obtain new certificates from Let's Encrypt
|
2. If certificates are missing, expired, expiring within 30 days, or don't cover the required domains, CertMagic will automatically obtain new certificates from Let's Encrypt
|
||||||
3. Certificates are automatically renewed before expiration
|
3. Certificates are automatically renewed before expiration
|
||||||
4. User-provided certificates support hot-reload (changes detected every 30 seconds)
|
4. User-provided certificates support hot-reload (changes detected every 30 seconds)
|
||||||
@@ -71,7 +75,7 @@ ACME_EMAIL=admin@example.com
|
|||||||
|
|
||||||
### SSH Key Auto-Generation
|
### SSH Key Auto-Generation
|
||||||
|
|
||||||
If the SSH private key specified in `SSH_PRIVATE_KEY` doesn't exist, the application will automatically generate a new 4096-bit RSA key pair at the specified location. This makes it easier to get started without manually creating SSH keys.
|
The application will automatically generate a new 4096-bit RSA key pair at `certs/ssh/id_rsa` if it doesn't exist. This makes it easier to get started without manually creating SSH keys. SSH keys are stored separately from TLS certificates.
|
||||||
|
|
||||||
### Memory Optimization
|
### Memory Optimization
|
||||||
|
|
||||||
|
|||||||
37
go.sum
37
go.sum
@@ -2,6 +2,10 @@ github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx6
|
|||||||
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
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.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
@@ -14,44 +18,39 @@ github.com/mholt/acmez/v3 v3.1.3 h1:gUl789rjbJSuM5hYzOFnNaGgWPV1xVfnOs59o0dZEcc=
|
|||||||
github.com/mholt/acmez/v3 v3.1.3/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
github.com/mholt/acmez/v3 v3.1.3/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
|
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||||
|
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
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.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
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.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
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.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -9,36 +9,47 @@ import (
|
|||||||
"tunnel_pls/utils"
|
"tunnel_pls/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PortManager struct {
|
type Manager interface {
|
||||||
|
AddPortRange(startPort, endPort uint16) error
|
||||||
|
GetUnassignedPort() (uint16, bool)
|
||||||
|
SetPortStatus(port uint16, assigned bool) error
|
||||||
|
GetPortStatus(port uint16) (bool, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ports map[uint16]bool
|
ports map[uint16]bool
|
||||||
sortedPorts []uint16
|
sortedPorts []uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
var Manager = PortManager{
|
var Default Manager = &manager{
|
||||||
ports: make(map[uint16]bool),
|
ports: make(map[uint16]bool),
|
||||||
sortedPorts: []uint16{},
|
sortedPorts: []uint16{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rawRange := utils.Getenv("ALLOWED_PORTS", "40000-41000")
|
rawRange := utils.Getenv("ALLOWED_PORTS", "")
|
||||||
|
if rawRange == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
splitRange := strings.Split(rawRange, "-")
|
splitRange := strings.Split(rawRange, "-")
|
||||||
if len(splitRange) != 2 {
|
if len(splitRange) != 2 {
|
||||||
Manager.AddPortRange(30000, 31000)
|
return
|
||||||
} else {
|
}
|
||||||
|
|
||||||
start, err := strconv.ParseUint(splitRange[0], 10, 16)
|
start, err := strconv.ParseUint(splitRange[0], 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
start = 30000
|
return
|
||||||
}
|
}
|
||||||
end, err := strconv.ParseUint(splitRange[1], 10, 16)
|
end, err := strconv.ParseUint(splitRange[1], 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
end = 31000
|
return
|
||||||
}
|
|
||||||
Manager.AddPortRange(uint16(start), uint16(end))
|
|
||||||
}
|
}
|
||||||
|
_ = Default.AddPortRange(uint16(start), uint16(end))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *PortManager) AddPortRange(startPort, endPort uint16) error {
|
func (pm *manager) AddPortRange(startPort, endPort uint16) error {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
@@ -57,7 +68,7 @@ func (pm *PortManager) AddPortRange(startPort, endPort uint16) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *PortManager) GetUnassignedPort() (uint16, bool) {
|
func (pm *manager) GetUnassignedPort() (uint16, bool) {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
@@ -70,7 +81,7 @@ func (pm *PortManager) GetUnassignedPort() (uint16, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *PortManager) SetPortStatus(port uint16, assigned bool) error {
|
func (pm *manager) SetPortStatus(port uint16, assigned bool) error {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
@@ -78,7 +89,7 @@ func (pm *PortManager) SetPortStatus(port uint16, assigned bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *PortManager) GetPortStatus(port uint16) (bool, bool) {
|
func (pm *manager) GetPortStatus(port uint16) (bool, bool) {
|
||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
defer pm.mu.RUnlock()
|
defer pm.mu.RUnlock()
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -33,7 +33,7 @@ func main() {
|
|||||||
ServerVersion: "SSH-2.0-TunnlPls-1.0",
|
ServerVersion: "SSH-2.0-TunnlPls-1.0",
|
||||||
}
|
}
|
||||||
|
|
||||||
sshKeyPath := utils.Getenv("SSH_PRIVATE_KEY", "certs/id_rsa")
|
sshKeyPath := "certs/ssh/id_rsa"
|
||||||
if err := utils.GenerateSSHKeyIfNotExist(sshKeyPath); err != nil {
|
if err := utils.GenerateSSHKeyIfNotExist(sshKeyPath); err != nil {
|
||||||
log.Fatalf("Failed to generate SSH key: %s", err)
|
log.Fatalf("Failed to generate SSH key: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ func (cw *CustomWriter) AddInteraction(interaction Interaction) {
|
|||||||
var redirectTLS = false
|
var redirectTLS = false
|
||||||
|
|
||||||
func NewHTTPServer() error {
|
func NewHTTPServer() error {
|
||||||
listener, err := net.Listen("tcp", ":80")
|
httpPort := utils.Getenv("HTTP_PORT", "8080")
|
||||||
|
listener, err := net.Listen("tcp", ":"+httpPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Error listening: " + err.Error())
|
return errors.New("Error listening: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ import (
|
|||||||
|
|
||||||
func NewHTTPSServer() error {
|
func NewHTTPSServer() error {
|
||||||
domain := utils.Getenv("DOMAIN", "localhost")
|
domain := utils.Getenv("DOMAIN", "localhost")
|
||||||
|
httpsPort := utils.Getenv("HTTPS_PORT", "8443")
|
||||||
|
|
||||||
tlsConfig, err := NewTLSConfig(domain)
|
tlsConfig, err := NewTLSConfig(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize TLS config: %w", err)
|
return fmt.Errorf("failed to initialize TLS config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ln, err := tls.Listen("tcp", ":443", tlsConfig)
|
ln, err := tls.Listen("tcp", ":"+httpsPort, tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ func NewTLSConfig(domain string) (*tls.Config, error) {
|
|||||||
var initErr error
|
var initErr error
|
||||||
|
|
||||||
tlsManagerOnce.Do(func() {
|
tlsManagerOnce.Do(func() {
|
||||||
certPath := utils.Getenv("CERT_LOC", "certs/cert.pem")
|
certPath := "certs/tls/cert.pem"
|
||||||
keyPath := utils.Getenv("KEY_LOC", "certs/privkey.pem")
|
keyPath := "certs/tls/privkey.pem"
|
||||||
storagePath := utils.Getenv("CERT_STORAGE_PATH", "certs/certmagic")
|
storagePath := "certs/tls/certmagic"
|
||||||
|
|
||||||
tm := &TLSManager{
|
tm := &TLSManager{
|
||||||
domain: domain,
|
domain: domain,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ func (s *SSHSession) HandleTCPIPForward(req *ssh.Request) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if portToBind == 0 {
|
if portToBind == 0 {
|
||||||
unassign, success := portUtil.Manager.GetUnassignedPort()
|
unassign, success := portUtil.Default.GetUnassignedPort()
|
||||||
portToBind = unassign
|
portToBind = unassign
|
||||||
if !success {
|
if !success {
|
||||||
s.Interaction.SendMessage("No available port\r\n")
|
s.Interaction.SendMessage("No available port\r\n")
|
||||||
@@ -122,7 +122,7 @@ func (s *SSHSession) HandleTCPIPForward(req *ssh.Request) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if isUse, isExist := portUtil.Manager.GetPortStatus(portToBind); isExist && isUse {
|
} else if isUse, isExist := portUtil.Default.GetPortStatus(portToBind); isExist && isUse {
|
||||||
s.Interaction.SendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port. (03)\r\n", portToBind))
|
s.Interaction.SendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port. (03)\r\n", portToBind))
|
||||||
err := req.Reply(false, nil)
|
err := req.Reply(false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,7 +135,7 @@ func (s *SSHSession) HandleTCPIPForward(req *ssh.Request) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := portUtil.Manager.SetPortStatus(portToBind, true)
|
err := portUtil.Default.SetPortStatus(portToBind, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to set port status:", err)
|
log.Println("Failed to set port status:", err)
|
||||||
return
|
return
|
||||||
@@ -208,7 +208,7 @@ func (s *SSHSession) HandleTCPForward(req *ssh.Request, addr string, 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 {
|
||||||
s.Interaction.SendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port.\r\n", portToBind))
|
s.Interaction.SendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port.\r\n", portToBind))
|
||||||
if setErr := portUtil.Manager.SetPortStatus(portToBind, false); setErr != nil {
|
if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil {
|
||||||
log.Printf("Failed to reset port status: %v", setErr)
|
log.Printf("Failed to reset port status: %v", setErr)
|
||||||
}
|
}
|
||||||
err = req.Reply(false, nil)
|
err = req.Reply(false, nil)
|
||||||
@@ -227,7 +227,7 @@ func (s *SSHSession) HandleTCPForward(req *ssh.Request, addr string, portToBind
|
|||||||
err = binary.Write(buf, binary.BigEndian, uint32(portToBind))
|
err = binary.Write(buf, binary.BigEndian, uint32(portToBind))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to write port to buffer:", err)
|
log.Println("Failed to write port to buffer:", err)
|
||||||
if setErr := portUtil.Manager.SetPortStatus(portToBind, false); setErr != nil {
|
if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil {
|
||||||
log.Printf("Failed to reset port status: %v", setErr)
|
log.Printf("Failed to reset port status: %v", setErr)
|
||||||
}
|
}
|
||||||
err = listener.Close()
|
err = listener.Close()
|
||||||
@@ -242,7 +242,7 @@ func (s *SSHSession) HandleTCPForward(req *ssh.Request, addr string, portToBind
|
|||||||
err = req.Reply(true, buf.Bytes())
|
err = req.Reply(true, buf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to reply to request:", err)
|
log.Println("Failed to reply to request:", err)
|
||||||
if setErr := portUtil.Manager.SetPortStatus(portToBind, false); setErr != nil {
|
if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil {
|
||||||
log.Printf("Failed to reset port status: %v", setErr)
|
log.Printf("Failed to reset port status: %v", setErr)
|
||||||
}
|
}
|
||||||
err = listener.Close()
|
err = listener.Close()
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func (l *Lifecycle) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if l.Forwarder.GetTunnelType() == types.TCP {
|
if l.Forwarder.GetTunnelType() == types.TCP {
|
||||||
err := portUtil.Manager.SetPortStatus(l.Forwarder.GetForwardedPort(), false)
|
err := portUtil.Default.SetPortStatus(l.Forwarder.GetForwardedPort(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,22 +11,18 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Env struct {
|
|
||||||
value map[string]string
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var env *Env
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
env = &Env{value: map[string]string{}}
|
if _, err := os.Stat(".env"); err == nil {
|
||||||
|
if err := godotenv.Load(".env"); err != nil {
|
||||||
|
log.Printf("Warning: Failed to load .env file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateRandomString(length int) string {
|
func GenerateRandomString(length int) string {
|
||||||
@@ -41,24 +37,10 @@ func GenerateRandomString(length int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Getenv(key, defaultValue string) string {
|
func Getenv(key, defaultValue string) string {
|
||||||
env.mu.Lock()
|
|
||||||
defer env.mu.Unlock()
|
|
||||||
if val, ok := env.value[key]; ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("HOSTNAME") == "" {
|
|
||||||
err := godotenv.Load(".env")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error loading .env file: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val := os.Getenv(key)
|
val := os.Getenv(key)
|
||||||
if val == "" {
|
if val == "" {
|
||||||
val = defaultValue
|
val = defaultValue
|
||||||
}
|
}
|
||||||
env.value[key] = val
|
|
||||||
|
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user