Skip to content

Commit

Permalink
Add most of "remote add"
Browse files Browse the repository at this point in the history
Signed-off-by: Stéphane Graber <stgraber@ubuntu.com>
  • Loading branch information
stgraber committed Jul 16, 2014
1 parent 6a2c36a commit 60a1bbb
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 16 deletions.
82 changes: 77 additions & 5 deletions src/lxccmd/lxccmd/commands/remote/__init__.py
Expand Up @@ -22,6 +22,16 @@
# Import everything we need
import gettext

from lxccmd.config import config_has_section, config_list_sections
from lxccmd.exceptions import LXCError
from lxccmd.network import remote_get_fingerprint, remote_get_role, \
remote_add_trusted

try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse

# Setup i18n
_ = gettext.gettext

Expand All @@ -38,21 +48,83 @@ def cli_subparser(sp):

# Service control
sp_add = subparsers.add_parser("add", help=_("Add a remote server"))
sp_add.add_argument("name", metavar="NAME",
help=_("Local name for the remote server"))
sp_add.add_argument("url", metavar="URL",
help=_("URL of the remote server"))
sp_add.add_argument("--skip-fingerprint", action="store_true",
help=_("Don't ask for fingerprint confirmation"))
sp_add.add_argument("--password", type=str, default=None,
help=_("Password to use to setup the trust"))
sp_add.set_defaults(func=cli_add_remote)

sp_list = subparsers.add_parser("list", help=_("List remote servers"))
sp_list.set_defaults(func=cli_list_remote)

sp_remove = subparsers.add_parser("remove",
help=_("Remove a remote server"))
sp_remove.add_argument("name", metavar="NAME",
help=_("Local name of the remote server"))
sp_remove.set_defaults(func=cli_remove_remote)


def cli_add_remote(args):
if config_has_section("remote/%s" % args.name):
raise LXCError(_("Remote '%s' already exists." % args.name))

parsed = urlparse(args.url)

if parsed.scheme not in ("https"):
raise LXCError(_("Invalid URL scheme '%s'.") % parsed.scheme)

host = parsed.hostname
port = parsed.port
if not port:
port = 8443

fingerprint = remote_get_fingerprint(host, port)

if not fingerprint:
raise LXCError(_("Unable to reach remote server."))

if not args.skip_fingerprint:
print(_("Remote server certificate fingerprint is: %s" % fingerprint))
if input(_("Is this correct? (yes/no): ")) != _("yes"):
return

role = remote_get_role(host, port)
if role == "guest":
if args.password:
password = args.passwd
else:
if not args.skip_fingerprint:
print("")
password = input(_("Remote password: "))

res = remote_add_trusted(host, port, password)
if "success" not in res:
raise LXCError(_("Invalid password."))

# remote_add(args.name, args.url, fingerprint)


def cli_list_remote(args):
for entry in config_list_sections():
if not entry.startswith("remote/"):
continue

print(entry.split("remote/", 1)[-1])


assert(sp_add)
assert(sp_list)
assert(sp_remove)
def cli_remove_remote(args):
if not config_has_section("remote/%s" % args.name):
raise LXCError(_("Remote '%s' doesn't exist." % args.name))


# REST functions
def rest_get_remotes():
def rest_list_remote():
return {}


def rest_functions():
return {("trusted", "GET", "/remote"): rest_get_remotes}
return {("trusted", "GET", "/remote"): rest_list_remote}
25 changes: 20 additions & 5 deletions src/lxccmd/lxccmd/commands/server/__init__.py
Expand Up @@ -105,13 +105,18 @@ def handle_one_request(self):
except:
pass

self.send_response(200)
try:
ret = self.server.exported_functions[signature](args=args)
self.send_response(200)
except LXCError as e:
ret = {'error': e}
self.send_response(500)

self.send_header("Content-type:", "application/json")
self.end_headers()
self.wfile.write(
json.dumps(
self.server.exported_functions[signature](args=args),
sort_keys=True, indent=4, separators=(',', ': ')).encode())
json.dumps(ret, sort_keys=True, indent=4,
separators=(',', ': ')).encode())

self.wfile.flush()
except socket.timeout as e:
Expand Down Expand Up @@ -275,7 +280,17 @@ def cli_set(args):

# REST functions
def rest_functions():
return {("guest", "POST", "/server/trust"): rest_trust_add_as_guest}
return {("guest", "POST", "/server/trust"): rest_trust_add_as_guest,
("guest", "GET", "/server/whoami"): rest_whoami_as_guest,
("trusted", "GET", "/server/whoami"): rest_whoami_as_trusted}


def rest_whoami_as_guest(args):
return {'role': "guest"}


def rest_whoami_as_trusted(args):
return {'role': "trusted"}


def rest_trust_add_as_guest(args):
Expand Down
2 changes: 1 addition & 1 deletion src/lxccmd/lxccmd/commands/status/__init__.py
Expand Up @@ -152,7 +152,7 @@ def cli_status(args):
if not args.remote:
status = get_status()
else:
status = secure_remote_call(args.remote, "GET", "/status")
status = secure_remote_call(args.remote, 8443, "GET", "/status")

for entry, entry_dict in sorted(status.items()):
keys.append(entry_dict['description'])
Expand Down
26 changes: 26 additions & 0 deletions src/lxccmd/lxccmd/config.py
Expand Up @@ -55,6 +55,32 @@ def config_get(section, key, default=None, answer_type=str):
return answer_type(value)


def config_has_section(section):
"""
Returns True if a section exists, False otherwise.
"""

config_file = "%s/lxccmd.conf" % get_config_path()

config = ConfigParser()
config.read(config_file)

return config.has_section(section)


def config_list_sections():
"""
Returns a list of all the config sections.
"""

config_file = "%s/lxccmd.conf" % get_config_path()

config = ConfigParser()
config.read(config_file)

return config.sections()


def config_set(section, key, value):
"""
Sets a key in the configuration file.
Expand Down
64 changes: 59 additions & 5 deletions src/lxccmd/lxccmd/network/__init__.py
Expand Up @@ -21,6 +21,7 @@

# Import everything we need
import gettext
import hashlib
import json
import socket
import ssl
Expand All @@ -37,6 +38,50 @@
_ = gettext.gettext


def remote_add_trusted(host, port, password, cert="client"):
"""
Setup a trust relationship with a remote server using a password.
"""

client_crt, client_key, client_capath = get_cert_path(cert)
with open(client_crt, "r") as fd:
cert = fd.read()

return secure_remote_call(host, port, "POST", "/server/trust", None,
{'password': password,
'cert': cert})


def remote_get_fingerprint(host, port):
"""
Connect to the target and retrieve the fingerprint.
"""

try:
return hashlib.sha1(
ssl.PEM_cert_to_DER_cert(
ssl.get_server_certificate(
(host, port)))).hexdigest()
except:
return False


def remote_get_role(host, port):
"""
Return the role (trusted or guest) as seen from the remote server.
"""

try:
ret = secure_remote_call(host, port, "GET", "/server/whoami")
except:
try:
ret = secure_remote_call(host, port, "GET", "/server/whoami", None)
except:
raise LXCError(_("Unable to get current role."))

return ret['role']


def server_is_running():
"""
Attempts to bind the server address to check if it's running.
Expand All @@ -53,21 +98,30 @@ def server_is_running():
return True


def secure_remote_call(target, method, path, role="client", **args):
def secure_remote_call(host, port, method, path, cert="client", data=None):
"""
Call a function using the authenticated REST API.
"""

client_crt, client_key, client_capath = get_cert_path("client")
client_crt = None
client_key = None
client_capath = None
if cert:
client_crt, client_key, client_capath = get_cert_path(cert)

try:
conn = HTTPSConnection(target, 8443,
conn = HTTPSConnection(host, port,
key_file=client_key,
cert_file=client_crt)
conn.request(method, path)
if data:
params = json.dumps(data)
headers = {'Content-Type': "application/json"}
conn.request(method, path, params, headers)
else:
conn.request(method, path)
response = conn.getresponse()
conn.close()
except ssl.SSLError:
except:
raise LXCError(_("Remote function isn't available."))

if response.status != 200:
Expand Down

0 comments on commit 60a1bbb

Please sign in to comment.