mirror of
https://github.com/fosrl/olm.git
synced 2026-02-13 00:16:42 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e84f802ed | ||
|
|
f40b0ff820 | ||
|
|
95a4840374 | ||
|
|
27424170e4 | ||
|
|
a8ace6f64a | ||
|
|
3fa1073f49 | ||
|
|
76d86c10ff | ||
|
|
2d34c6c8b2 | ||
|
|
a7f3477bdd | ||
|
|
af0a72d296 | ||
|
|
d1e836e760 | ||
|
|
8dd45c4ca2 | ||
|
|
9db009058b | ||
|
|
29c01deb05 | ||
|
|
8afc28fdff | ||
|
|
4ba2fb7b53 | ||
|
|
2e6076923d | ||
|
|
4c001dc751 | ||
|
|
dc9a547950 | ||
|
|
2be0933246 |
2
.github/workflows/cicd.yml
vendored
2
.github/workflows/cicd.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Build and Release
|
name: Build and Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: amd64-runner
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|||||||
132
.github/workflows/mirror.yaml
vendored
Normal file
132
.github/workflows/mirror.yaml
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
name: Mirror & Sign (Docker Hub to GHCR)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write # for keyless OIDC
|
||||||
|
|
||||||
|
env:
|
||||||
|
SOURCE_IMAGE: docker.io/fosrl/olm
|
||||||
|
DEST_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror-and-dual-sign:
|
||||||
|
runs-on: amd64-runner
|
||||||
|
steps:
|
||||||
|
- name: Install skopeo + jq
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y skopeo jq
|
||||||
|
skopeo --version
|
||||||
|
|
||||||
|
- name: Install cosign
|
||||||
|
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||||
|
|
||||||
|
- name: Input check
|
||||||
|
run: |
|
||||||
|
test -n "${SOURCE_IMAGE}" || (echo "SOURCE_IMAGE is empty" && exit 1)
|
||||||
|
echo "Source : ${SOURCE_IMAGE}"
|
||||||
|
echo "Target : ${DEST_IMAGE}"
|
||||||
|
|
||||||
|
# Auth for skopeo (containers-auth)
|
||||||
|
- name: Skopeo login to GHCR
|
||||||
|
run: |
|
||||||
|
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|
||||||
|
# Auth for cosign (docker-config)
|
||||||
|
- name: Docker login to GHCR (for cosign)
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||||
|
|
||||||
|
- name: List source tags
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
skopeo list-tags --retry-times 3 docker://"${SOURCE_IMAGE}" \
|
||||||
|
| jq -r '.Tags[]' | sort -u > src-tags.txt
|
||||||
|
echo "Found source tags: $(wc -l < src-tags.txt)"
|
||||||
|
head -n 20 src-tags.txt || true
|
||||||
|
|
||||||
|
- name: List destination tags (skip existing)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if skopeo list-tags --retry-times 3 docker://"${DEST_IMAGE}" >/tmp/dst.json 2>/dev/null; then
|
||||||
|
jq -r '.Tags[]' /tmp/dst.json | sort -u > dst-tags.txt
|
||||||
|
else
|
||||||
|
: > dst-tags.txt
|
||||||
|
fi
|
||||||
|
echo "Existing destination tags: $(wc -l < dst-tags.txt)"
|
||||||
|
|
||||||
|
- name: Mirror, dual-sign, and verify
|
||||||
|
env:
|
||||||
|
# keyless
|
||||||
|
COSIGN_YES: "true"
|
||||||
|
# key-based
|
||||||
|
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||||
|
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||||
|
# verify
|
||||||
|
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
copied=0; skipped=0; v_ok=0; errs=0
|
||||||
|
|
||||||
|
issuer="https://token.actions.githubusercontent.com"
|
||||||
|
id_regex="^https://github.com/${{ github.repository }}/.+"
|
||||||
|
|
||||||
|
while read -r tag; do
|
||||||
|
[ -z "$tag" ] && continue
|
||||||
|
|
||||||
|
if grep -Fxq "$tag" dst-tags.txt; then
|
||||||
|
echo "::notice ::Skip (exists) ${DEST_IMAGE}:${tag}"
|
||||||
|
skipped=$((skipped+1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Copy ${SOURCE_IMAGE}:${tag} → ${DEST_IMAGE}:${tag}"
|
||||||
|
if ! skopeo copy --all --retry-times 3 \
|
||||||
|
docker://"${SOURCE_IMAGE}:${tag}" docker://"${DEST_IMAGE}:${tag}"; then
|
||||||
|
echo "::warning title=Copy failed::${SOURCE_IMAGE}:${tag}"
|
||||||
|
errs=$((errs+1)); continue
|
||||||
|
fi
|
||||||
|
copied=$((copied+1))
|
||||||
|
|
||||||
|
digest="$(skopeo inspect --retry-times 3 docker://"${DEST_IMAGE}:${tag}" | jq -r '.Digest')"
|
||||||
|
ref="${DEST_IMAGE}@${digest}"
|
||||||
|
|
||||||
|
echo "==> cosign sign (keyless) --recursive ${ref}"
|
||||||
|
if ! cosign sign --recursive "${ref}"; then
|
||||||
|
echo "::warning title=Keyless sign failed::${ref}"
|
||||||
|
errs=$((errs+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> cosign sign (key) --recursive ${ref}"
|
||||||
|
if ! cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${ref}"; then
|
||||||
|
echo "::warning title=Key sign failed::${ref}"
|
||||||
|
errs=$((errs+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> cosign verify (public key) ${ref}"
|
||||||
|
if ! cosign verify --key env://COSIGN_PUBLIC_KEY "${ref}" -o text; then
|
||||||
|
echo "::warning title=Verify(pubkey) failed::${ref}"
|
||||||
|
errs=$((errs+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> cosign verify (keyless policy) ${ref}"
|
||||||
|
if ! cosign verify \
|
||||||
|
--certificate-oidc-issuer "${issuer}" \
|
||||||
|
--certificate-identity-regexp "${id_regex}" \
|
||||||
|
"${ref}" -o text; then
|
||||||
|
echo "::warning title=Verify(keyless) failed::${ref}"
|
||||||
|
errs=$((errs+1))
|
||||||
|
else
|
||||||
|
v_ok=$((v_ok+1))
|
||||||
|
fi
|
||||||
|
done < src-tags.txt
|
||||||
|
|
||||||
|
echo "---- Summary ----"
|
||||||
|
echo "Copied : $copied"
|
||||||
|
echo "Skipped : $skipped"
|
||||||
|
echo "Verified OK : $v_ok"
|
||||||
|
echo "Errors : $errs"
|
||||||
@@ -4,11 +4,7 @@ Contributions are welcome!
|
|||||||
|
|
||||||
Please see the contribution and local development guide on the docs page before getting started:
|
Please see the contribution and local development guide on the docs page before getting started:
|
||||||
|
|
||||||
https://docs.fossorial.io/development
|
https://docs.pangolin.net/development/contributing
|
||||||
|
|
||||||
For ideas about what features to work on and our future plans, please see the roadmap:
|
|
||||||
|
|
||||||
https://docs.fossorial.io/roadmap
|
|
||||||
|
|
||||||
### Licensing Considerations
|
### Licensing Considerations
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Olm is a [WireGuard](https://www.wireguard.com/) tunnel client designed to secur
|
|||||||
|
|
||||||
Olm is used with Pangolin and Newt as part of the larger system. See documentation below:
|
Olm is used with Pangolin and Newt as part of the larger system. See documentation below:
|
||||||
|
|
||||||
- [Full Documentation](https://docs.fossorial.io)
|
- [Full Documentation](https://docs.pangolin.net)
|
||||||
|
|
||||||
## Key Functions
|
## Key Functions
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ $ cat ~/.config/olm-client/config.json
|
|||||||
{
|
{
|
||||||
"id": "spmzu8rbpzj1qq6",
|
"id": "spmzu8rbpzj1qq6",
|
||||||
"secret": "f6v61mjutwme2kkydbw3fjo227zl60a2tsf5psw9r25hgae3",
|
"secret": "f6v61mjutwme2kkydbw3fjo227zl60a2tsf5psw9r25hgae3",
|
||||||
"endpoint": "https://pangolin.fossorial.io",
|
"endpoint": "https://app.pangolin.net",
|
||||||
"tlsClientCert": ""
|
"tlsClientCert": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
If you discover a security vulnerability, please follow the steps below to responsibly disclose it to us:
|
If you discover a security vulnerability, please follow the steps below to responsibly disclose it to us:
|
||||||
|
|
||||||
1. **Do not create a public GitHub issue or discussion post.** This could put the security of other users at risk.
|
1. **Do not create a public GitHub issue or discussion post.** This could put the security of other users at risk.
|
||||||
2. Send a detailed report to [security@fossorial.io](mailto:security@fossorial.io) or send a **private** message to a maintainer on [Discord](https://discord.gg/HCJR8Xhme4). Include:
|
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) or send a **private** message to a maintainer on [Discord](https://discord.gg/HCJR8Xhme4). Include:
|
||||||
|
|
||||||
- Description and location of the vulnerability.
|
- Description and location of the vulnerability.
|
||||||
- Potential impact of the vulnerability.
|
- Potential impact of the vulnerability.
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fosrl/newt/logger"
|
"github.com/fosrl/newt/logger"
|
||||||
"github.com/fosrl/newt/websocket"
|
|
||||||
"github.com/fosrl/olm/peermonitor"
|
"github.com/fosrl/olm/peermonitor"
|
||||||
|
"github.com/fosrl/olm/websocket"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
|
|||||||
484
config.go
Normal file
484
config.go
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OlmConfig holds all configuration options for the Olm client
|
||||||
|
type OlmConfig struct {
|
||||||
|
// Connection settings
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
|
||||||
|
// Network settings
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
DNS string `json:"dns"`
|
||||||
|
InterfaceName string `json:"interface"`
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
LogLevel string `json:"logLevel"`
|
||||||
|
|
||||||
|
// HTTP server
|
||||||
|
EnableHTTP bool `json:"enableHttp"`
|
||||||
|
HTTPAddr string `json:"httpAddr"`
|
||||||
|
|
||||||
|
// Ping settings
|
||||||
|
PingInterval string `json:"pingInterval"`
|
||||||
|
PingTimeout string `json:"pingTimeout"`
|
||||||
|
|
||||||
|
// Advanced
|
||||||
|
Holepunch bool `json:"holepunch"`
|
||||||
|
TlsClientCert string `json:"tlsClientCert"`
|
||||||
|
|
||||||
|
// Parsed values (not in JSON)
|
||||||
|
PingIntervalDuration time.Duration `json:"-"`
|
||||||
|
PingTimeoutDuration time.Duration `json:"-"`
|
||||||
|
|
||||||
|
// Source tracking (not in JSON)
|
||||||
|
sources map[string]string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigSource tracks where each config value came from
|
||||||
|
type ConfigSource string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SourceDefault ConfigSource = "default"
|
||||||
|
SourceFile ConfigSource = "file"
|
||||||
|
SourceEnv ConfigSource = "environment"
|
||||||
|
SourceCLI ConfigSource = "cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultConfig returns a config with default values
|
||||||
|
func DefaultConfig() *OlmConfig {
|
||||||
|
config := &OlmConfig{
|
||||||
|
MTU: 1280,
|
||||||
|
DNS: "8.8.8.8",
|
||||||
|
LogLevel: "INFO",
|
||||||
|
InterfaceName: "olm",
|
||||||
|
EnableHTTP: false,
|
||||||
|
HTTPAddr: ":9452",
|
||||||
|
PingInterval: "3s",
|
||||||
|
PingTimeout: "5s",
|
||||||
|
Holepunch: false,
|
||||||
|
sources: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track default sources
|
||||||
|
config.sources["mtu"] = string(SourceDefault)
|
||||||
|
config.sources["dns"] = string(SourceDefault)
|
||||||
|
config.sources["logLevel"] = string(SourceDefault)
|
||||||
|
config.sources["interface"] = string(SourceDefault)
|
||||||
|
config.sources["enableHttp"] = string(SourceDefault)
|
||||||
|
config.sources["httpAddr"] = string(SourceDefault)
|
||||||
|
config.sources["pingInterval"] = string(SourceDefault)
|
||||||
|
config.sources["pingTimeout"] = string(SourceDefault)
|
||||||
|
config.sources["holepunch"] = string(SourceDefault)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOlmConfigPath returns the path to the olm config file
|
||||||
|
func getOlmConfigPath() string {
|
||||||
|
configFile := os.Getenv("CONFIG_FILE")
|
||||||
|
if configFile != "" {
|
||||||
|
return configFile
|
||||||
|
}
|
||||||
|
|
||||||
|
var configDir string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
configDir = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "olm-client")
|
||||||
|
case "windows":
|
||||||
|
configDir = filepath.Join(os.Getenv("PROGRAMDATA"), "olm", "olm-client")
|
||||||
|
default: // linux and others
|
||||||
|
configDir = filepath.Join(os.Getenv("HOME"), ".config", "olm-client")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||||
|
fmt.Printf("Warning: Failed to create config directory: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(configDir, "config.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads configuration from file, env vars, and CLI args
|
||||||
|
// Priority: CLI args > Env vars > Config file > Defaults
|
||||||
|
// Returns: (config, showVersion, showConfig, error)
|
||||||
|
func LoadConfig(args []string) (*OlmConfig, bool, bool, error) {
|
||||||
|
// Start with defaults
|
||||||
|
config := DefaultConfig()
|
||||||
|
|
||||||
|
// Load from config file (if exists)
|
||||||
|
fileConfig, err := loadConfigFromFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, false, fmt.Errorf("failed to load config file: %w", err)
|
||||||
|
}
|
||||||
|
if fileConfig != nil {
|
||||||
|
mergeConfigs(config, fileConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override with environment variables
|
||||||
|
loadConfigFromEnv(config)
|
||||||
|
|
||||||
|
// Override with CLI arguments
|
||||||
|
showVersion, showConfig, err := loadConfigFromCLI(config, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse duration strings
|
||||||
|
if err := config.parseDurations(); err != nil {
|
||||||
|
return nil, false, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, showVersion, showConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfigFromFile loads configuration from the JSON config file
|
||||||
|
func loadConfigFromFile() (*OlmConfig, error) {
|
||||||
|
configPath := getOlmConfigPath()
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil // File doesn't exist, not an error
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config OlmConfig
|
||||||
|
if err := json.Unmarshal(data, &config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfigFromEnv loads configuration from environment variables
|
||||||
|
func loadConfigFromEnv(config *OlmConfig) {
|
||||||
|
if val := os.Getenv("PANGOLIN_ENDPOINT"); val != "" {
|
||||||
|
config.Endpoint = val
|
||||||
|
config.sources["endpoint"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("OLM_ID"); val != "" {
|
||||||
|
config.ID = val
|
||||||
|
config.sources["id"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("OLM_SECRET"); val != "" {
|
||||||
|
config.Secret = val
|
||||||
|
config.sources["secret"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("MTU"); val != "" {
|
||||||
|
if mtu, err := strconv.Atoi(val); err == nil {
|
||||||
|
config.MTU = mtu
|
||||||
|
config.sources["mtu"] = string(SourceEnv)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Invalid MTU value: %s, keeping current value\n", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val := os.Getenv("DNS"); val != "" {
|
||||||
|
config.DNS = val
|
||||||
|
config.sources["dns"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("LOG_LEVEL"); val != "" {
|
||||||
|
config.LogLevel = val
|
||||||
|
config.sources["logLevel"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("INTERFACE"); val != "" {
|
||||||
|
config.InterfaceName = val
|
||||||
|
config.sources["interface"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("HTTP_ADDR"); val != "" {
|
||||||
|
config.HTTPAddr = val
|
||||||
|
config.sources["httpAddr"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("PING_INTERVAL"); val != "" {
|
||||||
|
config.PingInterval = val
|
||||||
|
config.sources["pingInterval"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("PING_TIMEOUT"); val != "" {
|
||||||
|
config.PingTimeout = val
|
||||||
|
config.sources["pingTimeout"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("ENABLE_HTTP"); val == "true" {
|
||||||
|
config.EnableHTTP = true
|
||||||
|
config.sources["enableHttp"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
if val := os.Getenv("HOLEPUNCH"); val == "true" {
|
||||||
|
config.Holepunch = true
|
||||||
|
config.sources["holepunch"] = string(SourceEnv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfigFromCLI loads configuration from command-line arguments
|
||||||
|
func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) {
|
||||||
|
serviceFlags := flag.NewFlagSet("service", flag.ContinueOnError)
|
||||||
|
|
||||||
|
// Store original values to detect changes
|
||||||
|
origValues := map[string]interface{}{
|
||||||
|
"endpoint": config.Endpoint,
|
||||||
|
"id": config.ID,
|
||||||
|
"secret": config.Secret,
|
||||||
|
"mtu": config.MTU,
|
||||||
|
"dns": config.DNS,
|
||||||
|
"logLevel": config.LogLevel,
|
||||||
|
"interface": config.InterfaceName,
|
||||||
|
"httpAddr": config.HTTPAddr,
|
||||||
|
"pingInterval": config.PingInterval,
|
||||||
|
"pingTimeout": config.PingTimeout,
|
||||||
|
"enableHttp": config.EnableHTTP,
|
||||||
|
"holepunch": config.Holepunch,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define flags
|
||||||
|
serviceFlags.StringVar(&config.Endpoint, "endpoint", config.Endpoint, "Endpoint of your Pangolin server")
|
||||||
|
serviceFlags.StringVar(&config.ID, "id", config.ID, "Olm ID")
|
||||||
|
serviceFlags.StringVar(&config.Secret, "secret", config.Secret, "Olm secret")
|
||||||
|
serviceFlags.IntVar(&config.MTU, "mtu", config.MTU, "MTU to use")
|
||||||
|
serviceFlags.StringVar(&config.DNS, "dns", config.DNS, "DNS server to use")
|
||||||
|
serviceFlags.StringVar(&config.LogLevel, "log-level", config.LogLevel, "Log level (DEBUG, INFO, WARN, ERROR, FATAL)")
|
||||||
|
serviceFlags.StringVar(&config.InterfaceName, "interface", config.InterfaceName, "Name of the WireGuard interface")
|
||||||
|
serviceFlags.StringVar(&config.HTTPAddr, "http-addr", config.HTTPAddr, "HTTP server address (e.g., ':9452')")
|
||||||
|
serviceFlags.StringVar(&config.PingInterval, "ping-interval", config.PingInterval, "Interval for pinging the server")
|
||||||
|
serviceFlags.StringVar(&config.PingTimeout, "ping-timeout", config.PingTimeout, "Timeout for each ping")
|
||||||
|
serviceFlags.BoolVar(&config.EnableHTTP, "enable-http", config.EnableHTTP, "Enable HTTP server for receiving connection requests")
|
||||||
|
serviceFlags.BoolVar(&config.Holepunch, "holepunch", config.Holepunch, "Enable hole punching")
|
||||||
|
|
||||||
|
version := serviceFlags.Bool("version", false, "Print the version")
|
||||||
|
showConfig := serviceFlags.Bool("show-config", false, "Show configuration sources and exit")
|
||||||
|
|
||||||
|
// Parse the arguments
|
||||||
|
if err := serviceFlags.Parse(args); err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track which values were changed by CLI args
|
||||||
|
if config.Endpoint != origValues["endpoint"].(string) {
|
||||||
|
config.sources["endpoint"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.ID != origValues["id"].(string) {
|
||||||
|
config.sources["id"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.Secret != origValues["secret"].(string) {
|
||||||
|
config.sources["secret"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.MTU != origValues["mtu"].(int) {
|
||||||
|
config.sources["mtu"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.DNS != origValues["dns"].(string) {
|
||||||
|
config.sources["dns"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.LogLevel != origValues["logLevel"].(string) {
|
||||||
|
config.sources["logLevel"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.InterfaceName != origValues["interface"].(string) {
|
||||||
|
config.sources["interface"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.HTTPAddr != origValues["httpAddr"].(string) {
|
||||||
|
config.sources["httpAddr"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.PingInterval != origValues["pingInterval"].(string) {
|
||||||
|
config.sources["pingInterval"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.PingTimeout != origValues["pingTimeout"].(string) {
|
||||||
|
config.sources["pingTimeout"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.EnableHTTP != origValues["enableHttp"].(bool) {
|
||||||
|
config.sources["enableHttp"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
if config.Holepunch != origValues["holepunch"].(bool) {
|
||||||
|
config.sources["holepunch"] = string(SourceCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
return *version, *showConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDurations parses the duration strings into time.Duration
|
||||||
|
func (c *OlmConfig) parseDurations() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Parse ping interval
|
||||||
|
if c.PingInterval != "" {
|
||||||
|
c.PingIntervalDuration, err = time.ParseDuration(c.PingInterval)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", c.PingInterval)
|
||||||
|
c.PingIntervalDuration = 3 * time.Second
|
||||||
|
c.PingInterval = "3s"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.PingIntervalDuration = 3 * time.Second
|
||||||
|
c.PingInterval = "3s"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse ping timeout
|
||||||
|
if c.PingTimeout != "" {
|
||||||
|
c.PingTimeoutDuration, err = time.ParseDuration(c.PingTimeout)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", c.PingTimeout)
|
||||||
|
c.PingTimeoutDuration = 5 * time.Second
|
||||||
|
c.PingTimeout = "5s"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.PingTimeoutDuration = 5 * time.Second
|
||||||
|
c.PingTimeout = "5s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeConfigs merges source config into destination (only non-empty values)
|
||||||
|
// Also tracks that these values came from a file
|
||||||
|
func mergeConfigs(dest, src *OlmConfig) {
|
||||||
|
if src.Endpoint != "" {
|
||||||
|
dest.Endpoint = src.Endpoint
|
||||||
|
dest.sources["endpoint"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.ID != "" {
|
||||||
|
dest.ID = src.ID
|
||||||
|
dest.sources["id"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.Secret != "" {
|
||||||
|
dest.Secret = src.Secret
|
||||||
|
dest.sources["secret"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.MTU != 0 && src.MTU != 1280 {
|
||||||
|
dest.MTU = src.MTU
|
||||||
|
dest.sources["mtu"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.DNS != "" && src.DNS != "8.8.8.8" {
|
||||||
|
dest.DNS = src.DNS
|
||||||
|
dest.sources["dns"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.LogLevel != "" && src.LogLevel != "INFO" {
|
||||||
|
dest.LogLevel = src.LogLevel
|
||||||
|
dest.sources["logLevel"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.InterfaceName != "" && src.InterfaceName != "olm" {
|
||||||
|
dest.InterfaceName = src.InterfaceName
|
||||||
|
dest.sources["interface"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.HTTPAddr != "" && src.HTTPAddr != ":9452" {
|
||||||
|
dest.HTTPAddr = src.HTTPAddr
|
||||||
|
dest.sources["httpAddr"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.PingInterval != "" && src.PingInterval != "3s" {
|
||||||
|
dest.PingInterval = src.PingInterval
|
||||||
|
dest.sources["pingInterval"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.PingTimeout != "" && src.PingTimeout != "5s" {
|
||||||
|
dest.PingTimeout = src.PingTimeout
|
||||||
|
dest.sources["pingTimeout"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.TlsClientCert != "" {
|
||||||
|
dest.TlsClientCert = src.TlsClientCert
|
||||||
|
dest.sources["tlsClientCert"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
// For booleans, we always take the source value if explicitly set
|
||||||
|
if src.EnableHTTP {
|
||||||
|
dest.EnableHTTP = src.EnableHTTP
|
||||||
|
dest.sources["enableHttp"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
if src.Holepunch {
|
||||||
|
dest.Holepunch = src.Holepunch
|
||||||
|
dest.sources["holepunch"] = string(SourceFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig saves the current configuration to the config file
|
||||||
|
func SaveConfig(config *OlmConfig) error {
|
||||||
|
configPath := getOlmConfigPath()
|
||||||
|
data, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal config: %w", err)
|
||||||
|
}
|
||||||
|
return os.WriteFile(configPath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowConfig prints the configuration and the source of each value
|
||||||
|
func (c *OlmConfig) ShowConfig() {
|
||||||
|
configPath := getOlmConfigPath()
|
||||||
|
|
||||||
|
fmt.Println("\n=== Olm Configuration ===\n")
|
||||||
|
fmt.Printf("Config File: %s\n", configPath)
|
||||||
|
|
||||||
|
// Check if config file exists
|
||||||
|
if _, err := os.Stat(configPath); err == nil {
|
||||||
|
fmt.Printf("Config File Status: ✓ exists\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Config File Status: ✗ not found\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\n--- Configuration Values ---")
|
||||||
|
fmt.Println("(Format: Setting = Value [source])\n")
|
||||||
|
|
||||||
|
// Helper to get source or default
|
||||||
|
getSource := func(key string) string {
|
||||||
|
if source, ok := c.sources[key]; ok {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
return string(SourceDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to format value (mask secrets)
|
||||||
|
formatValue := func(key, value string) string {
|
||||||
|
if key == "secret" && value != "" {
|
||||||
|
if len(value) > 8 {
|
||||||
|
return value[:4] + "****" + value[len(value)-4:]
|
||||||
|
}
|
||||||
|
return "****"
|
||||||
|
}
|
||||||
|
if value == "" {
|
||||||
|
return "(not set)"
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection settings
|
||||||
|
fmt.Println("Connection:")
|
||||||
|
fmt.Printf(" endpoint = %s [%s]\n", formatValue("endpoint", c.Endpoint), getSource("endpoint"))
|
||||||
|
fmt.Printf(" id = %s [%s]\n", formatValue("id", c.ID), getSource("id"))
|
||||||
|
fmt.Printf(" secret = %s [%s]\n", formatValue("secret", c.Secret), getSource("secret"))
|
||||||
|
|
||||||
|
// Network settings
|
||||||
|
fmt.Println("\nNetwork:")
|
||||||
|
fmt.Printf(" mtu = %d [%s]\n", c.MTU, getSource("mtu"))
|
||||||
|
fmt.Printf(" dns = %s [%s]\n", c.DNS, getSource("dns"))
|
||||||
|
fmt.Printf(" interface = %s [%s]\n", c.InterfaceName, getSource("interface"))
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
fmt.Println("\nLogging:")
|
||||||
|
fmt.Printf(" log-level = %s [%s]\n", c.LogLevel, getSource("logLevel"))
|
||||||
|
|
||||||
|
// HTTP server
|
||||||
|
fmt.Println("\nHTTP Server:")
|
||||||
|
fmt.Printf(" enable-http = %v [%s]\n", c.EnableHTTP, getSource("enableHttp"))
|
||||||
|
fmt.Printf(" http-addr = %s [%s]\n", c.HTTPAddr, getSource("httpAddr"))
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
fmt.Println("\nTiming:")
|
||||||
|
fmt.Printf(" ping-interval = %s [%s]\n", c.PingInterval, getSource("pingInterval"))
|
||||||
|
fmt.Printf(" ping-timeout = %s [%s]\n", c.PingTimeout, getSource("pingTimeout"))
|
||||||
|
|
||||||
|
// Advanced
|
||||||
|
fmt.Println("\nAdvanced:")
|
||||||
|
fmt.Printf(" holepunch = %v [%s]\n", c.Holepunch, getSource("holepunch"))
|
||||||
|
if c.TlsClientCert != "" {
|
||||||
|
fmt.Printf(" tls-cert = %s [%s]\n", c.TlsClientCert, getSource("tlsClientCert"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source legend
|
||||||
|
fmt.Println("\n--- Source Legend ---")
|
||||||
|
fmt.Println(" default = Built-in default value")
|
||||||
|
fmt.Println(" file = Loaded from config file")
|
||||||
|
fmt.Println(" environment = Set via environment variable")
|
||||||
|
fmt.Println(" cli = Provided as command-line argument")
|
||||||
|
fmt.Println("\nPriority: cli > environment > file > default")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
35
go.mod
35
go.mod
@@ -5,47 +5,18 @@ go 1.25
|
|||||||
require (
|
require (
|
||||||
github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7
|
github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
golang.org/x/crypto v0.42.0
|
golang.org/x/crypto v0.43.0
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
||||||
golang.org/x/sys v0.36.0
|
golang.org/x/sys v0.37.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
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
|
||||||
github.com/docker/docker v28.4.0+incompatible // indirect
|
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/google/btree v1.1.3 // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/google/gopacket v1.1.19 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
golang.org/x/net v0.45.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
|
||||||
golang.org/x/net v0.44.0 // indirect
|
|
||||||
golang.org/x/sync v0.16.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
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 // indirect
|
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 // indirect
|
||||||
software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect
|
software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
82
go.sum
82
go.sum
@@ -1,103 +1,33 @@
|
|||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
|
||||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
|
||||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
|
||||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/fosrl/newt v0.0.0-20250730062419-3ccd755d557a h1:bUGN4piHlcqgfdRLrwqiLZZxgcitzBzNDQS1+CHSmJI=
|
|
||||||
github.com/fosrl/newt v0.0.0-20250730062419-3ccd755d557a/go.mod h1:PbiPYp1hbL07awrmbqTSTz7lTenieTHN6cIkUVCGD3I=
|
|
||||||
github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7 h1:6bSU8Efyhx1SR53iSw1Wjk5V8vDfizGAudq/GlE9b+o=
|
github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7 h1:6bSU8Efyhx1SR53iSw1Wjk5V8vDfizGAudq/GlE9b+o=
|
||||||
github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7/go.mod h1:Ac0k2FmAMC+hu21rAK+p7EnnEGrqKO/QZuGTVHA/XDM=
|
github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7/go.mod h1:Ac0k2FmAMC+hu21rAK+p7EnnEGrqKO/QZuGTVHA/XDM=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
|
||||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
|
||||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
|
||||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
|
||||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
|
||||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
|
||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
|
||||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
|
||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
|
|
||||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
|
||||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
|
||||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
|
||||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
|
||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
|
||||||
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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
|
||||||
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/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
|
||||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
|
||||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 h1:H+qymc2ndLKNFR5TcaPmsHGiJnhJMqeofBYSRq4oG3c=
|
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 h1:H+qymc2ndLKNFR5TcaPmsHGiJnhJMqeofBYSRq4oG3c=
|
||||||
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56/go.mod h1:i8iCZyAdwRnLZYaIi2NUL1gfNtAveqxkKAe0JfAv9Bs=
|
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56/go.mod h1:i8iCZyAdwRnLZYaIi2NUL1gfNtAveqxkKAe0JfAv9Bs=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU=
|
software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU=
|
||||||
|
|||||||
191
main.go
191
main.go
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -16,10 +15,9 @@ import (
|
|||||||
|
|
||||||
"github.com/fosrl/newt/logger"
|
"github.com/fosrl/newt/logger"
|
||||||
"github.com/fosrl/newt/updates"
|
"github.com/fosrl/newt/updates"
|
||||||
"github.com/fosrl/newt/websocket"
|
|
||||||
"github.com/fosrl/olm/httpserver"
|
"github.com/fosrl/olm/httpserver"
|
||||||
"github.com/fosrl/olm/peermonitor"
|
"github.com/fosrl/olm/peermonitor"
|
||||||
"github.com/fosrl/olm/wgtester"
|
"github.com/fosrl/olm/websocket"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
@@ -204,122 +202,40 @@ func runOlmMain(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runOlmMainWithArgs(ctx context.Context, args []string) {
|
func runOlmMainWithArgs(ctx context.Context, args []string) {
|
||||||
// Log that we've entered the main function
|
// Load configuration from file, env vars, and CLI args
|
||||||
// fmt.Printf("runOlmMainWithArgs() called with args: %v\n", args)
|
// Priority: CLI args > Env vars > Config file > Defaults
|
||||||
|
config, showVersion, showConfig, err := LoadConfig(args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to load configuration: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new FlagSet for parsing service arguments
|
// Handle --show-config flag
|
||||||
serviceFlags := flag.NewFlagSet("service", flag.ContinueOnError)
|
if showConfig {
|
||||||
|
config.ShowConfig()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract commonly used values from config for convenience
|
||||||
var (
|
var (
|
||||||
endpoint string
|
endpoint = config.Endpoint
|
||||||
id string
|
id = config.ID
|
||||||
secret string
|
secret = config.Secret
|
||||||
mtu string
|
mtu = config.MTU
|
||||||
mtuInt int
|
logLevel = config.LogLevel
|
||||||
dns string
|
interfaceName = config.InterfaceName
|
||||||
|
enableHTTP = config.EnableHTTP
|
||||||
|
httpAddr = config.HTTPAddr
|
||||||
|
pingInterval = config.PingIntervalDuration
|
||||||
|
pingTimeout = config.PingTimeoutDuration
|
||||||
|
doHolepunch = config.Holepunch
|
||||||
privateKey wgtypes.Key
|
privateKey wgtypes.Key
|
||||||
err error
|
|
||||||
logLevel string
|
|
||||||
interfaceName string
|
|
||||||
enableHTTP bool
|
|
||||||
httpAddr string
|
|
||||||
testMode bool // Add this var for the test flag
|
|
||||||
testTarget string // Add this var for test target
|
|
||||||
pingInterval time.Duration
|
|
||||||
pingTimeout time.Duration
|
|
||||||
doHolepunch bool
|
|
||||||
connected bool
|
connected bool
|
||||||
)
|
)
|
||||||
|
|
||||||
stopHolepunch = make(chan struct{})
|
stopHolepunch = make(chan struct{})
|
||||||
stopPing = make(chan struct{})
|
stopPing = make(chan struct{})
|
||||||
|
|
||||||
// if PANGOLIN_ENDPOINT, OLM_ID, and OLM_SECRET are set as environment variables, they will be used as default values
|
|
||||||
endpoint = os.Getenv("PANGOLIN_ENDPOINT")
|
|
||||||
id = os.Getenv("OLM_ID")
|
|
||||||
secret = os.Getenv("OLM_SECRET")
|
|
||||||
mtu = os.Getenv("MTU")
|
|
||||||
dns = os.Getenv("DNS")
|
|
||||||
logLevel = os.Getenv("LOG_LEVEL")
|
|
||||||
interfaceName = os.Getenv("INTERFACE")
|
|
||||||
httpAddr = os.Getenv("HTTP_ADDR")
|
|
||||||
pingIntervalStr := os.Getenv("PING_INTERVAL")
|
|
||||||
pingTimeoutStr := os.Getenv("PING_TIMEOUT")
|
|
||||||
enableHTTPEnv := os.Getenv("ENABLE_HTTP")
|
|
||||||
holepunchEnv := os.Getenv("HOLEPUNCH")
|
|
||||||
|
|
||||||
enableHTTP = enableHTTPEnv == "true"
|
|
||||||
doHolepunch = holepunchEnv == "true"
|
|
||||||
|
|
||||||
if endpoint == "" {
|
|
||||||
serviceFlags.StringVar(&endpoint, "endpoint", "", "Endpoint of your Pangolin server")
|
|
||||||
}
|
|
||||||
if id == "" {
|
|
||||||
serviceFlags.StringVar(&id, "id", "", "Olm ID")
|
|
||||||
}
|
|
||||||
if secret == "" {
|
|
||||||
serviceFlags.StringVar(&secret, "secret", "", "Olm secret")
|
|
||||||
}
|
|
||||||
if mtu == "" {
|
|
||||||
serviceFlags.StringVar(&mtu, "mtu", "1280", "MTU to use")
|
|
||||||
}
|
|
||||||
if dns == "" {
|
|
||||||
serviceFlags.StringVar(&dns, "dns", "8.8.8.8", "DNS server to use")
|
|
||||||
}
|
|
||||||
if logLevel == "" {
|
|
||||||
serviceFlags.StringVar(&logLevel, "log-level", "INFO", "Log level (DEBUG, INFO, WARN, ERROR, FATAL)")
|
|
||||||
}
|
|
||||||
if interfaceName == "" {
|
|
||||||
serviceFlags.StringVar(&interfaceName, "interface", "olm", "Name of the WireGuard interface")
|
|
||||||
}
|
|
||||||
if httpAddr == "" {
|
|
||||||
serviceFlags.StringVar(&httpAddr, "http-addr", ":9452", "HTTP server address (e.g., ':9452')")
|
|
||||||
}
|
|
||||||
if pingIntervalStr == "" {
|
|
||||||
serviceFlags.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)")
|
|
||||||
}
|
|
||||||
if pingTimeoutStr == "" {
|
|
||||||
serviceFlags.StringVar(&pingTimeoutStr, "ping-timeout", "5s", " Timeout for each ping (default 3s)")
|
|
||||||
}
|
|
||||||
if enableHTTPEnv == "" {
|
|
||||||
serviceFlags.BoolVar(&enableHTTP, "enable-http", false, "Enable HTT server for receiving connection requests")
|
|
||||||
}
|
|
||||||
if holepunchEnv == "" {
|
|
||||||
serviceFlags.BoolVar(&doHolepunch, "holepunch", false, "Enable hole punching (default false)")
|
|
||||||
}
|
|
||||||
|
|
||||||
version := serviceFlags.Bool("version", false, "Print the version")
|
|
||||||
|
|
||||||
// Parse the service arguments
|
|
||||||
if err := serviceFlags.Parse(args); err != nil {
|
|
||||||
fmt.Printf("Error parsing service arguments: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug: Print final values after flag parsing
|
|
||||||
// fmt.Printf("After flag parsing: endpoint='%s', id='%s', secret='%s'\n", endpoint, id, secret)
|
|
||||||
|
|
||||||
// Parse ping intervals
|
|
||||||
if pingIntervalStr != "" {
|
|
||||||
pingInterval, err = time.ParseDuration(pingIntervalStr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", pingIntervalStr)
|
|
||||||
pingInterval = 3 * time.Second
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pingInterval = 3 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
if pingTimeoutStr != "" {
|
|
||||||
pingTimeout, err = time.ParseDuration(pingTimeoutStr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", pingTimeoutStr)
|
|
||||||
pingTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pingTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup Windows event logging if on Windows
|
// Setup Windows event logging if on Windows
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
setupWindowsEventLog()
|
setupWindowsEventLog()
|
||||||
@@ -331,12 +247,11 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
|
|||||||
logger.GetLogger().SetLevel(parseLogLevel(logLevel))
|
logger.GetLogger().SetLevel(parseLogLevel(logLevel))
|
||||||
|
|
||||||
olmVersion := "version_replaceme"
|
olmVersion := "version_replaceme"
|
||||||
if *version {
|
if showVersion {
|
||||||
fmt.Println("Olm version " + olmVersion)
|
fmt.Println("Olm version " + olmVersion)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
} else {
|
|
||||||
logger.Info("Olm version " + olmVersion)
|
|
||||||
}
|
}
|
||||||
|
logger.Info("Olm version " + olmVersion)
|
||||||
|
|
||||||
if err := updates.CheckForUpdate("fosrl", "olm", olmVersion); err != nil {
|
if err := updates.CheckForUpdate("fosrl", "olm", olmVersion); err != nil {
|
||||||
logger.Debug("Failed to check for updates: %v", err)
|
logger.Debug("Failed to check for updates: %v", err)
|
||||||
@@ -351,35 +266,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
|
|||||||
logger.Warn("Hole punching is enabled. This is EXPERIMENTAL and may not work in all environments.")
|
logger.Warn("Hole punching is enabled. This is EXPERIMENTAL and may not work in all environments.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle test mode
|
|
||||||
if testMode {
|
|
||||||
if testTarget == "" {
|
|
||||||
logger.Fatal("Test mode requires -test-target to be set to a server:port")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Running in test mode, connecting to %s", testTarget)
|
|
||||||
|
|
||||||
// Create a new tester client
|
|
||||||
tester, err := wgtester.NewClient(testTarget)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal("Failed to create tester client: %v", err)
|
|
||||||
}
|
|
||||||
defer tester.Close()
|
|
||||||
|
|
||||||
// Test connection with a 2-second timeout
|
|
||||||
connected, rtt := tester.TestConnectionWithTimeout(2 * time.Second)
|
|
||||||
|
|
||||||
if connected {
|
|
||||||
logger.Info("Connection test successful! RTT: %v", rtt)
|
|
||||||
fmt.Printf("Connection test successful! RTT: %v\n", rtt)
|
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
logger.Error("Connection test failed - no response received")
|
|
||||||
fmt.Println("Connection test failed - no response received")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpServer *httpserver.HTTPServer
|
var httpServer *httpserver.HTTPServer
|
||||||
if enableHTTP {
|
if enableHTTP {
|
||||||
httpServer = httpserver.NewHTTPServer(httpAddr)
|
httpServer = httpserver.NewHTTPServer(httpAddr)
|
||||||
@@ -437,9 +323,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("Failed to create olm: %v", err)
|
logger.Fatal("Failed to create olm: %v", err)
|
||||||
}
|
}
|
||||||
endpoint = olm.GetConfig().Endpoint // Update endpoint from config
|
|
||||||
id = olm.GetConfig().ID // Update ID from config
|
|
||||||
secret = olm.GetConfig().Secret // Update secret from config
|
|
||||||
|
|
||||||
// wait until we have a client id and secret and endpoint
|
// wait until we have a client id and secret and endpoint
|
||||||
waitCount := 0
|
waitCount := 0
|
||||||
@@ -467,12 +350,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the mtu string into an int
|
|
||||||
mtuInt, err = strconv.Atoi(mtu)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal("Failed to parse MTU: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -593,12 +470,12 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return tun.CreateTUN(interfaceName, mtuInt)
|
return tun.CreateTUN(interfaceName, mtu)
|
||||||
}
|
}
|
||||||
if tunFdStr := os.Getenv(ENV_WG_TUN_FD); tunFdStr != "" {
|
if tunFdStr := os.Getenv(ENV_WG_TUN_FD); tunFdStr != "" {
|
||||||
return createTUNFromFD(tunFdStr, mtuInt)
|
return createTUNFromFD(tunFdStr, mtu)
|
||||||
}
|
}
|
||||||
return tun.CreateTUN(interfaceName, mtuInt)
|
return tun.CreateTUN(interfaceName, mtu)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -974,6 +851,14 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
|
|||||||
httpServer.SetConnectionStatus(true)
|
httpServer.SetConnectionStatus(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Save our full config AFTER websocket saves its limited config
|
||||||
|
// This ensures all 13 fields are preserved, not just the 4 that websocket saves
|
||||||
|
if err := SaveConfig(config); err != nil {
|
||||||
|
logger.Error("Failed to save full olm config: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Debug("Saved full olm config with all options")
|
||||||
|
}
|
||||||
|
|
||||||
if connected {
|
if connected {
|
||||||
logger.Debug("Already connected, skipping registration")
|
logger.Debug("Already connected, skipping registration")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
2
olm.iss
2
olm.iss
@@ -4,7 +4,7 @@
|
|||||||
#define MyAppName "olm"
|
#define MyAppName "olm"
|
||||||
#define MyAppVersion "1.0.0"
|
#define MyAppVersion "1.0.0"
|
||||||
#define MyAppPublisher "Fossorial Inc."
|
#define MyAppPublisher "Fossorial Inc."
|
||||||
#define MyAppURL "https://fossorial.io"
|
#define MyAppURL "https://pangolin.net"
|
||||||
#define MyAppExeName "olm.exe"
|
#define MyAppExeName "olm.exe"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fosrl/newt/logger"
|
"github.com/fosrl/newt/logger"
|
||||||
"github.com/fosrl/newt/websocket"
|
"github.com/fosrl/olm/websocket"
|
||||||
"github.com/fosrl/olm/wgtester"
|
"github.com/fosrl/olm/wgtester"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
)
|
)
|
||||||
@@ -205,11 +205,11 @@ func (pm *PeerMonitor) HandleFailover(siteID int, relayEndpoint string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for IPv6 and format the endpoint correctly
|
// Check for IPv6 and format the endpoint correctly
|
||||||
formattedEndpoint := relayEndpoint
|
formattedEndpoint := relayEndpoint
|
||||||
if strings.Contains(relayEndpoint, ":") {
|
if strings.Contains(relayEndpoint, ":") {
|
||||||
formattedEndpoint = fmt.Sprintf("[%s]", relayEndpoint)
|
formattedEndpoint = fmt.Sprintf("[%s]", relayEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure WireGuard to use the relay
|
// Configure WireGuard to use the relay
|
||||||
wgConfig := fmt.Sprintf(`private_key=%s
|
wgConfig := fmt.Sprintf(`private_key=%s
|
||||||
|
|||||||
637
websocket/client.go
Normal file
637
websocket/client.go
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
|
||||||
|
"github.com/fosrl/newt/logger"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenResponse struct {
|
||||||
|
Data struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
} `json:"data"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WSMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is not json anymore
|
||||||
|
type Config struct {
|
||||||
|
ID string
|
||||||
|
Secret string
|
||||||
|
Endpoint string
|
||||||
|
TlsClientCert string // legacy PKCS12 file path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
config *Config
|
||||||
|
conn *websocket.Conn
|
||||||
|
baseURL string
|
||||||
|
handlers map[string]MessageHandler
|
||||||
|
done chan struct{}
|
||||||
|
handlersMux sync.RWMutex
|
||||||
|
reconnectInterval time.Duration
|
||||||
|
isConnected bool
|
||||||
|
reconnectMux sync.RWMutex
|
||||||
|
pingInterval time.Duration
|
||||||
|
pingTimeout time.Duration
|
||||||
|
onConnect func() error
|
||||||
|
onTokenUpdate func(token string)
|
||||||
|
writeMux sync.Mutex
|
||||||
|
clientType string // Type of client (e.g., "newt", "olm")
|
||||||
|
tlsConfig TLSConfig
|
||||||
|
configNeedsSave bool // Flag to track if config needs to be saved
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
|
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
|
||||||
|
func WithBaseURL(url string) ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.baseURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTLSConfig sets the TLS configuration for the client
|
||||||
|
func WithTLSConfig(config TLSConfig) ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.tlsConfig = config
|
||||||
|
// For backward compatibility, also set the legacy field
|
||||||
|
if config.PKCS12File != "" {
|
||||||
|
c.config.TlsClientCert = config.PKCS12File
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OnConnect(callback func() error) {
|
||||||
|
c.onConnect = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OnTokenUpdate(callback func(token string)) {
|
||||||
|
c.onTokenUpdate = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new websocket client
|
||||||
|
func NewClient(clientType string, ID, secret string, endpoint string, pingInterval time.Duration, pingTimeout time.Duration, opts ...ClientOption) (*Client, error) {
|
||||||
|
config := &Config{
|
||||||
|
ID: ID,
|
||||||
|
Secret: secret,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
config: config,
|
||||||
|
baseURL: endpoint, // default value
|
||||||
|
handlers: make(map[string]MessageHandler),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
reconnectInterval: 3 * time.Second,
|
||||||
|
isConnected: false,
|
||||||
|
pingInterval: pingInterval,
|
||||||
|
pingTimeout: pingTimeout,
|
||||||
|
clientType: clientType,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply options before loading config
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opt(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetConfig() *Config {
|
||||||
|
return c.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect establishes the WebSocket connection
|
||||||
|
func (c *Client) Connect() error {
|
||||||
|
go c.connectWithRetry()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the WebSocket connection gracefully
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
// Signal shutdown to all goroutines first
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
// Already closed
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
close(c.done)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set connection status to false
|
||||||
|
c.setConnected(false)
|
||||||
|
|
||||||
|
// Close the WebSocket connection gracefully
|
||||||
|
if c.conn != nil {
|
||||||
|
// Send close message
|
||||||
|
c.writeMux.Lock()
|
||||||
|
c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
|
c.writeMux.Unlock()
|
||||||
|
|
||||||
|
// Close the connection
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage sends a message through the WebSocket connection
|
||||||
|
func (c *Client) SendMessage(messageType string, data interface{}) error {
|
||||||
|
if c.conn == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := WSMessage{
|
||||||
|
Type: messageType,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Sending message: %s, data: %+v", messageType, data)
|
||||||
|
|
||||||
|
c.writeMux.Lock()
|
||||||
|
defer c.writeMux.Unlock()
|
||||||
|
return c.conn.WriteJSON(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendMessageInterval(messageType string, data interface{}, interval time.Duration) (stop func()) {
|
||||||
|
stopChan := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
count := 0
|
||||||
|
maxAttempts := 10
|
||||||
|
|
||||||
|
err := c.SendMessage(messageType, data) // Send immediately
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to send initial message: %v", err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to send message: %v", err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
close(stopChan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHandler registers a handler for a specific message type
|
||||||
|
func (c *Client) RegisterHandler(messageType string, handler MessageHandler) {
|
||||||
|
c.handlersMux.Lock()
|
||||||
|
defer c.handlersMux.Unlock()
|
||||||
|
c.handlers[messageType] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getToken() (string, error) {
|
||||||
|
// Parse the base URL to ensure we have the correct hostname
|
||||||
|
baseURL, err := url.Parse(c.baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse base URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have the base URL without trailing slashes
|
||||||
|
baseEndpoint := strings.TrimRight(baseURL.String(), "/")
|
||||||
|
|
||||||
|
var tlsConfig *tls.Config = nil
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return "", fmt.Errorf("failed to setup TLS configuration: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for environment variable to skip TLS verification
|
||||||
|
if os.Getenv("SKIP_TLS_VERIFY") == "true" {
|
||||||
|
if tlsConfig == nil {
|
||||||
|
tlsConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
logger.Debug("TLS certificate verification disabled via SKIP_TLS_VERIFY environment variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenData map[string]interface{}
|
||||||
|
|
||||||
|
// Get a new token
|
||||||
|
if c.clientType == "newt" {
|
||||||
|
tokenData = map[string]interface{}{
|
||||||
|
"newtId": c.config.ID,
|
||||||
|
"secret": c.config.Secret,
|
||||||
|
}
|
||||||
|
} else if c.clientType == "olm" {
|
||||||
|
tokenData = map[string]interface{}{
|
||||||
|
"olmId": c.config.ID,
|
||||||
|
"secret": c.config.Secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(tokenData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to marshal token request data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new request
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"POST",
|
||||||
|
baseEndpoint+"/api/v1/auth/"+c.clientType+"/get-token",
|
||||||
|
bytes.NewBuffer(jsonData),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-CSRF-Token", "x-csrf-protection")
|
||||||
|
|
||||||
|
// Make the request
|
||||||
|
client := &http.Client{}
|
||||||
|
if tlsConfig != nil {
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to request new token: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
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
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
||||||
|
logger.Error("Failed to decode token response.")
|
||||||
|
return "", fmt.Errorf("failed to decode token response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenResp.Success {
|
||||||
|
return "", fmt.Errorf("failed to get token: %s", tokenResp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenResp.Data.Token == "" {
|
||||||
|
return "", fmt.Errorf("received empty token from server")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Received token: %s", tokenResp.Data.Token)
|
||||||
|
|
||||||
|
return tokenResp.Data.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) connectWithRetry() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err := c.establishConnection()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to connect: %v. Retrying in %v...", err, c.reconnectInterval)
|
||||||
|
time.Sleep(c.reconnectInterval)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) establishConnection() error {
|
||||||
|
// Get token for authentication
|
||||||
|
token, err := c.getToken()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.onTokenUpdate != nil {
|
||||||
|
c.onTokenUpdate(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the base URL to determine protocol and hostname
|
||||||
|
baseURL, err := url.Parse(c.baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse base URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine WebSocket protocol based on HTTP protocol
|
||||||
|
wsProtocol := "wss"
|
||||||
|
if baseURL.Scheme == "http" {
|
||||||
|
wsProtocol = "ws"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create WebSocket URL
|
||||||
|
wsURL := fmt.Sprintf("%s://%s/api/v1/ws", wsProtocol, baseURL.Host)
|
||||||
|
u, err := url.Parse(wsURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse WebSocket URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add token to query parameters
|
||||||
|
q := u.Query()
|
||||||
|
q.Set("token", token)
|
||||||
|
q.Set("clientType", c.clientType)
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
// Connect to WebSocket
|
||||||
|
dialer := websocket.DefaultDialer
|
||||||
|
|
||||||
|
// Use new TLS configuration method
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("failed to setup TLS configuration: %w", err)
|
||||||
|
}
|
||||||
|
dialer.TLSClientConfig = tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for environment variable to skip TLS verification for WebSocket connection
|
||||||
|
if os.Getenv("SKIP_TLS_VERIFY") == "true" {
|
||||||
|
if dialer.TLSClientConfig == nil {
|
||||||
|
dialer.TLSClientConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
dialer.TLSClientConfig.InsecureSkipVerify = true
|
||||||
|
logger.Debug("WebSocket TLS certificate verification disabled via SKIP_TLS_VERIFY environment variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, _, err := dialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to WebSocket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.conn = conn
|
||||||
|
c.setConnected(true)
|
||||||
|
|
||||||
|
// Start the ping monitor
|
||||||
|
go c.pingMonitor()
|
||||||
|
// Start the read pump with disconnect detection
|
||||||
|
go c.readPumpWithDisconnectDetection()
|
||||||
|
|
||||||
|
if c.onConnect != nil {
|
||||||
|
if err := c.onConnect(); err != nil {
|
||||||
|
logger.Error("OnConnect callback failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
func (c *Client) pingMonitor() {
|
||||||
|
ticker := time.NewTicker(c.pingInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if c.conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.writeMux.Lock()
|
||||||
|
err := c.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(c.pingTimeout))
|
||||||
|
c.writeMux.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
// Check if we're shutting down before logging error and reconnecting
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
// Expected during shutdown
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
logger.Error("Ping failed: %v", err)
|
||||||
|
c.reconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPumpWithDisconnectDetection reads messages and triggers reconnect on error
|
||||||
|
func (c *Client) readPumpWithDisconnectDetection() {
|
||||||
|
defer func() {
|
||||||
|
if c.conn != nil {
|
||||||
|
c.conn.Close()
|
||||||
|
}
|
||||||
|
// Only attempt reconnect if we're not shutting down
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
// Shutting down, don't reconnect
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
c.reconnect()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
var msg WSMessage
|
||||||
|
err := c.conn.ReadJSON(&msg)
|
||||||
|
if err != nil {
|
||||||
|
// Check if we're shutting down before logging error
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
// Expected during shutdown, don't log as error
|
||||||
|
logger.Debug("WebSocket connection closed during shutdown")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Unexpected error during normal operation
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNormalClosure) {
|
||||||
|
logger.Error("WebSocket read error: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Debug("WebSocket connection closed: %v", err)
|
||||||
|
}
|
||||||
|
return // triggers reconnect via defer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.handlersMux.RLock()
|
||||||
|
if handler, ok := c.handlers[msg.Type]; ok {
|
||||||
|
handler(msg)
|
||||||
|
}
|
||||||
|
c.handlersMux.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) reconnect() {
|
||||||
|
c.setConnected(false)
|
||||||
|
if c.conn != nil {
|
||||||
|
c.conn.Close()
|
||||||
|
c.conn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only reconnect if we're not shutting down
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
go c.connectWithRetry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) setConnected(status bool) {
|
||||||
|
c.reconnectMux.Lock()
|
||||||
|
defer c.reconnectMux.Unlock()
|
||||||
|
c.isConnected = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadClientCertificate Helper method to load client certificates (PKCS12 format)
|
||||||
|
func loadClientCertificate(p12Path string) (*tls.Config, error) {
|
||||||
|
logger.Info("Loading tls-client-cert %s", p12Path)
|
||||||
|
// Read the PKCS12 file
|
||||||
|
p12Data, err := os.ReadFile(p12Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read PKCS12 file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PKCS12 with empty password for non-encrypted files
|
||||||
|
privateKey, certificate, caCerts, err := pkcs12.DecodeChain(p12Data, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode PKCS12: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create certificate
|
||||||
|
cert := tls.Certificate{
|
||||||
|
Certificate: [][]byte{certificate.Raw},
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Add CA certificates if present
|
||||||
|
rootCAs, err := x509.SystemCertPool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load system cert pool: %w", err)
|
||||||
|
}
|
||||||
|
if len(caCerts) > 0 {
|
||||||
|
for _, caCert := range caCerts {
|
||||||
|
rootCAs.AddCert(caCert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create TLS configuration
|
||||||
|
return &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
RootCAs: rootCAs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user