mirror of
https://github.com/fosrl/newt.git
synced 2026-03-27 13:06:38 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2969f9d2d6 | ||
|
|
502ebfc362 | ||
|
|
288413fd15 | ||
|
|
0ba44206b1 | ||
|
|
3f8dcd8f22 | ||
|
|
c5c0143013 | ||
|
|
87ac5c97e3 | ||
|
|
e2238c3cc8 | ||
|
|
58a67328d3 | ||
|
|
002fdc4d3f | ||
|
|
9a1fa2c19f | ||
|
|
a6797172ef | ||
|
|
d373de7fa1 | ||
|
|
f876bad632 | ||
|
|
54b096e6a7 | ||
|
|
10720afd31 | ||
|
|
0b37f20d5d | ||
|
|
aa6e54f383 | ||
|
|
30f8eb9785 | ||
|
|
e765d9c774 | ||
|
|
3ae4ac23ef | ||
|
|
6a98b90b01 | ||
|
|
e0ce9d4e48 | ||
|
|
5914c9ed33 | ||
|
|
109bda961f | ||
|
|
c2a93134b1 | ||
|
|
100d8e6afe | ||
|
|
04f2048a0a | ||
|
|
04de5ef8ba | ||
|
|
e77601cccc | ||
|
|
d52f89f629 | ||
|
|
a9d8ec0b1e | ||
|
|
e9dbfb239b | ||
|
|
a79dccc0e4 | ||
|
|
42dfb6b3d8 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -33,3 +33,8 @@ updates:
|
|||||||
minor-updates:
|
minor-updates:
|
||||||
update-types:
|
update-types:
|
||||||
- "minor"
|
- "minor"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|||||||
10
.github/workflows/cicd.yml
vendored
10
.github/workflows/cicd.yml
vendored
@@ -12,16 +12,16 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||||
@@ -31,9 +31,9 @@ jobs:
|
|||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.24
|
go-version: 1.25
|
||||||
|
|
||||||
- name: Update version in main.go
|
- name: Update version in main.go
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: 1.25
|
||||||
|
|
||||||
- name: Build go
|
- name: Build go
|
||||||
run: go build
|
run: go build
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.24
|
1.25
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.24-alpine AS builder
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
# Set the working directory inside the container
|
# Set the working directory inside the container
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
74
README.md
74
README.md
@@ -37,11 +37,15 @@ When Newt receives WireGuard control messages, it will use the information encod
|
|||||||
- `mtu` (optional): MTU for the internal WG interface. Default: 1280
|
- `mtu` (optional): MTU for the internal WG interface. Default: 1280
|
||||||
- `dns` (optional): DNS server to use to resolve the endpoint. Default: 8.8.8.8
|
- `dns` (optional): DNS server to use to resolve the endpoint. Default: 8.8.8.8
|
||||||
- `log-level` (optional): The log level to use (DEBUG, INFO, WARN, ERROR, FATAL). Default: INFO
|
- `log-level` (optional): The log level to use (DEBUG, INFO, WARN, ERROR, FATAL). Default: INFO
|
||||||
|
- `enforce-hc-cert` (optional): Enforce certificate validation for health checks. Default: false (accepts any cert)
|
||||||
- `docker-socket` (optional): Set the Docker socket to use the container discovery integration
|
- `docker-socket` (optional): Set the Docker socket to use the container discovery integration
|
||||||
- `ping-interval` (optional): Interval for pinging the server. Default: 3s
|
- `ping-interval` (optional): Interval for pinging the server. Default: 3s
|
||||||
- `ping-timeout` (optional): Timeout for each ping. Default: 5s
|
- `ping-timeout` (optional): Timeout for each ping. Default: 5s
|
||||||
- `updown` (optional): A script to be called when targets are added or removed.
|
- `updown` (optional): A script to be called when targets are added or removed.
|
||||||
- `tls-client-cert` (optional): Client certificate (p12 or pfx) for mTLS. See [mTLS](#mtls)
|
- `tls-client-cert` (optional): Client certificate (p12 or pfx) for mTLS. See [mTLS](#mtls)
|
||||||
|
- `tls-client-cert` (optional): Path to client certificate (PEM format, optional if using PKCS12). See [mTLS](#mtls)
|
||||||
|
- `tls-client-key` (optional): Path to private key for mTLS (PEM format, optional if using PKCS12)
|
||||||
|
- `tls-ca-cert` (optional): Path to CA certificate to verify server (PEM format, optional if using PKCS12)
|
||||||
- `docker-enforce-network-validation` (optional): Validate the container target is on the same network as the newt process. Default: false
|
- `docker-enforce-network-validation` (optional): Validate the container target is on the same network as the newt process. Default: false
|
||||||
- `health-file` (optional): Check if connection to WG server (pangolin) is ok. creates a file if ok, removes it if not ok. Can be used with docker healtcheck to restart newt
|
- `health-file` (optional): Check if connection to WG server (pangolin) is ok. creates a file if ok, removes it if not ok. Can be used with docker healtcheck to restart newt
|
||||||
- `accept-clients` (optional): Enable WireGuard server mode to accept incoming newt client connections. Default: false
|
- `accept-clients` (optional): Enable WireGuard server mode to accept incoming newt client connections. Default: false
|
||||||
@@ -65,7 +69,11 @@ All CLI arguments can be set using environment variables as an alternative to co
|
|||||||
- `PING_TIMEOUT`: Timeout for each ping. Default: 5s (equivalent to `--ping-timeout`)
|
- `PING_TIMEOUT`: Timeout for each ping. Default: 5s (equivalent to `--ping-timeout`)
|
||||||
- `UPDOWN_SCRIPT`: Path to updown script for target add/remove events (equivalent to `--updown`)
|
- `UPDOWN_SCRIPT`: Path to updown script for target add/remove events (equivalent to `--updown`)
|
||||||
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
||||||
|
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
||||||
|
- `TLS_CLIENT_KEY`: Path to private key for mTLS (equivalent to `--tls-client-key`)
|
||||||
|
- `TLS_CA_CERT`: Path to CA certificate to verify server (equivalent to `--tls-ca-cert`)
|
||||||
- `DOCKER_ENFORCE_NETWORK_VALIDATION`: Validate container targets are on same network. Default: false (equivalent to `--docker-enforce-network-validation`)
|
- `DOCKER_ENFORCE_NETWORK_VALIDATION`: Validate container targets are on same network. Default: false (equivalent to `--docker-enforce-network-validation`)
|
||||||
|
- `ENFORCE_HC_CERT`: Enforce certificate validation for health checks. Default: false (equivalent to `--enforce-hc-cert`)
|
||||||
- `HEALTH_FILE`: Path to health file for connection monitoring (equivalent to `--health-file`)
|
- `HEALTH_FILE`: Path to health file for connection monitoring (equivalent to `--health-file`)
|
||||||
- `ACCEPT_CLIENTS`: Enable WireGuard server mode. Default: false (equivalent to `--accept-clients`)
|
- `ACCEPT_CLIENTS`: Enable WireGuard server mode. Default: false (equivalent to `--accept-clients`)
|
||||||
- `GENERATE_AND_SAVE_KEY_TO`: Path to save generated private key (equivalent to `--generateAndSaveKeyTo`)
|
- `GENERATE_AND_SAVE_KEY_TO`: Path to save generated private key (equivalent to `--generateAndSaveKeyTo`)
|
||||||
@@ -229,7 +237,27 @@ Newt can integrate with the Docker socket to provide remote inspection of Docker
|
|||||||
|
|
||||||
**Configuration:**
|
**Configuration:**
|
||||||
|
|
||||||
You can specify the Docker socket path using the `--docker-socket` CLI argument or by setting the `DOCKER_SOCKET` environment variable. On most linux systems the socket is `/var/run/docker.sock`. When deploying newt as a container, you need to mount the host socket as a volume for the newt container to access it. If the Docker socket is not available or accessible, Newt will gracefully disable Docker integration and continue normal operation.
|
You can specify the Docker socket path using the `--docker-socket` CLI argument or by setting the `DOCKER_SOCKET` environment variable. If the Docker socket is not available or accessible, Newt will gracefully disable Docker integration and continue normal operation.
|
||||||
|
|
||||||
|
Supported values include:
|
||||||
|
|
||||||
|
- Local UNIX socket (default):
|
||||||
|
>You must mount the socket file into the container using a volume, so Newt can access it.
|
||||||
|
|
||||||
|
`unix:///var/run/docker.sock`
|
||||||
|
|
||||||
|
- TCP socket (e.g., via Docker Socket Proxy):
|
||||||
|
|
||||||
|
`tcp://localhost:2375`
|
||||||
|
|
||||||
|
- HTTP/HTTPS endpoints (e.g., remote Docker APIs):
|
||||||
|
|
||||||
|
`http://your-host:2375`
|
||||||
|
|
||||||
|
- SSH connections (experimental, requires SSH setup):
|
||||||
|
|
||||||
|
`ssh://user@host`
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -243,8 +271,9 @@ services:
|
|||||||
- PANGOLIN_ENDPOINT=https://example.com
|
- PANGOLIN_ENDPOINT=https://example.com
|
||||||
- NEWT_ID=2ix2t8xk22ubpfy
|
- NEWT_ID=2ix2t8xk22ubpfy
|
||||||
- NEWT_SECRET=nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2
|
- NEWT_SECRET=nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2
|
||||||
- DOCKER_SOCKET=/var/run/docker.sock
|
- DOCKER_SOCKET=unix:///var/run/docker.sock
|
||||||
```
|
```
|
||||||
|
>If you previously used just a path like `/var/run/docker.sock`, it still works — Newt assumes it is a UNIX socket by default.
|
||||||
|
|
||||||
#### Hostnames vs IPs
|
#### Hostnames vs IPs
|
||||||
|
|
||||||
@@ -283,16 +312,20 @@ You can look at updown.py as a reference script to get started!
|
|||||||
|
|
||||||
### mTLS
|
### mTLS
|
||||||
|
|
||||||
Newt supports mutual TLS (mTLS) authentication, if the server has been configured to request a client certificate.
|
Newt supports mutual TLS (mTLS) authentication if the server is configured to request a client certificate. You can use either a PKCS12 (.p12/.pfx) file or split PEM files for the client cert, private key, and CA.
|
||||||
|
|
||||||
- Only PKCS12 (.p12 or .pfx) file format is accepted
|
#### Option 1: PKCS12 (Legacy)
|
||||||
- The PKCS12 file must contain:
|
|
||||||
- Private key
|
|
||||||
- Public certificate
|
|
||||||
- CA certificate
|
|
||||||
- Encrypted PKCS12 files are currently not supported
|
|
||||||
|
|
||||||
Examples:
|
> This is the original method and still supported.
|
||||||
|
|
||||||
|
* File must contain:
|
||||||
|
|
||||||
|
* Client private key
|
||||||
|
* Public certificate
|
||||||
|
* CA certificate
|
||||||
|
* Encrypted `.p12` files are **not supported**
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
newt \
|
newt \
|
||||||
@@ -302,6 +335,27 @@ newt \
|
|||||||
--tls-client-cert ./client.p12
|
--tls-client-cert ./client.p12
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Option 2: Split PEM Files (Preferred)
|
||||||
|
|
||||||
|
You can now provide separate files for:
|
||||||
|
|
||||||
|
* `--tls-client-cert`: client certificate (`.crt` or `.pem`)
|
||||||
|
* `--tls-client-key`: client private key (`.key` or `.pem`)
|
||||||
|
* `--tls-ca-cert`: CA cert to verify the server
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
newt \
|
||||||
|
--id 31frd0uzbjvp721 \
|
||||||
|
--secret h51mmlknrvrwv8s4r1i210azhumt6isgbpyavxodibx1k2d6 \
|
||||||
|
--endpoint https://example.com \
|
||||||
|
--tls-client-cert ./client.crt \
|
||||||
|
--tls-client-key ./client.key \
|
||||||
|
--tls-ca-cert ./ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
newt:
|
newt:
|
||||||
|
|||||||
@@ -53,22 +53,65 @@ type Network struct {
|
|||||||
DNSNames []string `json:"dnsNames,omitempty"`
|
DNSNames []string `json:"dnsNames,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strcuture parts of docker api endpoint
|
||||||
|
type dockerHost struct {
|
||||||
|
protocol string // e.g. unix, http, tcp, ssh
|
||||||
|
address string // e.g. "/var/run/docker.sock" or "host:port"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the docker api endpoint into its parts
|
||||||
|
func parseDockerHost(raw string) (dockerHost, error) {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(raw, "unix://"):
|
||||||
|
return dockerHost{"unix", strings.TrimPrefix(raw, "unix://")}, nil
|
||||||
|
case strings.HasPrefix(raw, "ssh://"):
|
||||||
|
// SSH is treated as TCP-like transport by the docker client
|
||||||
|
return dockerHost{"ssh", strings.TrimPrefix(raw, "ssh://")}, nil
|
||||||
|
case strings.HasPrefix(raw, "tcp://"), strings.HasPrefix(raw, "http://"), strings.HasPrefix(raw, "https://"):
|
||||||
|
s := raw
|
||||||
|
s = strings.TrimPrefix(s, "tcp://")
|
||||||
|
s = strings.TrimPrefix(s, "http://")
|
||||||
|
s = strings.TrimPrefix(s, "https://")
|
||||||
|
return dockerHost{"tcp", s}, nil
|
||||||
|
case strings.HasPrefix(raw, "/"):
|
||||||
|
// Absolute path without scheme - treat as unix socket
|
||||||
|
return dockerHost{"unix", raw}, nil
|
||||||
|
default:
|
||||||
|
// For relative paths or other formats, also default to unix
|
||||||
|
return dockerHost{"unix", raw}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CheckSocket checks if Docker socket is available
|
// CheckSocket checks if Docker socket is available
|
||||||
func CheckSocket(socketPath string) bool {
|
func CheckSocket(socketPath string) bool {
|
||||||
// Use the provided socket path or default to standard location
|
// Use the provided socket path or default to standard location
|
||||||
if socketPath == "" {
|
if socketPath == "" {
|
||||||
socketPath = "/var/run/docker.sock"
|
socketPath = "unix:///var/run/docker.sock"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to create a connection to the Docker socket
|
// Ensure the socket path is properly formatted
|
||||||
conn, err := net.Dial("unix", socketPath)
|
if !strings.Contains(socketPath, "://") {
|
||||||
|
// If no scheme provided, assume unix socket
|
||||||
|
socketPath = "unix://" + socketPath
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := parseDockerHost(socketPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("Docker socket not available at %s: %v", socketPath, err)
|
logger.Debug("Invalid Docker socket path '%s': %v", socketPath, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
protocol := host.protocol
|
||||||
|
addr := host.address
|
||||||
|
|
||||||
|
// ssh might need different verification, but tcp works for basic reachability
|
||||||
|
conn, err := net.DialTimeout(protocol, addr, 2*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug("Docker not reachable via %s at %s: %v", protocol, addr, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
logger.Debug("Docker socket is available at %s", socketPath)
|
logger.Debug("Docker reachable via %s at %s", protocol, addr)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +159,13 @@ func IsWithinHostNetwork(socketPath string, targetAddress string, targetPort int
|
|||||||
func ListContainers(socketPath string, enforceNetworkValidation bool) ([]Container, error) {
|
func ListContainers(socketPath string, enforceNetworkValidation bool) ([]Container, error) {
|
||||||
// Use the provided socket path or default to standard location
|
// Use the provided socket path or default to standard location
|
||||||
if socketPath == "" {
|
if socketPath == "" {
|
||||||
socketPath = "/var/run/docker.sock"
|
socketPath = "unix:///var/run/docker.sock"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the socket path is properly formatted for the Docker client
|
||||||
|
if !strings.Contains(socketPath, "://") {
|
||||||
|
// If no scheme provided, assume unix socket
|
||||||
|
socketPath = "unix://" + socketPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to filter down containers returned to Pangolin
|
// Used to filter down containers returned to Pangolin
|
||||||
@@ -132,7 +181,7 @@ func ListContainers(socketPath string, enforceNetworkValidation bool) ([]Contain
|
|||||||
|
|
||||||
// Create client with custom socket path
|
// Create client with custom socket path
|
||||||
cli, err := client.NewClientWithOpts(
|
cli, err := client.NewClientWithOpts(
|
||||||
client.WithHost("unix://"+socketPath),
|
client.WithHost(socketPath),
|
||||||
client.WithAPIVersionNegotiation(),
|
client.WithAPIVersionNegotiation(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -182,7 +231,6 @@ func ListContainers(socketPath string, enforceNetworkValidation bool) ([]Contain
|
|||||||
hostname = containerInfo.Config.Hostname
|
hostname = containerInfo.Config.Hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Skip host container if set
|
// Skip host container if set
|
||||||
if hostContainerId != "" && c.ID == hostContainerId {
|
if hostContainerId != "" && c.ID == hostContainerId {
|
||||||
continue
|
continue
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1753489912,
|
"lastModified": 1756217674,
|
||||||
"narHash": "sha256-uDCFHeXdRIgJpYmtcUxGEsZ+hYlLPBhR83fdU+vbC1s=",
|
"narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "13e8d35b7d6028b7198f8186bc0347c6abaa2701",
|
"rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
pkgs = pkgsFor system;
|
pkgs = pkgsFor system;
|
||||||
|
|
||||||
# Update version when releasing
|
# Update version when releasing
|
||||||
version = "1.4.1";
|
version = "1.4.2";
|
||||||
|
|
||||||
# Update the version in a new source tree
|
# Update the version in a new source tree
|
||||||
srcWithReplacedVersion = pkgs.runCommand "newt-src-with-version" { } ''
|
srcWithReplacedVersion = pkgs.runCommand "newt-src-with-version" { } ''
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -1,15 +1,15 @@
|
|||||||
module github.com/fosrl/newt
|
module github.com/fosrl/newt
|
||||||
|
|
||||||
go 1.24
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/docker/docker v28.3.3+incompatible
|
github.com/docker/docker v28.3.3+incompatible
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
golang.org/x/crypto v0.40.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
||||||
golang.org/x/net v0.42.0
|
golang.org/x/net v0.43.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
|
||||||
@@ -48,7 +48,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -105,8 +105,8 @@ go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAj
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
@@ -117,8 +117,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -129,12 +129,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package healthcheck
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -66,20 +67,31 @@ type StatusChangeCallback func(targets map[int]*Target)
|
|||||||
|
|
||||||
// Monitor manages health check targets and their monitoring
|
// Monitor manages health check targets and their monitoring
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
targets map[int]*Target
|
targets map[int]*Target
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
callback StatusChangeCallback
|
callback StatusChangeCallback
|
||||||
client *http.Client
|
client *http.Client
|
||||||
|
enforceCert bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMonitor creates a new health check monitor
|
// NewMonitor creates a new health check monitor
|
||||||
func NewMonitor(callback StatusChangeCallback) *Monitor {
|
func NewMonitor(callback StatusChangeCallback, enforceCert bool) *Monitor {
|
||||||
logger.Info("Creating new health check monitor")
|
logger.Info("Creating new health check monitor with certificate enforcement: %t", enforceCert)
|
||||||
|
|
||||||
|
// Configure TLS settings based on certificate enforcement
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: !enforceCert,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return &Monitor{
|
return &Monitor{
|
||||||
targets: make(map[int]*Target),
|
targets: make(map[int]*Target),
|
||||||
callback: callback,
|
callback: callback,
|
||||||
|
enforceCert: enforceCert,
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
|
Transport: transport,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,6 +379,11 @@ func (m *Monitor) performHealthCheck(target *Target) {
|
|||||||
logger.Debug("Target %d: performing health check %d to %s",
|
logger.Debug("Target %d: performing health check %d to %s",
|
||||||
target.Config.ID, target.CheckCount, url)
|
target.Config.ID, target.CheckCount, url)
|
||||||
|
|
||||||
|
if target.Config.Scheme == "https" {
|
||||||
|
logger.Debug("Target %d: HTTPS health check with certificate enforcement: %t",
|
||||||
|
target.Config.ID, m.enforceCert)
|
||||||
|
}
|
||||||
|
|
||||||
// Create request
|
// Create request
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(target.Config.Timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(target.Config.Timeout)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
174
main.go
174
main.go
@@ -74,6 +74,18 @@ type ExitNodePingResult struct {
|
|||||||
WasPreviouslyConnected bool `json:"wasPreviouslyConnected"`
|
WasPreviouslyConnected bool `json:"wasPreviouslyConnected"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom flag type for multiple CA files
|
||||||
|
type stringSlice []string
|
||||||
|
|
||||||
|
func (s *stringSlice) String() string {
|
||||||
|
return strings.Join(*s, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSlice) Set(value string) error {
|
||||||
|
*s = append(*s, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
endpoint string
|
endpoint string
|
||||||
id string
|
id string
|
||||||
@@ -89,7 +101,6 @@ var (
|
|||||||
keepInterface bool
|
keepInterface bool
|
||||||
acceptClients bool
|
acceptClients bool
|
||||||
updownScript string
|
updownScript string
|
||||||
tlsPrivateKey string
|
|
||||||
dockerSocket string
|
dockerSocket string
|
||||||
dockerEnforceNetworkValidation string
|
dockerEnforceNetworkValidation string
|
||||||
dockerEnforceNetworkValidationBool bool
|
dockerEnforceNetworkValidationBool bool
|
||||||
@@ -103,6 +114,15 @@ var (
|
|||||||
authorizedKeysFile string
|
authorizedKeysFile string
|
||||||
preferEndpoint string
|
preferEndpoint string
|
||||||
healthMonitor *healthcheck.Monitor
|
healthMonitor *healthcheck.Monitor
|
||||||
|
enforceHealthcheckCert bool
|
||||||
|
|
||||||
|
// New mTLS configuration variables
|
||||||
|
tlsClientCert string
|
||||||
|
tlsClientKey string
|
||||||
|
tlsClientCAs []string
|
||||||
|
|
||||||
|
// Legacy PKCS12 support (deprecated)
|
||||||
|
tlsPrivateKey string
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -119,12 +139,13 @@ func main() {
|
|||||||
keepInterfaceEnv := os.Getenv("KEEP_INTERFACE")
|
keepInterfaceEnv := os.Getenv("KEEP_INTERFACE")
|
||||||
acceptClientsEnv := os.Getenv("ACCEPT_CLIENTS")
|
acceptClientsEnv := os.Getenv("ACCEPT_CLIENTS")
|
||||||
useNativeInterfaceEnv := os.Getenv("USE_NATIVE_INTERFACE")
|
useNativeInterfaceEnv := os.Getenv("USE_NATIVE_INTERFACE")
|
||||||
|
enforceHealthcheckCertEnv := os.Getenv("ENFORCE_HC_CERT")
|
||||||
|
|
||||||
keepInterface = keepInterfaceEnv == "true"
|
keepInterface = keepInterfaceEnv == "true"
|
||||||
acceptClients = acceptClientsEnv == "true"
|
acceptClients = acceptClientsEnv == "true"
|
||||||
useNativeInterface = useNativeInterfaceEnv == "true"
|
useNativeInterface = useNativeInterfaceEnv == "true"
|
||||||
|
enforceHealthcheckCert = enforceHealthcheckCertEnv == "true"
|
||||||
|
|
||||||
tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT")
|
|
||||||
dockerSocket = os.Getenv("DOCKER_SOCKET")
|
dockerSocket = os.Getenv("DOCKER_SOCKET")
|
||||||
pingIntervalStr := os.Getenv("PING_INTERVAL")
|
pingIntervalStr := os.Getenv("PING_INTERVAL")
|
||||||
pingTimeoutStr := os.Getenv("PING_TIMEOUT")
|
pingTimeoutStr := os.Getenv("PING_TIMEOUT")
|
||||||
@@ -133,6 +154,25 @@ func main() {
|
|||||||
// authorizedKeysFile = os.Getenv("AUTHORIZED_KEYS_FILE")
|
// authorizedKeysFile = os.Getenv("AUTHORIZED_KEYS_FILE")
|
||||||
authorizedKeysFile = ""
|
authorizedKeysFile = ""
|
||||||
|
|
||||||
|
// Read new mTLS environment variables
|
||||||
|
tlsClientCert = os.Getenv("TLS_CLIENT_CERT")
|
||||||
|
tlsClientKey = os.Getenv("TLS_CLIENT_KEY")
|
||||||
|
tlsClientCAsEnv := os.Getenv("TLS_CLIENT_CAS")
|
||||||
|
if tlsClientCAsEnv != "" {
|
||||||
|
tlsClientCAs = strings.Split(tlsClientCAsEnv, ",")
|
||||||
|
// Trim spaces from each CA file path
|
||||||
|
for i, ca := range tlsClientCAs {
|
||||||
|
tlsClientCAs[i] = strings.TrimSpace(ca)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy PKCS12 support (deprecated)
|
||||||
|
tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT_PKCS12")
|
||||||
|
// Keep backward compatibility with old environment variable name
|
||||||
|
if tlsPrivateKey == "" {
|
||||||
|
tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT")
|
||||||
|
}
|
||||||
|
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server")
|
flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server")
|
||||||
}
|
}
|
||||||
@@ -169,11 +209,11 @@ func main() {
|
|||||||
if acceptClientsEnv == "" {
|
if acceptClientsEnv == "" {
|
||||||
flag.BoolVar(&acceptClients, "accept-clients", false, "Accept clients on the WireGuard interface")
|
flag.BoolVar(&acceptClients, "accept-clients", false, "Accept clients on the WireGuard interface")
|
||||||
}
|
}
|
||||||
if tlsPrivateKey == "" {
|
if enforceHealthcheckCertEnv == "" {
|
||||||
flag.StringVar(&tlsPrivateKey, "tls-client-cert", "", "Path to client certificate used for mTLS")
|
flag.BoolVar(&enforceHealthcheckCert, "enforce-hc-cert", false, "Enforce certificate validation for health checks (default: false, accepts any cert)")
|
||||||
}
|
}
|
||||||
if dockerSocket == "" {
|
if dockerSocket == "" {
|
||||||
flag.StringVar(&dockerSocket, "docker-socket", "", "Path to Docker socket (typically /var/run/docker.sock)")
|
flag.StringVar(&dockerSocket, "docker-socket", "", "Path or address to Docker socket (typically unix:///var/run/docker.sock)")
|
||||||
}
|
}
|
||||||
if pingIntervalStr == "" {
|
if pingIntervalStr == "" {
|
||||||
flag.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)")
|
flag.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)")
|
||||||
@@ -188,6 +228,23 @@ func main() {
|
|||||||
// flag.StringVar(&authorizedKeysFile, "authorized-keys-file", "~/.ssh/authorized_keys", "Path to authorized keys file (if unset, no keys will be authorized)")
|
// flag.StringVar(&authorizedKeysFile, "authorized-keys-file", "~/.ssh/authorized_keys", "Path to authorized keys file (if unset, no keys will be authorized)")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// Add new mTLS flags
|
||||||
|
if tlsClientCert == "" {
|
||||||
|
flag.StringVar(&tlsClientCert, "tls-client-cert-file", "", "Path to client certificate file (PEM/DER format)")
|
||||||
|
}
|
||||||
|
if tlsClientKey == "" {
|
||||||
|
flag.StringVar(&tlsClientKey, "tls-client-key", "", "Path to client private key file (PEM/DER format)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle multiple CA files
|
||||||
|
var tlsClientCAsFlag stringSlice
|
||||||
|
flag.Var(&tlsClientCAsFlag, "tls-client-ca", "Path to CA certificate file for validating remote certificates (can be specified multiple times)")
|
||||||
|
|
||||||
|
// Legacy PKCS12 flag (deprecated)
|
||||||
|
if tlsPrivateKey == "" {
|
||||||
|
flag.StringVar(&tlsPrivateKey, "tls-client-cert", "", "Path to client certificate (PKCS12 format) - DEPRECATED: use --tls-client-cert-file and --tls-client-key instead")
|
||||||
|
}
|
||||||
|
|
||||||
if pingIntervalStr != "" {
|
if pingIntervalStr != "" {
|
||||||
pingInterval, err = time.ParseDuration(pingIntervalStr)
|
pingInterval, err = time.ParseDuration(pingIntervalStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,7 +269,7 @@ func main() {
|
|||||||
flag.StringVar(&dockerEnforceNetworkValidation, "docker-enforce-network-validation", "false", "Enforce validation of container on newt network (true or false)")
|
flag.StringVar(&dockerEnforceNetworkValidation, "docker-enforce-network-validation", "false", "Enforce validation of container on newt network (true or false)")
|
||||||
}
|
}
|
||||||
if healthFile == "" {
|
if healthFile == "" {
|
||||||
flag.StringVar(&healthFile, "health-file", "", "Path to health file (if unset, health file won’t be written)")
|
flag.StringVar(&healthFile, "health-file", "", "Path to health file (if unset, health file won't be written)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// do a --version check
|
// do a --version check
|
||||||
@@ -220,6 +277,11 @@ func main() {
|
|||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Merge command line CA flags with environment variable CAs
|
||||||
|
if len(tlsClientCAsFlag) > 0 {
|
||||||
|
tlsClientCAs = append(tlsClientCAs, tlsClientCAsFlag...)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Init()
|
logger.Init()
|
||||||
loggerLevel := parseLogLevel(logLevel)
|
loggerLevel := parseLogLevel(logLevel)
|
||||||
logger.GetLogger().SetLevel(parseLogLevel(logLevel))
|
logger.GetLogger().SetLevel(parseLogLevel(logLevel))
|
||||||
@@ -249,14 +311,42 @@ func main() {
|
|||||||
dockerEnforceNetworkValidationBool = false
|
dockerEnforceNetworkValidationBool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add TLS configuration validation
|
||||||
|
if err := validateTLSConfig(); err != nil {
|
||||||
|
logger.Fatal("TLS configuration error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show deprecation warning if using PKCS12
|
||||||
|
if tlsPrivateKey != "" {
|
||||||
|
logger.Warn("Using deprecated PKCS12 format for mTLS. Consider migrating to separate certificate files using --tls-client-cert-file, --tls-client-key, and --tls-client-ca")
|
||||||
|
}
|
||||||
|
|
||||||
privateKey, err = wgtypes.GeneratePrivateKey()
|
privateKey, err = wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("Failed to generate private key: %v", err)
|
logger.Fatal("Failed to generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create client option based on TLS configuration
|
||||||
var opt websocket.ClientOption
|
var opt websocket.ClientOption
|
||||||
if tlsPrivateKey != "" {
|
if tlsClientCert != "" && tlsClientKey != "" {
|
||||||
opt = websocket.WithTLSConfig(tlsPrivateKey)
|
// Use new separate certificate configuration
|
||||||
|
opt = websocket.WithTLSConfig(websocket.TLSConfig{
|
||||||
|
ClientCertFile: tlsClientCert,
|
||||||
|
ClientKeyFile: tlsClientKey,
|
||||||
|
CAFiles: tlsClientCAs,
|
||||||
|
})
|
||||||
|
logger.Debug("Using separate certificate files for mTLS")
|
||||||
|
logger.Debug("Client cert: %s", tlsClientCert)
|
||||||
|
logger.Debug("Client key: %s", tlsClientKey)
|
||||||
|
logger.Debug("CA files: %v", tlsClientCAs)
|
||||||
|
} else if tlsPrivateKey != "" {
|
||||||
|
// Use existing PKCS12 configuration for backward compatibility
|
||||||
|
opt = websocket.WithTLSConfig(websocket.TLSConfig{
|
||||||
|
PKCS12File: tlsPrivateKey,
|
||||||
|
})
|
||||||
|
logger.Debug("Using PKCS12 file for mTLS: %s", tlsPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new client
|
// Create a new client
|
||||||
client, err := websocket.NewClient(
|
client, err := websocket.NewClient(
|
||||||
"newt",
|
"newt",
|
||||||
@@ -277,7 +367,22 @@ func main() {
|
|||||||
logger.Debug("Endpoint: %v", endpoint)
|
logger.Debug("Endpoint: %v", endpoint)
|
||||||
logger.Debug("Log Level: %v", logLevel)
|
logger.Debug("Log Level: %v", logLevel)
|
||||||
logger.Debug("Docker Network Validation Enabled: %v", dockerEnforceNetworkValidationBool)
|
logger.Debug("Docker Network Validation Enabled: %v", dockerEnforceNetworkValidationBool)
|
||||||
logger.Debug("TLS Private Key Set: %v", tlsPrivateKey != "")
|
logger.Debug("Health Check Certificate Enforcement: %v", enforceHealthcheckCert)
|
||||||
|
|
||||||
|
// Add new TLS debug logging
|
||||||
|
if tlsClientCert != "" {
|
||||||
|
logger.Debug("TLS Client Cert File: %v", tlsClientCert)
|
||||||
|
}
|
||||||
|
if tlsClientKey != "" {
|
||||||
|
logger.Debug("TLS Client Key File: %v", tlsClientKey)
|
||||||
|
}
|
||||||
|
if len(tlsClientCAs) > 0 {
|
||||||
|
logger.Debug("TLS CA Files: %v", tlsClientCAs)
|
||||||
|
}
|
||||||
|
if tlsPrivateKey != "" {
|
||||||
|
logger.Debug("TLS PKCS12 File: %v", tlsPrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
if dns != "" {
|
if dns != "" {
|
||||||
logger.Debug("Dns: %v", dns)
|
logger.Debug("Dns: %v", dns)
|
||||||
}
|
}
|
||||||
@@ -328,7 +433,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to send health check status update: %v", err)
|
logger.Error("Failed to send health check status update: %v", err)
|
||||||
}
|
}
|
||||||
})
|
}, enforceHealthcheckCert)
|
||||||
|
|
||||||
var pingWithRetryStopChan chan struct{}
|
var pingWithRetryStopChan chan struct{}
|
||||||
|
|
||||||
@@ -1094,6 +1199,10 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
|||||||
logger.Info("Websocket connected")
|
logger.Info("Websocket connected")
|
||||||
|
|
||||||
if !connected {
|
if !connected {
|
||||||
|
// make sure the stop function is called
|
||||||
|
if stopFunc != nil {
|
||||||
|
stopFunc()
|
||||||
|
}
|
||||||
// request from the server the list of nodes to ping at newt/ping/request
|
// request from the server the list of nodes to ping at newt/ping/request
|
||||||
stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{}, 3*time.Second)
|
stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{}, 3*time.Second)
|
||||||
logger.Info("Requesting exit nodes from server")
|
logger.Info("Requesting exit nodes from server")
|
||||||
@@ -1147,3 +1256,48 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
|||||||
logger.Info("Exiting...")
|
logger.Info("Exiting...")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateTLSConfig validates the TLS configuration
|
||||||
|
func validateTLSConfig() error {
|
||||||
|
// Check for conflicting configurations
|
||||||
|
pkcs12Specified := tlsPrivateKey != ""
|
||||||
|
separateFilesSpecified := tlsClientCert != "" || tlsClientKey != "" || len(tlsClientCAs) > 0
|
||||||
|
|
||||||
|
if pkcs12Specified && separateFilesSpecified {
|
||||||
|
return fmt.Errorf("cannot use both PKCS12 format (--tls-client-cert) and separate certificate files (--tls-client-cert-file, --tls-client-key, --tls-client-ca)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If using separate files, both cert and key are required
|
||||||
|
if (tlsClientCert != "" && tlsClientKey == "") || (tlsClientCert == "" && tlsClientKey != "") {
|
||||||
|
return fmt.Errorf("both --tls-client-cert-file and --tls-client-key must be specified together")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate certificate files exist
|
||||||
|
if tlsClientCert != "" {
|
||||||
|
if _, err := os.Stat(tlsClientCert); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("client certificate file does not exist: %s", tlsClientCert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsClientKey != "" {
|
||||||
|
if _, err := os.Stat(tlsClientKey); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("client key file does not exist: %s", tlsClientKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CA files exist
|
||||||
|
for _, caFile := range tlsClientCAs {
|
||||||
|
if _, err := os.Stat(caFile); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("CA certificate file does not exist: %s", caFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate PKCS12 file exists if specified
|
||||||
|
if tlsPrivateKey != "" {
|
||||||
|
if _, err := os.Stat(tlsPrivateKey); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("PKCS12 certificate file does not exist: %s", tlsPrivateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -35,12 +36,24 @@ type Client struct {
|
|||||||
onTokenUpdate func(token string)
|
onTokenUpdate func(token string)
|
||||||
writeMux sync.Mutex
|
writeMux sync.Mutex
|
||||||
clientType string // Type of client (e.g., "newt", "olm")
|
clientType string // Type of client (e.g., "newt", "olm")
|
||||||
|
tlsConfig TLSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOption func(*Client)
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
type MessageHandler func(message WSMessage)
|
type MessageHandler func(message WSMessage)
|
||||||
|
|
||||||
|
// TLSConfig holds TLS configuration options
|
||||||
|
type TLSConfig struct {
|
||||||
|
// New separate certificate support
|
||||||
|
ClientCertFile string
|
||||||
|
ClientKeyFile string
|
||||||
|
CAFiles []string
|
||||||
|
|
||||||
|
// Existing PKCS12 support (deprecated)
|
||||||
|
PKCS12File string
|
||||||
|
}
|
||||||
|
|
||||||
// WithBaseURL sets the base URL for the client
|
// WithBaseURL sets the base URL for the client
|
||||||
func WithBaseURL(url string) ClientOption {
|
func WithBaseURL(url string) ClientOption {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
@@ -48,9 +61,14 @@ func WithBaseURL(url string) ClientOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithTLSConfig(tlsClientCertPath string) ClientOption {
|
// WithTLSConfig sets the TLS configuration for the client
|
||||||
|
func WithTLSConfig(config TLSConfig) ClientOption {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.config.TlsClientCert = tlsClientCertPath
|
c.tlsConfig = config
|
||||||
|
// For backward compatibility, also set the legacy field
|
||||||
|
if config.PKCS12File != "" {
|
||||||
|
c.config.TlsClientCert = config.PKCS12File
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,19 +175,29 @@ func (c *Client) SendMessage(messageType string, data interface{}) error {
|
|||||||
func (c *Client) SendMessageInterval(messageType string, data interface{}, interval time.Duration) (stop func()) {
|
func (c *Client) SendMessageInterval(messageType string, data interface{}, interval time.Duration) (stop func()) {
|
||||||
stopChan := make(chan struct{})
|
stopChan := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
count := 0
|
||||||
|
maxAttempts := 10
|
||||||
|
|
||||||
err := c.SendMessage(messageType, data) // Send immediately
|
err := c.SendMessage(messageType, data) // Send immediately
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to send initial message: %v", err)
|
logger.Error("Failed to send initial message: %v", err)
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
if count >= maxAttempts {
|
||||||
|
logger.Info("SendMessageInterval timed out after %d attempts for message type: %s", maxAttempts, messageType)
|
||||||
|
return
|
||||||
|
}
|
||||||
err = c.SendMessage(messageType, data)
|
err = c.SendMessage(messageType, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to send message: %v", err)
|
logger.Error("Failed to send message: %v", err)
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -198,10 +226,12 @@ func (c *Client) getToken() (string, error) {
|
|||||||
baseEndpoint := strings.TrimRight(baseURL.String(), "/")
|
baseEndpoint := strings.TrimRight(baseURL.String(), "/")
|
||||||
|
|
||||||
var tlsConfig *tls.Config = nil
|
var tlsConfig *tls.Config = nil
|
||||||
if c.config.TlsClientCert != "" {
|
|
||||||
tlsConfig, err = loadClientCertificate(c.config.TlsClientCert)
|
// Use new TLS configuration method
|
||||||
|
if c.tlsConfig.ClientCertFile != "" || c.tlsConfig.ClientKeyFile != "" || len(c.tlsConfig.CAFiles) > 0 || c.tlsConfig.PKCS12File != "" {
|
||||||
|
tlsConfig, err = c.setupTLS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to load certificate %s: %w", c.config.TlsClientCert, err)
|
return "", fmt.Errorf("failed to setup TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +292,9 @@ func (c *Client) getToken() (string, error) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logger.Error("Failed to get token with status code: %d", resp.StatusCode)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
return "", fmt.Errorf("failed to get token with status code: %d", resp.StatusCode)
|
logger.Error("Failed to get token with status code: %d, body: %s", resp.StatusCode, string(body))
|
||||||
|
return "", fmt.Errorf("failed to get token with status code: %d, body: %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenResp TokenResponse
|
var tokenResp TokenResponse
|
||||||
@@ -340,11 +371,13 @@ func (c *Client) establishConnection() error {
|
|||||||
|
|
||||||
// Connect to WebSocket
|
// Connect to WebSocket
|
||||||
dialer := websocket.DefaultDialer
|
dialer := websocket.DefaultDialer
|
||||||
if c.config.TlsClientCert != "" {
|
|
||||||
logger.Info("Adding tls to req")
|
// Use new TLS configuration method
|
||||||
tlsConfig, err := loadClientCertificate(c.config.TlsClientCert)
|
if c.tlsConfig.ClientCertFile != "" || c.tlsConfig.ClientKeyFile != "" || len(c.tlsConfig.CAFiles) > 0 || c.tlsConfig.PKCS12File != "" {
|
||||||
|
logger.Info("Setting up TLS configuration for WebSocket connection")
|
||||||
|
tlsConfig, err := c.setupTLS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load certificate %s: %w", c.config.TlsClientCert, err)
|
return fmt.Errorf("failed to setup TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
dialer.TLSClientConfig = tlsConfig
|
dialer.TLSClientConfig = tlsConfig
|
||||||
}
|
}
|
||||||
@@ -357,6 +390,7 @@ func (c *Client) establishConnection() error {
|
|||||||
dialer.TLSClientConfig.InsecureSkipVerify = true
|
dialer.TLSClientConfig.InsecureSkipVerify = true
|
||||||
logger.Debug("WebSocket TLS certificate verification disabled via SKIP_TLS_VERIFY environment variable")
|
logger.Debug("WebSocket TLS certificate verification disabled via SKIP_TLS_VERIFY environment variable")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, _, err := dialer.Dial(u.String(), nil)
|
conn, _, err := dialer.Dial(u.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to connect to WebSocket: %w", err)
|
return fmt.Errorf("failed to connect to WebSocket: %w", err)
|
||||||
@@ -383,6 +417,69 @@ func (c *Client) establishConnection() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupTLS configures TLS based on the TLS configuration
|
||||||
|
func (c *Client) setupTLS() (*tls.Config, error) {
|
||||||
|
tlsConfig := &tls.Config{}
|
||||||
|
|
||||||
|
// Handle new separate certificate configuration
|
||||||
|
if c.tlsConfig.ClientCertFile != "" && c.tlsConfig.ClientKeyFile != "" {
|
||||||
|
logger.Info("Loading separate certificate files for mTLS")
|
||||||
|
logger.Debug("Client cert: %s", c.tlsConfig.ClientCertFile)
|
||||||
|
logger.Debug("Client key: %s", c.tlsConfig.ClientKeyFile)
|
||||||
|
|
||||||
|
// Load client certificate and key
|
||||||
|
cert, err := tls.LoadX509KeyPair(c.tlsConfig.ClientCertFile, c.tlsConfig.ClientKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load client certificate pair: %w", err)
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
|
||||||
|
// Load CA certificates for remote validation if specified
|
||||||
|
if len(c.tlsConfig.CAFiles) > 0 {
|
||||||
|
logger.Debug("Loading CA certificates: %v", c.tlsConfig.CAFiles)
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
for _, caFile := range c.tlsConfig.CAFiles {
|
||||||
|
caCert, err := os.ReadFile(caFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read CA file %s: %w", caFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as PEM first, then DER
|
||||||
|
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||||
|
// If PEM parsing failed, try DER
|
||||||
|
cert, err := x509.ParseCertificate(caCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse CA certificate from %s: %w", caFile, err)
|
||||||
|
}
|
||||||
|
caCertPool.AddCert(cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsConfig.RootCAs = caCertPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to existing PKCS12 implementation for backward compatibility
|
||||||
|
if c.tlsConfig.PKCS12File != "" {
|
||||||
|
logger.Info("Loading PKCS12 certificate for mTLS (deprecated)")
|
||||||
|
return c.setupPKCS12TLS()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy fallback using config.TlsClientCert
|
||||||
|
if c.config.TlsClientCert != "" {
|
||||||
|
logger.Info("Loading legacy PKCS12 certificate for mTLS (deprecated)")
|
||||||
|
return loadClientCertificate(c.config.TlsClientCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupPKCS12TLS loads TLS configuration from PKCS12 file
|
||||||
|
func (c *Client) setupPKCS12TLS() (*tls.Config, error) {
|
||||||
|
return loadClientCertificate(c.tlsConfig.PKCS12File)
|
||||||
|
}
|
||||||
|
|
||||||
// pingMonitor sends pings at a short interval and triggers reconnect on failure
|
// pingMonitor sends pings at a short interval and triggers reconnect on failure
|
||||||
func (c *Client) pingMonitor() {
|
func (c *Client) pingMonitor() {
|
||||||
ticker := time.NewTicker(c.pingInterval)
|
ticker := time.NewTicker(c.pingInterval)
|
||||||
@@ -487,7 +584,7 @@ func (c *Client) setConnected(status bool) {
|
|||||||
c.isConnected = status
|
c.isConnected = status
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadClientCertificate Helper method to load client certificates
|
// LoadClientCertificate Helper method to load client certificates (PKCS12 format)
|
||||||
func loadClientCertificate(p12Path string) (*tls.Config, error) {
|
func loadClientCertificate(p12Path string) (*tls.Config, error) {
|
||||||
logger.Info("Loading tls-client-cert %s", p12Path)
|
logger.Info("Loading tls-client-cert %s", p12Path)
|
||||||
// Read the PKCS12 file
|
// Read the PKCS12 file
|
||||||
|
|||||||
@@ -399,6 +399,10 @@ func (s *WireGuardService) SetOnNetstackClose(callback func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *WireGuardService) LoadRemoteConfig() error {
|
func (s *WireGuardService) LoadRemoteConfig() error {
|
||||||
|
if s.stopGetConfig != nil {
|
||||||
|
s.stopGetConfig()
|
||||||
|
s.stopGetConfig = nil
|
||||||
|
}
|
||||||
s.stopGetConfig = s.client.SendMessageInterval("newt/wg/get-config", map[string]interface{}{
|
s.stopGetConfig = s.client.SendMessageInterval("newt/wg/get-config", map[string]interface{}{
|
||||||
"publicKey": s.key.PublicKey().String(),
|
"publicKey": s.key.PublicKey().String(),
|
||||||
"port": s.Port,
|
"port": s.Port,
|
||||||
|
|||||||
Reference in New Issue
Block a user