Add first-run admin setup and independent password and API token rotation
This commit is contained in:
@@ -35,14 +35,15 @@ v=json.load(sys.stdin)
|
||||
c=http.client.HTTPConnection(v['host'],8110,timeout=5)
|
||||
h={'Content-Type':'application/json','X-Athena-Deck':'1'}
|
||||
if v.get('cookie'):h['Cookie']=v['cookie']
|
||||
if v.get('token'):h['Authorization']='Bearer '+v['token']
|
||||
if v.get('origin'):h['Origin']=v['origin']
|
||||
c.request(v.get('method','GET'),v.get('path','/'),json.dumps(v['body']) if 'body' in v else None,h)
|
||||
r=c.getresponse();b=r.read().decode()
|
||||
print(json.dumps({'status':r.status,'body':b,'cookie':r.getheader('Set-Cookie')}))
|
||||
'''
|
||||
|
||||
def http(host,path='/',method='GET',body=None,cookie=None):
|
||||
value=dict(host=host,path=path,method=method,cookie=cookie)
|
||||
def http(host,path='/',method='GET',body=None,cookie=None,token=None):
|
||||
value=dict(host=host,path=path,method=method,cookie=cookie,token=token)
|
||||
if body is not None:value['body']=body
|
||||
return execute(PEER,HTTP,value)
|
||||
|
||||
@@ -56,7 +57,8 @@ def main():
|
||||
state=tempfile.TemporaryDirectory(prefix='athena-deck-test-')
|
||||
password=secrets.token_urlsafe(32)
|
||||
salt=secrets.token_bytes(16)
|
||||
auth=dict(salt=salt.hex(),hash=hashlib.pbkdf2_hmac('sha256',password.encode(),salt,600000).hex())
|
||||
api_token=secrets.token_urlsafe(32)
|
||||
auth=dict(version=1,password=dict(salt=salt.hex(),hash=hashlib.pbkdf2_hmac('sha256',password.encode(),salt,600000).hex()),revision=secrets.token_hex(16),api_token_hash=hashlib.sha256(api_token.encode()).hexdigest(),password_changed_at=time.time(),token_changed_at=time.time())
|
||||
p=Path(state.name)/'auth.json';p.write_text(json.dumps(auth));p.chmod(0o600)
|
||||
try:
|
||||
run('docker','run','-d','--name',PEER,'--cap-drop','ALL','--cap-add','NET_ADMIN',IMAGE,'sleep','600')
|
||||
@@ -78,6 +80,22 @@ def main():
|
||||
cookie=login['cookie'].split(';')[0]
|
||||
assert http(deckip,'/api/v1/status',cookie=cookie)['status']==200
|
||||
print('PASS real LAN login and authenticated API',flush=True)
|
||||
assert http(deckip,'/api/v1/status',token=api_token)['status']==200
|
||||
assert http(deckip,'/api/v1/network',token=api_token)['status']==401
|
||||
next_token=secrets.token_urlsafe(32)
|
||||
assert http(deckip,'/api/v1/auth/token','POST',{'current_password':password,'new_token':next_token},cookie)['status']==200
|
||||
assert http(deckip,'/api/v1/status',token=api_token)['status']==401
|
||||
assert http(deckip,'/api/v1/status',token=next_token)['status']==200
|
||||
assert http(deckip,'/api/v1/status',cookie=cookie)['status']==200
|
||||
next_password=secrets.token_urlsafe(32)
|
||||
assert http(deckip,'/api/v1/auth/password','POST',{'current_password':password,'new_password':next_password},cookie)['status']==200
|
||||
assert http(deckip,'/api/v1/status',cookie=cookie)['status']==401
|
||||
assert http(deckip,'/api/v1/login','POST',{'password':password})['status']==401
|
||||
password=next_password
|
||||
login=http(deckip,'/api/v1/login','POST',{'password':password})
|
||||
assert login['status']==200
|
||||
cookie=login['cookie'].split(';')[0]
|
||||
print('PASS proxy forwards API token; rotations persist through privileged helper; old credentials rejected',flush=True)
|
||||
# Keys stay in this Python process and stdin pipes; never printed.
|
||||
private_a=run('docker','exec',PEER,'wg','genkey')
|
||||
private_b=run('docker','exec',PEER,'wg','genkey')
|
||||
@@ -132,6 +150,16 @@ cmd('ip','route','add','10.240.77.1/32','dev','peerwg0')
|
||||
except Exception:time.sleep(.5)
|
||||
assert status['mode']=='lan' and not status['pending']
|
||||
print('PASS restart discards unconfirmed trial and restores confirmed mode',flush=True)
|
||||
for _ in range(20):
|
||||
try:
|
||||
restored=http(deckip,'/api/v1/login','POST',{'password':password})
|
||||
if restored['status']==200:break
|
||||
except Exception:pass
|
||||
time.sleep(.5)
|
||||
else:raise RuntimeError('Changed password did not survive container restart')
|
||||
assert http(deckip,'/api/v1/status',token=next_token)['status']==200
|
||||
assert http(deckip,'/api/v1/status',token=api_token)['status']==401
|
||||
print('PASS changed password and token survive container restart',flush=True)
|
||||
after=run('docker','inspect','--format','{{.Id}} {{.State.StartedAt}}',*ids) if ids else ''
|
||||
assert baseline==after
|
||||
print('PASS production container start times unchanged',flush=True)
|
||||
|
||||
Reference in New Issue
Block a user