Replace WireGuard prototype with native host service and scoped access
This commit is contained in:
1 parent
37b5394df1
commit
11f16ef4a3
16 files changed
+667
-136
No files matched your search
@@ -0,0 +1,90 @@
|
||||
"""Explicit Linux/root test: native processes in two disposable network namespaces.
|
||||
No containers, host routes, production ports, keys or existing WireGuard peers used.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0,str(ROOT))
|
||||
A='deck-wg-test-a';B='deck-wg-test-b'
|
||||
def run(*args,data=None):
|
||||
r=subprocess.run(args,input=data,text=True,capture_output=True,timeout=15)
|
||||
if r.returncode:raise RuntimeError('Isolated integration command failed: '+args[0])
|
||||
return r.stdout.strip()
|
||||
def ns(name,*args,data=None):return run('ip','netns','exec',name,*args,data=data)
|
||||
SCRIPT=r'''
|
||||
import json,os,signal,socketserver,threading
|
||||
from pathlib import Path
|
||||
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
|
||||
from network.native import Manager,RPC,Server
|
||||
policy={'lan_address':'10.241.88.1','lan_interface':'testa','gui_port':18108,'ports':[18108,18120]}
|
||||
m=Manager(os.environ['TEST_STATE'],policy)
|
||||
class App(BaseHTTPRequestHandler):
|
||||
def log_message(self,*args):pass
|
||||
def do_GET(self):
|
||||
body=b'{"synthetic":true}';self.send_response(200);self.send_header('Content-Length',str(len(body)));self.end_headers();self.wfile.write(body)
|
||||
class Stream(socketserver.BaseRequestHandler):
|
||||
def handle(self):self.request.sendall(b'synthetic-stream');self.request.close()
|
||||
threading.Thread(target=ThreadingHTTPServer(('127.0.0.1',18108),App).serve_forever,daemon=True).start()
|
||||
threading.Thread(target=socketserver.ThreadingTCPServer(('127.0.0.1',18120),Stream).serve_forever,daemon=True).start()
|
||||
with Server(os.environ['TEST_SOCKET'],RPC) as server:
|
||||
server.manager=m;server.client_uid=0
|
||||
signal.signal(signal.SIGTERM,lambda *_:threading.Thread(target=server.shutdown,daemon=True).start())
|
||||
try:server.serve_forever()
|
||||
finally:
|
||||
m.disconnect()
|
||||
for s in m.listeners:s.shutdown();s.server_close()
|
||||
'''
|
||||
def main():
|
||||
if os.geteuid()!=0:raise SystemExit('Explicit root test on Linux required.')
|
||||
process=None
|
||||
before=run('ip','-4','route','show')
|
||||
with tempfile.TemporaryDirectory(prefix='deck-native-test-') as t:
|
||||
try:
|
||||
for n in (A,B):run('ip','netns','add',n)
|
||||
run('ip','link','add','testa','type','veth','peer','name','testb')
|
||||
run('ip','link','set','testa','netns',A);run('ip','link','set','testb','netns',B)
|
||||
for n,dev,ip in ((A,'testa','10.241.88.1/24'),(B,'testb','10.241.88.2/24')):
|
||||
ns(n,'ip','link','set','lo','up');ns(n,'ip','address','add',ip,'dev',dev);ns(n,'ip','link','set',dev,'up')
|
||||
env=dict(os.environ,PYTHONPATH=str(ROOT),TEST_STATE=t,TEST_SOCKET=t+'/rpc.sock')
|
||||
process=subprocess.Popen(['ip','netns','exec',A,'python3','-c',SCRIPT],env=env,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
|
||||
for _ in range(30):
|
||||
if Path(t+'/rpc.sock').exists():break
|
||||
time.sleep(.1)
|
||||
from network.rpc import request
|
||||
def rpc(action,**values):return request(dict(action=action,**values),path=t+'/rpc.sock')
|
||||
ka=ns(A,'wg','genkey');kb=ns(B,'wg','genkey');pa=ns(A,'wg','pubkey',data=ka+'\n');pb=ns(B,'wg','pubkey',data=kb+'\n')
|
||||
rpc('import',config=f'[Interface]\nPrivateKey = {ka}\nAddress = 10.242.88.1/24, fd42:88::1/64\n[Peer]\nPublicKey = {pb}\nAllowedIPs = 10.242.88.2/32, fd42:88::2/128\nEndpoint = 10.241.88.2:51835\nPersistentKeepalive = 1\n')
|
||||
peer=Path(t)/'peer.conf';peer.write_text(f'[Interface]\nPrivateKey = {kb}\nListenPort = 51835\n[Peer]\nPublicKey = {pa}\nAllowedIPs = 10.242.88.1/32, fd42:88::1/128\nEndpoint = 10.241.88.1:51836\nPersistentKeepalive = 1\n');peer.chmod(0o600)
|
||||
ns(B,'ip','link','add','peerwg0','type','wireguard');ns(B,'wg','setconf','peerwg0',str(peer));peer.unlink()
|
||||
ns(B,'ip','address','add','10.242.88.2/32','dev','peerwg0');ns(B,'ip','link','set','peerwg0','up');ns(B,'ip','route','add','10.242.88.1/32','dev','peerwg0')
|
||||
ns(B,'ip','-6','address','add','fd42:88::2/128','dev','peerwg0');ns(B,'ip','-6','route','add','fd42:88::1/128','dev','peerwg0')
|
||||
rpc('connect');ns(A,'wg','set','wgdeck0','listen-port','51836')
|
||||
for _ in range(40):
|
||||
if rpc('status')['state']=='connected':break
|
||||
time.sleep(.25)
|
||||
else:raise RuntimeError('Synthetic WireGuard handshake missing')
|
||||
def http(host):
|
||||
if ':' in host:host='['+host+']'
|
||||
return int(ns(B,'python3','-c',"import urllib.request,urllib.error;\ntry: print(urllib.request.urlopen('http://"+host+":18108/',timeout=5).status)\nexcept urllib.error.HTTPError as e: print(e.code)"))
|
||||
assert http('10.241.88.1')==200;assert http('10.242.88.1')==403
|
||||
s=rpc('mode',mode='both');assert http('10.242.88.1')==200;assert http('fd42:88::1')==200
|
||||
try:rpc('confirm',trial_id=s['pending']['id'],ingress='tunnel',proxy_token='fake')
|
||||
except ValueError:pass
|
||||
else:raise AssertionError('Forged ingress accepted')
|
||||
token=(Path(t)/'proxy-token').read_text().strip();rpc('confirm',trial_id=s['pending']['id'],ingress='tunnel',proxy_token=token)
|
||||
assert ns(B,'python3','-c',"import socket;s=socket.create_connection(('10.242.88.1',18120),5);print(s.recv(1024).decode())")=='synthetic-stream'
|
||||
s=rpc('mode',mode='tunnel');assert http('10.241.88.1')==403;assert http('10.242.88.1')==200
|
||||
rpc('cancel');assert http('10.241.88.1')==200
|
||||
rpc('disconnect');assert rpc('status')['enabled'] is False
|
||||
assert run('ip','-4','route','show')==before
|
||||
print('PASS native dual-stack handshake/access, LAN/tunnel/both, forged confirmation rejection, streamed API bytes, cancellation, cleanup; host routes unchanged')
|
||||
finally:
|
||||
if process:process.terminate();process.wait(timeout=15)
|
||||
for n in (A,B):subprocess.run(['ip','netns','del',n],capture_output=True)
|
||||
if __name__=='__main__':main()
|
||||
Reference in new issue
Block a user