-
Notifications
You must be signed in to change notification settings - Fork 164
feat(wireguard): manage WireGuard peers via Xray's inbound UserManager #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| stages: | ||
| - verify | ||
| - docker | ||
|
|
||
| variables: | ||
| GOTOOLCHAIN: auto | ||
| GOFLAGS: "-mod=readonly" | ||
|
|
||
| go-verify: | ||
| image: golang:1.26.3 | ||
| stage: verify | ||
| script: | ||
| - test -z "$(gofmt -l backend/xray/api/account.go backend/xray/api/wireguard_account.go backend/xray/api/wireguard_key.go backend/xray/api/wireguard_account_test.go backend/xray/config.go backend/xray/user.go common/service.pb.go)" | ||
| - go mod download | ||
| - apt-get update | ||
| - apt-get install -y --no-install-recommends curl openssl | ||
| - curl -L https://github.com/PasarGuard/scripts/raw/main/install_core.sh | bash -s -- --tag v26.7.11 --os linux --arch 64 | ||
| - make generate_server_cert | ||
| - make generate_client_cert | ||
| - go vet ./... | ||
| - go test ./... -p 1 | ||
|
|
||
| go-race-wireguard: | ||
| image: golang:1.26.3 | ||
| stage: verify | ||
| script: | ||
| - apt-get update | ||
| - apt-get install -y --no-install-recommends gcc | ||
| - CGO_ENABLED=1 go test -race ./backend/wireguard | ||
|
|
||
| docker-build: | ||
| image: docker:27.5.1 | ||
| stage: docker | ||
| services: | ||
| - name: docker:27.5.1-dind | ||
| command: ["--tls=false"] | ||
| variables: | ||
| DOCKER_HOST: tcp://docker:2375 | ||
| DOCKER_TLS_CERTDIR: "" | ||
| script: | ||
| - docker build --build-arg XRAY_TAG=v26.7.11 --tag "$CI_REGISTRY_IMAGE/node:$CI_COMMIT_SHA" . | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ MAIN = ./cmd/node | |
| PREFIX ?= $(shell go env GOPATH) | ||
| XRAY_OS ?= | ||
| XRAY_ARCH ?= | ||
| XRAY_TAG ?= v26.7.11 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: set -eu
printf '%s\n' '--- diff summary ---'
git diff --stat a69d7ed6811af281ffc01e11478638d922130f0d 9fd1526810d3a10f75ef0246e75a15c75588ced1
printf '%s\n' '--- relevant diff ---'
git diff --unified=20 a69d7ed6811af281ffc01e11478638d922130f0d 9fd1526810d3a10f75ef0246e75a15c75588ced1 -- Makefile Dockerfile Dockerfile.xray
printf '%s\n' '--- current relevant files ---'
cat -n Makefile | sed -n '1,35p'
cat -n Dockerfile | sed -n '1,20p'
cat -n Dockerfile.xray | sed -n '1,20p'
printf '%s\n' '--- XRAY_TAG and UserManager references ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' 'XRAY_TAG|UserManager|user.?manager|WireGuard' .
printf '%s\n' '--- project guidance candidates ---'
git ls-files | rg '(^|/)(README|CONTRIBUTING|AGENTS|CLAUDE| Makefile|Dockerfile)|(^|/)\.github/' | head -80Repository: PasarGuard/node Length of output: 25800 🏁 Script executed: set -eu
printf '%s\n' '--- changed Xray sources ---'
git diff --unified=12 a69d7ed6811af281ffc01e11478638d922130f0d 9fd1526810d3a10f75ef0246e75a15c75588ced1 -- backend/xray go.mod .gitlab-ci.yml
printf '%s\n' '--- Xray source outline ---'
ast-grep outline backend/xray
printf '%s\n' '--- Xray API/config/user source ---'
for f in backend/xray/config.go backend/xray/user.go backend/xray/api/account.go backend/xray/api/wireguard_account.go backend/xray/api/wireguard_key.go; do
echo "### $f"
cat -n "$f"
done
printf '%s\n' '--- dependency and guidance references ---'
rg -n 'xray|Xray|v26|install_core|XRAY_TAG|stable|release' go.mod go.sum README.md CONTRIBUTING.md .gitlab-ci.yml .github Dockerfile Dockerfile.xray Makefile
]]</capability>
<capability name="web_search"><![CDATA[
XTLS Xray-core v26.7.11 v26.9.8 release pre-release UserManager APIRepository: PasarGuard/node Length of output: 42567 🏁 Script executed: set -u
python3 - <<'PY'
import json, urllib.request
base = "https://api.github.com/repos/XTLS/Xray-core"
for endpoint in ("releases/tags/v26.7.11", "releases/tags/v26.9.8", "git/ref/tags/v26.7.11", "git/ref/tags/v26.9.8"):
url = f"{base}/{endpoint}"
print(f"### {url}")
try:
with urllib.request.urlopen(url, timeout=20) as r:
data = json.load(r)
if endpoint.startswith("releases/"):
print(json.dumps({k:data.get(k) for k in ("tag_name","name","prerelease","draft","published_at","html_url")}, sort_keys=True))
else:
print(json.dumps({"ref":data.get("ref"), "object":data.get("object")}, sort_keys=True))
except Exception as e:
print(type(e).__name__, str(e))
PY
printf '%s\n' '--- tagged Xray source markers ---'
for tag in v26.7.11 v26.9.8; do
echo "### $tag: proxy/wireguard config files"
for path in proxy/wireguard/config.proto proxy/wireguard/config.go app/proxyman/command/command.go; do
url="https://raw.githubusercontent.com/XTLS/Xray-core/$tag/$path"
code=$(curl -L -sS -o /tmp/xray-source -w '%{http_code}' "$url")
echo "$code $path"
if [ "$code" = 200 ]; then
rg -n 'PeerConfig|UserManager|AddInboundUser|RemoveInboundUser|wireguard' /tmp/xray-source | head -30 || true
fi
done
doneRepository: PasarGuard/node Length of output: 2705 🌐 Web query:
💡 Result: <source_evidence> Citations:
🏁 Script executed: set -u
for tag in v26.7.11 v26.9.8; do
echo "### release $tag"
curl -k -L -sS -H 'Accept: application/vnd.github+json' \
"https://api.github.com/repos/XTLS/Xray-core/releases/tags/$tag" |
python3 -c 'import json,sys; d=json.load(sys.stdin); print({k:d.get(k) for k in ("tag_name","name","prerelease","draft","published_at","html_url")})'
echo "### tag $tag"
curl -k -L -sS -H 'Accept: application/vnd.github+json' \
"https://api.github.com/repos/XTLS/Xray-core/git/ref/tags/$tag" |
python3 -c 'import json,sys; d=json.load(sys.stdin); print({"ref":d.get("ref"),"object":d.get("object")})'
doneRepository: PasarGuard/node Length of output: 1063 Document or replace the pre-release Xray default.
📍 Affects 3 files
🤖 Prompt for AI Agents |
||
| # Map GOARCH to installer arch flag (pure make vars to avoid shell leakage) | ||
| XRAY_ARCH_MAP_amd64 = 64 | ||
| XRAY_ARCH_MAP_386 = 32 | ||
|
|
@@ -125,9 +126,9 @@ ifeq ($(UNAME_S),Linux) | |
| if [ "$(DISTRO)" = "debian" ] || [ "$(DISTRO)" = "ubuntu" ] || \ | ||
| [ "$(DISTRO)" = "centos" ] || [ "$(DISTRO)" = "rhel" ] || [ "$(DISTRO)" = "fedora" ] || \ | ||
| [ "$(DISTRO)" = "arch" ]; then \ | ||
| curl -L https://github.com/PasarGuard/scripts/raw/main/install_core.sh | sudo bash -s -- $(XRAY_INSTALL_ARGS); \ | ||
| curl -L https://github.com/PasarGuard/scripts/raw/main/install_core.sh | sudo bash -s -- --tag $(XRAY_TAG) $(XRAY_INSTALL_ARGS); \ | ||
| else \ | ||
| curl -L https://github.com/PasarGuard/scripts/raw/main/install_core.sh | bash -s -- $(XRAY_INSTALL_ARGS); \ | ||
| curl -L https://github.com/PasarGuard/scripts/raw/main/install_core.sh | bash -s -- --tag $(XRAY_TAG) $(XRAY_INSTALL_ARGS); \ | ||
| fi | ||
|
|
||
| else | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/xtls/xray-core/common/serial" | ||
| "github.com/xtls/xray-core/proxy/wireguard" | ||
|
|
||
| "github.com/pasarguard/node/common" | ||
| ) | ||
|
|
||
| // WireguardAccount is an Xray UserManager WireGuard peer. | ||
| type WireguardAccount struct { | ||
| BaseAccount | ||
| PublicKey string `json:"publicKey"` | ||
| PreSharedKey string `json:"preSharedKey,omitempty"` | ||
| AllowedIPs []string `json:"allowedIPs"` | ||
| } | ||
|
|
||
| func (wa *WireguardAccount) Message() (*serial.TypedMessage, error) { | ||
| return ToTypedMessage(&wireguard.PeerConfig{ | ||
| PublicKey: wa.PublicKey, | ||
| PreSharedKey: wa.PreSharedKey, | ||
| AllowedIps: wa.AllowedIPs, | ||
| }) | ||
| } | ||
|
|
||
| func NewWireguardAccount(user *common.User) (*WireguardAccount, error) { | ||
| wg := user.GetProxies().GetWireguard() | ||
| if wg == nil || wg.GetPublicKey() == "" { | ||
| return nil, fmt.Errorf("wireguard public_key is required") | ||
| } | ||
|
|
||
| pubHex, err := WireguardKeyToHex(wg.GetPublicKey()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("wireguard public_key: %w", err) | ||
| } | ||
|
|
||
| pskHex := "" | ||
| if psk := wg.GetPreSharedKey(); psk != "" { | ||
| pskHex, err = WireguardKeyToHex(psk) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("wireguard pre_shared_key: %w", err) | ||
| } | ||
| } | ||
|
|
||
| allowed := wg.GetPeerIps() | ||
| if len(allowed) == 0 { | ||
| return nil, fmt.Errorf("wireguard peer_ips is required") | ||
| } | ||
|
|
||
| return &WireguardAccount{ | ||
| BaseAccount: BaseAccount{ | ||
| Email: user.GetEmail(), | ||
| Level: 0, | ||
| }, | ||
| PublicKey: pubHex, | ||
| PreSharedKey: pskHex, | ||
| AllowedIPs: allowed, | ||
| }, nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/pasarguard/node/common" | ||
| "github.com/xtls/xray-core/proxy/wireguard" | ||
| "google.golang.org/protobuf/proto" | ||
| ) | ||
|
|
||
| func TestWireguardKeyToHex(t *testing.T) { | ||
| raw := strings.Repeat("\x11", 32) | ||
| want := strings.Repeat("11", 32) | ||
| encoded := base64.StdEncoding.EncodeToString([]byte(raw)) | ||
|
|
||
| for _, tc := range []struct { | ||
| name string | ||
| key string | ||
| want string | ||
| }{ | ||
| {"base64", encoded, want}, | ||
| {"hex", want, want}, | ||
| } { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| got, err := WireguardKeyToHex(tc.key) | ||
| if err != nil || got != tc.want { | ||
| t.Fatalf("WireguardKeyToHex() = %q, %v; want %q, nil", got, err, tc.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestWireguardKeyToHexRejectsInvalidKeys(t *testing.T) { | ||
| for _, key := range []string{"", "not-a-key", base64.StdEncoding.EncodeToString([]byte("short")), strings.Repeat("z", 64)} { | ||
| if got, err := WireguardKeyToHex(key); err == nil || got != "" { | ||
| t.Fatalf("WireguardKeyToHex(%q) = %q, %v; want empty result and error", key, got, err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestNewWireguardAccountNormalizesPSKAndMessage(t *testing.T) { | ||
| publicKey := base64.StdEncoding.EncodeToString([]byte(strings.Repeat("P", 32))) | ||
| psk := base64.StdEncoding.EncodeToString([]byte(strings.Repeat("S", 32))) | ||
| account, err := NewWireguardAccount(&common.User{ | ||
| Email: "user@example.test", | ||
| Proxies: &common.Proxy{Wireguard: &common.Wireguard{ | ||
| PublicKey: publicKey, PreSharedKey: psk, PeerIps: []string{"10.0.0.2/32"}, | ||
| }}, | ||
| }) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if account.GetEmail() != "user@example.test" || account.PreSharedKey != strings.Repeat("53", 32) { | ||
| t.Fatalf("account = %#v", account) | ||
| } | ||
| message, err := account.Message() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| peer := new(wireguard.PeerConfig) | ||
| if err := proto.Unmarshal(message.Value, peer); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if peer.PreSharedKey != account.PreSharedKey { | ||
| t.Fatalf("peer message = %#v, want PSK %q", peer, account.PreSharedKey) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "encoding/hex" | ||
| "fmt" | ||
| "strings" | ||
| ) | ||
|
|
||
| // WireguardKeyToHex normalizes a WireGuard key from base64 (panel) or hex (Xray API) to hex. | ||
| func WireguardKeyToHex(key string) (string, error) { | ||
| key = strings.TrimSpace(key) | ||
| if key == "" { | ||
| return "", fmt.Errorf("empty wireguard key") | ||
| } | ||
|
|
||
| if len(key) == 64 { | ||
| if _, err := hex.DecodeString(key); err == nil { | ||
| return key, nil | ||
| } | ||
| } | ||
|
|
||
| raw, err := base64.StdEncoding.DecodeString(key) | ||
| if err != nil { | ||
| return "", fmt.Errorf("invalid wireguard key encoding: %w", err) | ||
| } | ||
| if len(raw) != 32 { | ||
| return "", fmt.Errorf("invalid wireguard key length: %d", len(raw)) | ||
| } | ||
| return hex.EncodeToString(raw), nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Let the installer detect the runner architecture.
If this job runs on ARM64,
--arch 64installs an x86-64 Xray binary. Certificate generation or tests that execute Xray then fail. Remove--arch 64; the installer detects ARM64 when that option is absent. (github.com)🤖 Prompt for AI Agents
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Pin the installer script before executing it.
The Xray
--tagpins the release, notinstall_core.sh. This job executes the script from the mutablemainbranch. A later script change can alter CI execution without a change to this repository. Fetch a reviewed script revision and verify the download before running it. The installer repository also recommends pinning installer revisions. (github.com)🤖 Prompt for AI Agents