mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
Comments, docstrings and live test names cited item IDs of a roadmap that is not part of the repository. They now say what is meant, or nothing where the sentence stands without them. Test assertions and behavior are unchanged.
92 lines
3.4 KiB
Python
92 lines
3.4 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 socket
|
|
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.
|
|
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
|
|
address_family = socket.AF_INET6 if ":" in ADDRESS else socket.AF_INET
|
|
|
|
|
|
class MyTCPServerHandler(socketserver.StreamRequestHandler):
|
|
timeout = 30 # seconds; an idle client must not hold a thread forever
|
|
|
|
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")
|
|
# Add the key; keep the ones already there.
|
|
keys_path = '/root/.ssh/authorized_keys'
|
|
keys = gfs.cat(keys_path) if gfs.is_file(keys_path) else ''
|
|
new_key = data['key'].strip()
|
|
if new_key not in keys.splitlines():
|
|
if keys and not keys.endswith('\n'):
|
|
keys += '\n'
|
|
gfs.write(keys_path, keys + new_key + '\n')
|
|
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()
|