Skip to content

Commit 2daccb5

Browse files
authored
Merge pull request #2250 from BishopFox/wg/opt-in
Disable wg wrapper on multiplayer by default
2 parents e2a009d + eda51f8 commit 2daccb5

18 files changed

Lines changed: 175 additions & 110 deletions

client/cli/cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func init() {
5252

5353
rootCmd.TraverseChildren = true
5454
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
55-
rootCmd.PersistentFlags().Bool(disableWGFlag, false, "connect to multiplayer directly even if the operator config includes a WireGuard wrapper")
55+
rootCmd.PersistentFlags().Bool(enableWGFlag, false, "connect to multiplayer through the operator config's WireGuard wrapper")
5656

5757
// Create the console client, without any RPC or commands bound to it yet.
5858
// This created before anything so that multiple commands can make use of

client/cli/transport_mode.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ import (
66
)
77

88
const (
9-
disableWGFlag = "disable-wg"
9+
enableWGFlag = "enable-wg"
1010
)
1111

1212
func applyMultiplayerConnectMode(cmd *cobra.Command) error {
1313
if cmd == nil {
14-
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectAuto)
14+
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectDirect)
1515
return nil
1616
}
1717

18-
disableWG, err := cmd.Flags().GetBool(disableWGFlag)
18+
enableWG, err := cmd.Flags().GetBool(enableWGFlag)
1919
if err != nil {
2020
return err
2121
}
2222

23-
mode := transport.MultiplayerConnectAuto
24-
if disableWG {
25-
mode = transport.MultiplayerConnectDisableWG
23+
mode := transport.MultiplayerConnectDirect
24+
if enableWG {
25+
mode = transport.MultiplayerConnectEnableWG
2626
}
2727
transport.SetMultiplayerConnectMode(mode)
2828
return nil

client/transport/connect_mode.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
type MultiplayerConnectMode int
1212

1313
const (
14-
MultiplayerConnectAuto MultiplayerConnectMode = iota
15-
MultiplayerConnectDisableWG
14+
MultiplayerConnectDirect MultiplayerConnectMode = iota
15+
MultiplayerConnectEnableWG
1616
)
1717

1818
type connectionCloser interface {
@@ -21,7 +21,7 @@ type connectionCloser interface {
2121

2222
var (
2323
multiplayerConnectModeMu sync.RWMutex
24-
multiplayerConnectMode = MultiplayerConnectAuto
24+
multiplayerConnectMode = MultiplayerConnectDirect
2525
connClosers sync.Map // *grpc.ClientConn -> connectionCloser
2626
)
2727

client/transport/mtls.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,13 @@ func selectMultiplayerDialStrategy(config *assets.ClientConfig) (multiplayerDial
105105
}
106106

107107
switch getMultiplayerConnectMode() {
108-
case MultiplayerConnectDisableWG:
109-
return multiplayerDialDirect, nil
110-
default:
111-
if config.WG == nil {
112-
return multiplayerDialDirect, nil
113-
}
108+
case MultiplayerConnectEnableWG:
114109
if err := validateWireGuardConfig(config); err != nil {
115110
return multiplayerDialDirect, err
116111
}
117112
return multiplayerDialWireGuard, nil
113+
default:
114+
return multiplayerDialDirect, nil
118115
}
119116
}
120117

client/transport/mtls_test.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func TestSelectMultiplayerDialStrategyLegacyConfigUsesDirectMTLS(t *testing.T) {
14-
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
14+
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
1515

1616
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
1717
if err != nil {
@@ -22,8 +22,34 @@ func TestSelectMultiplayerDialStrategyLegacyConfigUsesDirectMTLS(t *testing.T) {
2222
}
2323
}
2424

25-
func TestSelectMultiplayerDialStrategyRejectsIncompleteWGConfig(t *testing.T) {
26-
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
25+
func TestSelectMultiplayerDialStrategyDefaultIgnoresIncompleteWGConfig(t *testing.T) {
26+
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
27+
28+
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
29+
WG: &assets.ClientWGConfig{
30+
ServerPubKey: "server-pub",
31+
ClientIP: "100.64.0.2",
32+
},
33+
})
34+
if err != nil {
35+
t.Fatalf("select dial strategy: %v", err)
36+
}
37+
if strategy != multiplayerDialDirect {
38+
t.Fatalf("expected direct mTLS strategy, got %v", strategy)
39+
}
40+
}
41+
42+
func TestSelectMultiplayerDialStrategyEnableWGRejectsMissingWGConfig(t *testing.T) {
43+
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
44+
45+
_, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
46+
if !errors.Is(err, ErrMissingWireGuardConfig) {
47+
t.Fatalf("expected missing WG config error, got %v", err)
48+
}
49+
}
50+
51+
func TestSelectMultiplayerDialStrategyEnableWGRejectsIncompleteWGConfig(t *testing.T) {
52+
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
2753

2854
_, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
2955
WG: &assets.ClientWGConfig{
@@ -36,8 +62,8 @@ func TestSelectMultiplayerDialStrategyRejectsIncompleteWGConfig(t *testing.T) {
3662
}
3763
}
3864

39-
func TestSelectMultiplayerDialStrategyUsesWireGuardWhenConfigComplete(t *testing.T) {
40-
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
65+
func TestSelectMultiplayerDialStrategyEnableWGUsesWireGuardWhenConfigComplete(t *testing.T) {
66+
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
4167

4268
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
4369
WG: &assets.ClientWGConfig{
@@ -54,8 +80,8 @@ func TestSelectMultiplayerDialStrategyUsesWireGuardWhenConfigComplete(t *testing
5480
}
5581
}
5682

57-
func TestSelectMultiplayerDialStrategyDisableWGOverridesCompleteConfig(t *testing.T) {
58-
setTestMultiplayerConnectMode(t, MultiplayerConnectDisableWG)
83+
func TestSelectMultiplayerDialStrategyDefaultUsesDirectMTLSEvenWhenWGConfigComplete(t *testing.T) {
84+
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
5985

6086
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
6187
WG: &assets.ClientWGConfig{
@@ -68,7 +94,7 @@ func TestSelectMultiplayerDialStrategyDisableWGOverridesCompleteConfig(t *testin
6894
t.Fatalf("select dial strategy: %v", err)
6995
}
7096
if strategy != multiplayerDialDirect {
71-
t.Fatalf("expected direct strategy when WG is disabled, got %v", strategy)
97+
t.Fatalf("expected direct strategy when WG is not explicitly enabled, got %v", strategy)
7298
}
7399
}
74100

docs/sliver-docs/pages/docs/md/Configuration Files.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ daemon:
1212
host: ""
1313
port: 31337
1414
tailscale: false
15-
disable_wg: false
15+
enable_wg: false
1616
logs:
1717
level: 4
1818
grpc_unary_payloads: false
@@ -54,7 +54,7 @@ cxx: {}
5454
- `host` - Interface to bind the daemon listener to. Empty string means all interfaces.
5555
- `port` - Listen port for the multiplayer listener.
5656
- `tailscale` - Enable Tailscale for daemon listener setup. When enabled, Sliver does not wrap multiplayer in its own WireGuard layer.
57-
- `disable_wg` - Expose multiplayer directly over mTLS instead of the default WireGuard-protected mode.
57+
- `enable_wg` - Wrap multiplayer in WireGuard. If omitted or false, multiplayer is exposed directly over mTLS.
5858
- `logs` - Server logging options.
5959
- `level` - `logrus` level (`0`-`6`, clamped by Sliver). `4` is `INFO`, `5` is `DEBUG`, `6` is `TRACE`.
6060
- `grpc_unary_payloads` - Log gRPC unary payloads.
@@ -159,4 +159,4 @@ The `wg` block is optional:
159159

160160
- When present, `sliver-client` automatically brings up the multiplayer WireGuard wrapper and then dials the in-tunnel gRPC/mTLS service.
161161
- When absent, the client connects directly to the multiplayer mTLS listener.
162-
- Generate a direct-only profile with `new-operator --disable-wg` or `sliver-server operator --disable-wg` when you need compatibility with external gRPC clients that do not implement the multiplayer WireGuard wrapper.
162+
- Generate a WireGuard-enabled profile with `new-operator --enable-wg` or `sliver-server operator --enable-wg` when you want the multiplayer WireGuard wrapper.

docs/sliver-docs/pages/docs/md/Custom Clients.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Current Sliver releases generate multiplayer operator configs with a `wg` block
77
If you are writing a custom client in another language, you have two options:
88

99
- Implement the multiplayer WireGuard wrapper described in [multiplayer mode](/docs?name=Multi-player+Mode), then connect to the in-tunnel gRPC/mTLS service.
10-
- Keep multiplayer in direct mode by starting the listener with `multiplayer --disable-wg` or `sliver-server daemon --disable-wg`, then generate operator profiles with `new-operator --disable-wg` or `sliver-server operator --disable-wg`.
10+
- Keep multiplayer in direct mode by default, or explicitly opt into the wrapper with `multiplayer --enable-wg` or `sliver-server daemon --enable-wg`. Generate matching operator profiles with `new-operator --enable-wg` or `sliver-server operator --enable-wg` when you want the client-side WireGuard wrapper.
1111

1212
Once connected, the client/server API is still gRPC, so any language with gRPC support can in theory be used to create a custom client.
1313

docs/sliver-docs/pages/docs/md/Daemon Mode.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ If `server.yaml` does not exist, Sliver generates it. If a legacy `server.json`
1515
- `-l, --lhost` multiplayer listener host
1616
- `-p, --lport` multiplayer listener port
1717
- `-t, --tailscale` enable Tailscale listener
18-
- `--disable-wg` expose multiplayer directly over mTLS instead of using the default WireGuard wrapper
18+
- `--enable-wg` wrap multiplayer in WireGuard instead of exposing it directly over mTLS
1919
- `-f, --force` force unpack static assets
2020
- For `sliver-server daemon`, `--lhost` and `--lport` override config values. If omitted, Sliver uses `daemon.host` and `daemon.port` from `server.yaml`.
21-
- For normal startup (`sliver-server`) with `daemon_mode: true`, Sliver uses `daemon.tailscale` and `daemon.disable_wg` from `server.yaml`.
22-
- With `--disable-wg` or `daemon.disable_wg: true`, multiplayer falls back to direct TCP mTLS on the configured port.
21+
- For normal startup (`sliver-server`) with `daemon_mode: true`, Sliver uses `daemon.tailscale` and `daemon.enable_wg` from `server.yaml`.
22+
- With `--enable-wg` or `daemon.enable_wg: true`, multiplayer is wrapped in WireGuard. Otherwise it stays on direct TCP mTLS.
2323

2424
### Example Config
2525

@@ -29,7 +29,7 @@ daemon:
2929
host: ""
3030
port: 31337
3131
tailscale: false
32-
disable_wg: false
32+
enable_wg: false
3333
logs:
3434
level: 4
3535
grpc_unary_payloads: false
@@ -49,7 +49,7 @@ Since daemon mode does not provide an interactive server console, generate opera
4949
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfg
5050
```
5151

52-
The `operator` CLI matches the daemon's multiplayer exposure by default. If the daemon is running in direct mode or over Tailscale, the generated config omits the multiplayer `wg` block automatically. You can also force a direct-only profile with `--disable-wg`.
52+
The `operator` CLI generates direct multiplayer profiles by default. Add `--enable-wg` when the daemon is running with the WireGuard wrapper.
5353

5454
### Shutdown Behavior
5555

docs/sliver-docs/pages/docs/md/Multi-player Mode.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Multiplayer mode allows multiple operators to connect to the same Sliver server and collaborate on engagements. The easiest way to set up a server for multiplayer is to use the [Linux install script](/docs?name=Linux+Install+Script), which configures the server as a systemd service. However, any `sliver-server` binary supports multiplayer mode.
22

3-
By default, Sliver now protects the operator-facing multiplayer listener with a dedicated WireGuard wrapper. The gRPC/mTLS authentication stack is still used inside the tunnel, but the server no longer exposes the multiplayer gRPC listener directly unless you explicitly opt out with `--disable-wg`.
3+
Sliver exposes the operator-facing multiplayer listener directly over gRPC/mTLS by default. You can opt into a dedicated WireGuard wrapper with `--enable-wg`; the gRPC/mTLS authentication stack still runs inside that tunnel.
44

55
```
66
┌──────────────────┐ C2
@@ -53,7 +53,7 @@ From the server, start the multiplayer listener and generate an operator config
5353
[*] Saved new client config to: /Users/moloch/Desktop/moloch_example.com.cfg
5454
```
5555

56-
**IMPORTANT:** Before clients can connect to a server you must start a multiplayer listener with the `multiplayer` command. In the default WireGuard-protected mode the outer listener is UDP/31337. If you disable the wrapper, multiplayer is exposed directly on TCP/31337 instead.
56+
**IMPORTANT:** Before clients can connect to a server you must start a multiplayer listener with the `multiplayer` command. By default multiplayer is exposed directly on TCP/31337. If you enable the wrapper, the outer listener becomes UDP/31337 and the gRPC/mTLS service stays inside the tunnel.
5757

5858
You can now give this configuration file `moloch_example.com.cfg` to the operator and they can connect to the server using the `sliver-client` binary. The Sliver client will look for configuration files in `~/.sliver-client/configs/` or you can import configs using the `import` CLI. The configs directory can contain multiple configs for different servers.
5959

@@ -71,11 +71,11 @@ $ ./sliver-client
7171
If you want the old behavior, or you need compatibility with third-party gRPC clients that do not implement the multiplayer WireGuard wrapper, disable it on both the listener and the generated operator profile:
7272

7373
```
74-
[server] sliver > multiplayer --disable-wg
74+
[server] sliver > multiplayer
7575
7676
[*] Multiplayer mode enabled!
7777
78-
[server] sliver > new-operator --name tester --lhost 1.2.3.4 --permissions all --disable-wg
78+
[server] sliver > new-operator --name tester --lhost 1.2.3.4 --permissions all
7979
8080
[*] Generating new client certificate, please wait ...
8181
[*] Saved new client config to: /Users/tester/Desktop/tester_example.com.cfg
@@ -85,21 +85,21 @@ In direct mode:
8585

8686
- Multiplayer is exposed directly over TCP on `--lport` (default `31337`).
8787
- Generated operator configs omit the `wg` block.
88-
- `sliver-client --disable-wg` forces a direct connection even if the config includes a `wg` block.
88+
- `sliver-client --enable-wg` opts into the multiplayer WireGuard wrapper when the operator config includes a `wg` block.
8989

90-
The listener mode and the operator config need to match. A WireGuard-enabled config cannot talk to a direct listener, and a direct-only client cannot talk to the default WireGuard-wrapped listener.
90+
The listener mode, operator config, and client flag need to match. A WireGuard-wrapped listener needs a `wg` block in the operator profile plus `sliver-client --enable-wg`. Direct multiplayer works without the `wg` block and without the flag.
9191

9292
### Server CLI / Daemon Mode Multiplayer
9393

94-
If the server is running in daemon mode, the multiplayer listener is started for you without an interactive console. By default the daemon uses the same WireGuard-protected multiplayer mode described above. You can switch the daemon back to direct exposure with `sliver-server daemon --disable-wg` or `daemon.disable_wg: true` in `server.yaml`.
94+
If the server is running in daemon mode, the multiplayer listener is started for you without an interactive console. By default the daemon uses direct multiplayer mTLS. You can opt into the WireGuard wrapper with `sliver-server daemon --enable-wg` or `daemon.enable_wg: true` in `server.yaml`.
9595

9696
Use the server CLI to generate operator configuration files:
9797

9898
```
9999
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfg
100100
```
101101

102-
When daemon mode is configured for direct multiplayer or Tailscale, the CLI automatically omits the `wg` block by default. You can also force a direct-only profile with `--disable-wg`.
102+
Operator profiles omit the multiplayer `wg` block by default. Add `--enable-wg` when the listener is wrapped in WireGuard.
103103

104104
The installation script places the `sliver-server` binary in `/root` by default.
105105

@@ -116,4 +116,4 @@ $ ./sliver-server
116116
sliver > multiplayer -T
117117
```
118118

119-
You should now see a new Tailscale host named `sliver-server-<machine>`. Use that hostname as `--lhost` when generating operator configs for other hosts on the same tailnet. If you generate the operator config before enabling Tailscale multiplayer, add `--disable-wg` explicitly so the profile does not include the multiplayer `wg` block.
119+
You should now see a new Tailscale host named `sliver-server-<machine>`. Use that hostname as `--lhost` when generating operator configs for other hosts on the same tailnet. Tailscale multiplayer does not use the multiplayer WireGuard wrapper, so operator profiles should be generated without `--enable-wg`.

server/cli/cli.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const (
5252
outputFlagStr = "output"
5353
permissionsFlagStr = "permissions"
5454
tailscaleFlagStr = "tailscale"
55-
disableWGFlagStr = "disable-wg"
55+
enableWGFlagStr = "enable-wg"
5656

5757
// Cert flags
5858
caTypeFlagStr = "type"
@@ -87,7 +87,7 @@ func init() {
8787
operatorCmd.Flags().StringP(saveFlagStr, "s", "", "save file to ...")
8888
operatorCmd.Flags().StringP(outputFlagStr, "o", "file", "output format (file, stdout)")
8989
operatorCmd.Flags().StringSliceP(permissionsFlagStr, "P", []string{}, "grant permissions to the operator profile (all, builder, crackstation)")
90-
operatorCmd.Flags().Bool(disableWGFlagStr, false, "omit WireGuard tunnel settings from the generated operator config")
90+
operatorCmd.Flags().Bool(enableWGFlagStr, false, "include WireGuard tunnel settings in the generated operator config")
9191
rootCmd.AddCommand(operatorCmd)
9292

9393
// Certs
@@ -104,7 +104,7 @@ func init() {
104104
daemonCmd.Flags().Uint16P(lportFlagStr, "p", daemon.BlankPort, "multiplayer listener port")
105105
daemonCmd.Flags().BoolP(forceFlagStr, "f", false, "force unpack and overwrite static assets")
106106
daemonCmd.Flags().BoolP(tailscaleFlagStr, "t", false, "enable tailscale")
107-
daemonCmd.Flags().Bool(disableWGFlagStr, false, "expose multiplayer directly instead of wrapping it in WireGuard")
107+
daemonCmd.Flags().Bool(enableWGFlagStr, false, "wrap the multiplayer listener in WireGuard")
108108
rootCmd.AddCommand(daemonCmd)
109109

110110
// Builder
@@ -153,7 +153,7 @@ var rootCmd = &cobra.Command{
153153
fmt.Printf("[!] %s\n", err)
154154
}
155155
if serverConfig.DaemonMode {
156-
daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale, serverConfig.DaemonConfig.DisableWG)
156+
daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale, serverConfig.DaemonConfig.WireGuardEnabled())
157157
} else {
158158
rcScript, err := clientcli.ReadRCScript(cmd)
159159
if err != nil {

0 commit comments

Comments
 (0)