Skip to content

Commit

Permalink
wg-mux-*: add note on --auth-secret purpose
Browse files Browse the repository at this point in the history
  • Loading branch information
mk-fg committed Feb 23, 2019
1 parent 097902e commit 018dc20
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 21 deletions.
11 changes: 9 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -845,15 +845,22 @@ via --ident-\* string (derived from /etc/machine-id by default), and both
client/server need to use same -s/--auth-secret to create/validate MACs in each
packet.

Note that all that --auth-secret is used for is literally handing-out sequential
numbers, and isn't expected to be strong protection against anything, unlike ssh
auth that should come after that.

wg-mux-\*
'''''''''

Same thing as ssh-reverse-mux-\* scripts above, but for negotiating WireGuard
tunnels, with persistent host tunnel IPs tracked via --ident-\* strings with
simple auth via MACs on UDP packets derived from symmetric -s/--auth-secret.

Host identity, wg port, public keys and tunnel IPs are authenticated,
but are sent in the clear otherwise.
Client identity, wg port, public key and tunnel IPs are sent in the clear with
relatively weak authentication (hmac of -s/--auth-secret string), but wg server
is also authenticated by pre-shared public key (and --wg-psk, if specified).

Such setup is roughly equivalent to a weak-password-protected public network.

Runs "wg set" commands to update configuration, which need privileges, but can
be wrapped in sudo or suid/caps stuff via --wg-cmd to avoid root in the rest of
Expand Down
17 changes: 7 additions & 10 deletions wg-mux-client
Original file line number Diff line number Diff line change
Expand Up @@ -210,18 +210,18 @@ def build_req(secret, ident, wg_key_pk):
def parse_res(secret, ident, res):
if not res: return
try:
fmt = 'BBHB{}s{}s32s'.format(res[0], {4: 4, 6: 16}[res[1]])
fmt = 'BBHB{}s{}s'.format(res[0], {4: 4, 6: 16}[res[1]])
( ident_len, ip_len, wg_port, wg_mask, ident_buff,
wg_addr, wg_pk, salt, mac ) = struct.unpack(fmt + '8s32s', res)
wg_addr, salt, mac ) = struct.unpack(fmt + '8s32s', res)
mac_chk = hashlib.blake2s(
res[:struct.calcsize(fmt)], key=to_bytes(secret), salt=salt ).digest()
if not secrets.compare_digest(mac, mac_chk): raise AuthError('MAC mismatch')
except (KeyError, struct.error, AuthError) as err:
log.debug('Failed to parse/auth response value: {}', err)
return
wg_addr, wg_pk = ipaddress.ip_address(wg_addr), b64_encode(wg_pk)
wg_addr = ipaddress.ip_address(wg_addr)
wg_net = f'{wg_addr}/{wg_mask}'
return wg_pk, wg_addr, wg_net, wg_port
return wg_addr, wg_net, wg_port


async def mux_negotiate(
Expand Down Expand Up @@ -266,6 +266,7 @@ def main(args=None, conf=None):
group.add_argument('host',
help='Host or address (to be resolved via gai) or a host[:port] spec.'
' "port" will be used for -m/--mux-port option, if specified here.')
group.add_argument('pubkey', help='Base64-encoded WireGuard server public key.')
group.add_argument('-m', '--mux-port',
default=conf.mux_port, type=int, metavar='port',
help='Remote UDP port on which corresponding'
Expand Down Expand Up @@ -297,10 +298,6 @@ def main(args=None, conf=None):
group = parser.add_argument_group('WireGuard options')
group.add_argument('--wg-iface', metavar='iface', default=conf.wg_iface,
help='WireGuard interface name to configure. Default: %(default)s')
group.add_argument('--wg-pk', metavar='base64-pubkey',
help='WireGuard server public key.'
' Be sure to specify this if it is important to authenticate server.'
' Default is to use one returned by mux-server.')
group.add_argument('--wg-port', type=int, metavar='port',
help='Remote WireGuard port to use. Default is to use one returned by mux-server.')
group.add_argument('--wg-psk', metavar='file',
Expand Down Expand Up @@ -371,6 +368,7 @@ def main(args=None, conf=None):

wg_cmd = opts.wg_cmd.split()
wg_addr_net = ipaddress.ip_network(opts.wg_net)
wg_pk = opts.pubkey

wg_key = derive_wg_key(
pl.Path(conf.key_source_ssh).expanduser(),
Expand All @@ -386,14 +384,13 @@ def main(args=None, conf=None):
for sig in 'INT TERM'.split():
loop.add_signal_handler(getattr(signal, f'SIG{sig}'), muxer.cancel)
try:
wg_pk, wg_addr, wg_mask, wg_port = loop.run_until_complete(
wg_addr, wg_mask, wg_port = loop.run_until_complete(
aio_wait_or_cancel(loop, muxer, opts.timeout) )
except (asyncio.CancelledError, asyncio.TimeoutError) as err:
log.debug('mux_negotiate cancelled: {}', err_fmt(err))
return

if opts.wg_port: wg_port = opts.wg_port
if opts.wg_pk: wg_pk = opts.wg_pk
if not wg_addr_net and not wg_net:
print( 'ERROR: mux-server returned address'
' without network, and no --wg-net is specified', file=sys.stderr )
Expand Down
15 changes: 6 additions & 9 deletions wg-mux-server
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ class WGMuxConfig:
mux_port = 8739
mux_timeout = 5.0
wg_iface = 'wg'
wg_pk = bytes(32)
wg_port = 2200
wg_net = '10.215.72.0/24'
n_skip = 1 # to reserve e.g. 10.215.72.1/24 for server
Expand Down Expand Up @@ -136,11 +135,11 @@ def parse_req(secret, req):
return
return b64_encode(ident), b64_encode(wg_pk_peer)

def build_res(secret, ident, wg_addr, wg_net, wg_pk, wg_port):
def build_res(secret, ident, wg_addr, wg_net, wg_port):
ident_buff = to_bytes(ident)
salt, payload = os.urandom(8), bin_pack(
'>BBHBzzz', len(ident_buff), wg_addr.version,
wg_port, wg_net.prefixlen, ident_buff, wg_addr.packed, wg_pk )
'>BBHBzz', len(ident_buff), wg_addr.version,
wg_port, wg_net.prefixlen, ident_buff, wg_addr.packed )
mac = hashlib.blake2s(payload, key=to_bytes(secret), salt=salt).digest()
return payload + salt + mac

Expand Down Expand Up @@ -170,8 +169,8 @@ async def mux_listen( loop, secret, ident_db,
await responses[ident]
peer_info = init_peer(ident, wg_pk_peer, req_addr)
if not peer_info: continue
wg_addr, wg_net, wg_pk, wg_port = peer_info
response = build_res(secret, ident, wg_addr, wg_net, wg_pk, wg_port)
wg_addr, wg_net, wg_port = peer_info
response = build_res(secret, ident, wg_addr, wg_net, wg_port)
responses[ident] = loop.create_task(
mux_send(loop, transport, response, req_addr, delays) )
finally:
Expand Down Expand Up @@ -213,8 +212,6 @@ def main(args=None, conf=None):
group = parser.add_argument_group('WireGuard options')
group.add_argument('--wg-iface', metavar='iface', default=conf.wg_iface,
help='WireGuard interface name to configure. Default: %(default)s')
group.add_argument('--wg-pk', metavar='base64-pubkey', default=b64_encode(conf.wg_pk),
help='WireGuard server public key. Empty one will be sent, if not specified.')
group.add_argument('--wg-port', type=int, metavar='port', default=conf.wg_port,
help='WireGuard endpoint port to send to mux-clients. Default: %(default)s')
group.add_argument('--wg-psk', metavar='file',
Expand Down Expand Up @@ -313,7 +310,7 @@ def main(args=None, conf=None):
'peer', wg_pk_peer, 'remove' ], stderr=sp.DEVNULL)
sp.run(cmd, check=True)
else: log.debug('Config for peer: {}', ' '.join(cmd))
return wg_addr, wg_net, b64_decode(opts.wg_pk), opts.wg_port
return wg_addr, wg_net, opts.wg_port

retry_delays = retries_within_timeout(opts.attempts, opts.timeout)
with contextlib.closing(asyncio.get_event_loop()) as loop:
Expand Down

0 comments on commit 018dc20

Please sign in to comment.