mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-10 20:26:48 +00:00
- Hash the root password with a random SHA-512 salt instead of the fixed "$6$kgPoiREy". - Talk to gstfsd through one helper with a 120 s socket timeout; an unreachable daemon or a bad reply is an error message, not a 500. - gstfsd refuses to listen on a non-loopback address unless GSTFSD_ALLOW_REMOTE=1: it is an unauthenticated root service. - gstfsd: encode replies (sendall(str) raised TypeError on Python 3, so the success path never worked) and use octal chmod modes. - README: what gstfsd does and how to allow it on a remote compute.
81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# gstfsd - WebVirtCloud daemon for managing VM's filesystem
|
|
#
|
|
|
|
import ipaddress
|
|
import os
|
|
import socketserver
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
PORT = int(os.environ.get("GSTFSD_PORT", 16510))
|
|
ADDRESS = os.environ.get("GSTFSD_BIND_HOST", "127.0.0.1")
|
|
|
|
|
|
def is_loopback(host):
|
|
try:
|
|
return ipaddress.ip_address(host).is_loopback
|
|
except ValueError:
|
|
return host == "localhost"
|
|
|
|
|
|
# gstfsd is an unauthenticated root service that rewrites any VM's
|
|
# /etc/shadow and authorized_keys, so it only listens on loopback unless
|
|
# explicitly allowed (ROADMAP S-08).
|
|
if not is_loopback(ADDRESS) and os.environ.get("GSTFSD_ALLOW_REMOTE") != "1":
|
|
sys.stderr.write(
|
|
"gstfsd: refusing to listen on %s (unauthenticated root service). "
|
|
"Set GSTFSD_ALLOW_REMOTE=1 to allow it on a trusted network.\n" % ADDRESS
|
|
)
|
|
sys.exit(1)
|
|
|
|
import guestfs # noqa: E402
|
|
|
|
|
|
class MyTCPServer(socketserver.ThreadingTCPServer):
|
|
allow_reuse_address = True
|
|
|
|
|
|
class MyTCPServerHandler(socketserver.BaseRequestHandler):
|
|
def handle(self):
|
|
# recive data
|
|
d = self.request.recv(1024).strip()
|
|
data = json.loads(d)
|
|
|
|
# GuestFS
|
|
gfs = guestfs.GuestFS(python_return_dict=True)
|
|
try:
|
|
gfs.add_domain(data['vname'])
|
|
gfs.launch()
|
|
parts = gfs.list_partitions()
|
|
for part in parts:
|
|
try:
|
|
gfs.mount(part, '/')
|
|
if gfs.is_file('/etc/shadow'):
|
|
if data['action'] == 'password':
|
|
file_shadow = gfs.cat('/etc/shadow')
|
|
new_root_hash = "root:" + data['passwd'] + ":"
|
|
file_shadow_new = re.sub('^root:.*?:', new_root_hash, file_shadow)
|
|
gfs.write('/etc/shadow', file_shadow_new)
|
|
gfs.chmod(0o640, '/etc/shadow')
|
|
self.request.sendall(json.dumps({'return': 'success'}).encode())
|
|
if data['action'] == 'publickey':
|
|
if not gfs.is_dir('/root/.ssh'):
|
|
gfs.mkdir('/root/.ssh')
|
|
gfs.chmod(0o700, "/root/.ssh")
|
|
gfs.write('/root/.ssh/authorized_keys', data['key'])
|
|
gfs.chmod(0o600, '/root/.ssh/authorized_keys')
|
|
self.request.sendall(json.dumps({'return': 'success'}).encode())
|
|
gfs.umount(part)
|
|
except RuntimeError:
|
|
pass
|
|
gfs.shutdown()
|
|
gfs.close()
|
|
except Exception as err:
|
|
self.request.sendall(bytes(json.dumps({'return': 'error', 'message': str(err)}).encode()))
|
|
|
|
|
|
server = MyTCPServer((ADDRESS, PORT), MyTCPServerHandler)
|
|
server.serve_forever()
|