From: Ondrej Lichtner olichtne@redhat.com
The cryptography library is imported only when authentication methods that need it are requested. If the library is missing, the user will get an error message explaining the problem. It's a pretty hackish way of doing the imports but I'm not aware of a better solution... I'm open to suggestions if there are any.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/SecureSocket.py | 72 ++++++++++++++++++++++++++++++++++------- lnst/Controller/CtlSecSocket.py | 40 +++++++++++++++++++---- lnst/Slave/SlaveSecSocket.py | 42 ++++++++++++++++++++---- 3 files changed, 131 insertions(+), 23 deletions(-)
diff --git a/lnst/Common/SecureSocket.py b/lnst/Common/SecureSocket.py index d319c54..cd149c2 100644 --- a/lnst/Common/SecureSocket.py +++ b/lnst/Common/SecureSocket.py @@ -19,17 +19,6 @@ import os import cPickle import hashlib import hmac -import cryptography.exceptions -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.asymmetric import padding, ec -from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey -from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey -from cryptography.hazmat.primitives.asymmetric.dsa import DSAPrivateKey -from cryptography.hazmat.primitives.asymmetric.dsa import DSAPublicKey -from cryptography.hazmat.backends import default_backend
DH_GROUP = {"p": int("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"\ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"\ @@ -75,6 +64,58 @@ if SRP_GROUP["p"].bit_length()%8: class SecSocketException(Exception): pass
+cryptography = None +hashes = None +Cipher = None +algorithms = None +modes = None +padding = None +ec = None +EllipticCurvePrivateKey = None +EllipticCurvePublicKey = None +RSAPrivateKey = None +RSAPublicKey = None +DSAPrivateKey = None +DSAPublicKey = None +default_backend = None +cryptography_imported = False +def cryptography_imports(): + global cryptography_imported + if cryptography_imported: + return + + global cryptography + global hashes + global Cipher + global algorithms + global modes + global padding + global ec + global EllipticCurvePrivateKey + global EllipticCurvePublicKey + global RSAPrivateKey + global RSAPublicKey + global DSAPrivateKey + global DSAPublicKey + global default_backend + + try: + import cryptography.exceptions + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.primitives.asymmetric import padding, ec + from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey + from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey + from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey + from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey + from cryptography.hazmat.primitives.asymmetric.dsa import DSAPrivateKey + from cryptography.hazmat.primitives.asymmetric.dsa import DSAPublicKey + from cryptography.hazmat.backends import default_backend + cryptography_imported = True + except ImportError: + raise SecSocketException("Library 'cryptography' missing "\ + "can't establish secure channel.") + class SecureSocket(object): def __init__(self, soc): self._role = None @@ -112,6 +153,7 @@ class SecureSocket(object): def _add_mac_sign(self, data): if not self._current_write_spec["mac_key"]: return data + cryptography_imports()
msg = str(self._current_write_spec["seq_num"]) + str(len(data)) + data signature = hmac.new(self._current_write_spec["mac_key"], @@ -124,6 +166,7 @@ class SecureSocket(object): def _del_mac_sign(self, signed_data): if not self._current_read_spec["mac_key"]: return signed_data + cryptography_imports()
signed_msg = cPickle.loads(signed_data) data = signed_msg["data"] @@ -140,6 +183,7 @@ class SecureSocket(object): def _add_padding(self, data): if not self._current_write_spec["enc_key"]: return data + cryptography_imports()
block_size = algorithms.AES.block_size/8 pad_length = block_size - (len(data) % block_size) @@ -152,6 +196,7 @@ class SecureSocket(object): def _del_padding(self, data): if not self._current_read_spec["enc_key"]: return data + cryptography_imports()
pad_length = int(data[-1].encode("hex"), 16) for char in data[-pad_length]: @@ -163,6 +208,7 @@ class SecureSocket(object): def _add_encrypt(self, data): if not self._current_write_spec["enc_key"]: return data + cryptography_imports()
iv = os.urandom(algorithms.AES.block_size/8) mode = modes.CBC(iv) @@ -180,6 +226,7 @@ class SecureSocket(object): def _del_encrypt(self, data): if not self._current_read_spec["enc_key"]: return data + cryptography_imports()
encrypted_msg = cPickle.loads(data) encrypted_data = encrypted_msg["enc_data"] @@ -313,6 +360,7 @@ class SecureSocket(object): server_spec = self._next_read_spec else: raise SecSocketException("Socket without a role!") + cryptography_imports()
aes_keysize = max(algorithms.AES.key_sizes)/8 mac_keysize = hashlib.sha256().block_size @@ -334,6 +382,7 @@ class SecureSocket(object): return
def _sign_data(self, data, privkey): + cryptography_imports() if isinstance(privkey, DSAPrivateKey): signer = privkey.signer(hashes.SHA256()) elif isinstance(privkey, RSAPrivateKey): @@ -349,6 +398,7 @@ class SecureSocket(object): return signer.finalize()
def _verify_signature(self, pubkey, data, signature): + cryptography_imports() if isinstance(pubkey, DSAPublicKey): verifier = pubkey.verifier(signature, hashes.SHA256()) elif isinstance(pubkey, RSAPublicKey): diff --git a/lnst/Controller/CtlSecSocket.py b/lnst/Controller/CtlSecSocket.py index b4c857a..1f16055 100644 --- a/lnst/Controller/CtlSecSocket.py +++ b/lnst/Controller/CtlSecSocket.py @@ -19,13 +19,37 @@ from lnst.Common.SecureSocket import SecureSocket from lnst.Common.SecureSocket import DH_GROUP, SRP_GROUP from lnst.Common.SecureSocket import SecSocketException from lnst.Common.Config import lnst_config -from cryptography.hazmat.primitives import serialization as ser -from cryptography.hazmat.primitives.serialization import load_pem_private_key -from cryptography.hazmat.primitives.serialization import load_pem_public_key -from cryptography.hazmat.primitives.serialization import load_ssh_public_key -from cryptography.hazmat.backends import default_backend
-backend = default_backend() +ser = None +load_pem_private_key = None +load_pem_public_key = None +load_ssh_public_key = None +backend = None +cryptography_imported = False +def cryptography_imports(): + global cryptography_imported + if cryptography_imported: + return + + global ser + global load_pem_private_key + global load_pem_public_key + global load_ssh_public_key + global backend + + try: + import cryptography + import cryptography.hazmat.primitives.serialization as ser + from cryptography.hazmat.primitives.serialization import load_pem_private_key + from cryptography.hazmat.primitives.serialization import load_pem_public_key + from cryptography.hazmat.primitives.serialization import load_ssh_public_key + from cryptography.hazmat.backends import default_backend + except ImportError: + raise SecSocketException("Library 'cryptography' missing "\ + "can't establish secure channel.") + + backend = default_backend() + cryptography_imported = True
class CtlSecSocket(SecureSocket): def __init__(self, soc): @@ -57,10 +81,13 @@ class CtlSecSocket(SecureSocket): logging.warning(" NO AUTHENTICATION IN PLACE") logging.warning("SECURE CHANNEL IS VULNERABLE TO MIM ATTACKS") logging.warning("===========================================") + cryptography_imports() self._dh_handshake() elif sec_params["auth_type"] == "ssh": + cryptography_imports() self._ssh_handshake() elif sec_params["auth_type"] == "pubkey": + cryptography_imports() ctl_identity = sec_params["identity"] ctl_key_path = sec_params["privkey"] try: @@ -81,6 +108,7 @@ class CtlSecSocket(SecureSocket):
self._pubkey_handshake(ctl_identity, ctl_key, srv_key) elif sec_params["auth_type"] == "password": + cryptography_imports() self._passwd_handshake(sec_params["auth_passwd"]) else: raise SecSocketException("Unknown authentication method.") diff --git a/lnst/Slave/SlaveSecSocket.py b/lnst/Slave/SlaveSecSocket.py index 9e3cd92..8810bac 100644 --- a/lnst/Slave/SlaveSecSocket.py +++ b/lnst/Slave/SlaveSecSocket.py @@ -19,13 +19,39 @@ import logging from lnst.Common.SecureSocket import SecureSocket from lnst.Common.SecureSocket import DH_GROUP, SRP_GROUP from lnst.Common.SecureSocket import SecSocketException -from cryptography.hazmat.primitives import serialization as ser -from cryptography.hazmat.primitives.serialization import load_pem_private_key -from cryptography.hazmat.primitives.serialization import load_pem_public_key -from cryptography.hazmat.primitives.serialization import load_ssh_public_key -from cryptography.hazmat.backends import default_backend
-backend = default_backend() +ser = None +load_pem_private_key = None +load_pem_public_key = None +load_ssh_public_key = None +backend = None +cryptography_imported = False +def cryptography_imports(): + global cryptography_imported + if cryptography_imported: + return + + global ser + global load_pem_private_key + global load_pem_public_key + global load_ssh_public_key + global backend + + try: + import cryptography + import cryptography.hazmat.primitives.serialization as ser + from cryptography.hazmat.primitives.serialization import load_pem_private_key + from cryptography.hazmat.primitives.serialization import load_pem_public_key + from cryptography.hazmat.primitives.serialization import load_ssh_public_key + from cryptography.hazmat.backends import default_backend + except ImportError: + logging.error("Library 'cryptography' missing "\ + "can't establish secure channel.") + raise SecSocketException("Library 'cryptography' missing "\ + "can't establish secure channel.") + + backend = default_backend() + cryptography_imported = True
class SlaveSecSocket(SecureSocket): def __init__(self, soc): @@ -55,10 +81,13 @@ class SlaveSecSocket(SecureSocket): logging.warning(" NO AUTHENTICATION IN PLACE") logging.warning("SECURE CHANNEL IS VULNERABLE TO MIM ATTACKS") logging.warning("===========================================") + cryptography_imports() self._dh_handshake() elif sec_params["auth_types"] == "ssh": + cryptography_imports() self._ssh_handshake() elif sec_params["auth_types"] == "pubkey": + cryptography_imports() srv_key = None with open(sec_params["privkey"], 'r') as f: srv_key = load_pem_private_key(f.read(), None, backend) @@ -73,6 +102,7 @@ class SlaveSecSocket(SecureSocket):
self._pubkey_handshake(srv_key, ctl_pubkeys) elif sec_params["auth_types"] == "password": + cryptography_imports() self._passwd_handshake(sec_params["auth_password"]) else: raise SecSocketException("Unknown authentication method.")
From: Ondrej Lichtner olichtne@redhat.com
This updates the save_machine_config method to store the security information of the machine into the temporary file. This information is later used to connect to the machine during the deconfiguration. This can probably be done in a more sensible way, but that would probably require reimplementation of these two methods to work in a different way. To add at least some form of security I made sure that the temporary file only has rw- permissions for the owner.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Machine.py | 3 +++ lnst/Controller/NetTestController.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 4112bd0..5d8db7d 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -512,6 +512,9 @@ class Machine(object): def wait_interface_init(self): return self._rpc_call("wait_interface_init")
+ def get_security(self): + return self._security + class Interface(object): """ Abstraction of a test network interface on a slave machine
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index e1b2054..6484c99 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -28,6 +28,8 @@ from lnst.Controller.RecipeParser import RecipeParser, RecipeError from lnst.Controller.SlavePool import SlavePool from lnst.Controller.Machine import MachineError, VirtualInterface from lnst.Controller.Machine import StaticInterface +from lnst.Controller.CtlSecSocket import CtlSecSocket +from lnst.Common.SecureSocket import SecSocketException from lnst.Common.ConnectionHandler import send_data, recv_data from lnst.Common.ConnectionHandler import ConnectionHandler from lnst.Common.Config import lnst_config @@ -548,6 +550,8 @@ class NetTestController: machine["libvirt_dom"] = m.get_libvirt_domain() machine["interfaces"] = []
+ machine["security"] = m.get_security() + for i in m._interfaces: if isinstance(i, VirtualInterface): hwaddr = i.get_orig_hwaddr() @@ -559,6 +563,7 @@ class NetTestController: bridges.append(bridge.get_name())
with open("/tmp/.lnst_machine_conf", "wb") as f: + os.fchmod(f.fileno(), 0o600) pickled_data = cPickle.dump(config_data, f)
@classmethod @@ -578,16 +583,23 @@ class NetTestController: for hostname, machine in cfg["machines"].iteritems(): port = lnst_config.get_option("environment", "rpcport") if test_tcp_connection(hostname, port): - rpc_con = socket.create_connection((hostname, port)) + s = socket.create_connection((hostname, port)) + rpc_con = CtlSecSocket(s) + try: + rpc_con.handshake(machine["security"]) + except SecSocketException: + logging.error("Failed authentication for machine %s" %\ + hostname) + continue
rpc_msg= {"type": "command", "method_name": "machine_cleanup", "args": []}
logging.debug("Calling cleanup on slave '%s'" % hostname) - send_data(rpc_con, rpc_msg) + rpc_con.send_msg(rpc_msg) while True: - msg = recv_data(rpc_con) + msg = rpc_con.recv_msg() if msg['type'] == 'result': break rpc_con.close()
From: Ondrej Lichtner olichtne@redhat.com
This makes the Wizard use the CtlSecureSocket instead of the basic socket. In case of noninteractive mode it is automatically assumed that the communication between Slave and the wizard is not secured - it uses the "none" authentication method.
In case of interactive mode, the user will be asked to provide security parameters that will be used for the handshake. These will also be included in the resulting slave machine description XML file.
If the connection fails during the handshake the user will be notified of this.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Wizard.py | 101 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 7 deletions(-)
diff --git a/lnst/Controller/Wizard.py b/lnst/Controller/Wizard.py index 2f99a25..fca572a 100644 --- a/lnst/Controller/Wizard.py +++ b/lnst/Controller/Wizard.py @@ -21,6 +21,8 @@ import os from lnst.Common.Utils import mkdir_p, check_process_running from lnst.Common.Config import DefaultRPCPort from lnst.Common.ConnectionHandler import send_data, recv_data +from lnst.Controller.CtlSecSocket import CtlSecSocket +from lnst.Common.SecureSocket import SecSocketException from xml.dom.minidom import getDOMImplementation from lxml import etree
@@ -44,7 +46,8 @@ class Wizard: if hostname == -1: continue
- sock = self._get_connection(hostname, port) + sec_params = {"auth_type": "none"} + sock = self._get_connection(hostname, port, sec_params)
if sock is None: continue @@ -66,8 +69,9 @@ class Wizard: while True: hostname = self._query_hostname() port = self._query_port() + sec_params = self._query_sec_params(hostname)
- sock = self._get_connection(hostname, port) + sock = self._get_connection(hostname, port, sec_params) if sock is None: if self._query_continuation(): continue @@ -85,7 +89,7 @@ class Wizard: self._create_xml(machine_interfaces=machine_interfaces, hostname=hostname, pool_dir=pool_dir, filename=filename, port=port, - mode="interactive") + mode="interactive", sec_params=sec_params)
if self._query_continuation(): continue @@ -131,7 +135,8 @@ class Wizard: sys.stderr.write("Skipping host '%s'\n" % host) continue
- sock = self._get_connection(hostname, port) + sec_params = {"auth_type": "none"} + sock = self._get_connection(hostname, port, sec_params) if sock is None: sys.stderr.write("Skipping host '%s'\n" % host) continue @@ -271,7 +276,7 @@ class Wizard:
def _create_xml(self, machine_interfaces=None, hostname=None, pool_dir=None, filename=None, mode=None, - port=None, libvirt_domain=None): + port=None, libvirt_domain=None, sec_params=None): """ Creates slave machine XML file @param machine_interfaces Dictionary with machine's interfaces @param hostname Hostname of the machine @@ -328,13 +333,36 @@ class Wizard: "'%s' will be created\n" % filename) return
+ if sec_params: + security_el = doc.createElement("security") + top_el.appendChild(security_el) + + auth_type_el = doc.createElement("auth_type") + security_el.appendChild(auth_type_el) + + auth_type_text = doc.createTextNode(sec_params["auth_type"]) + auth_type_el.appendChild(auth_type_text) + + if sec_params["auth_type"] is "password": + password_el = doc.createElement("auth_passwd") + security_el.appendChild(password_el) + + password_text = doc.createTextNode(sec_params["auth_password"]) + password_el.appendChild(password_text) + elif sec_params["auth_type"] is "pubkey": + pubkey_el = doc.createElement("pubkey_path") + security_el.appendChild(pubkey_el) + + pubkey_text = doc.createTextNode(sec_params["srv_pubkey_path"]) + pubkey_el.appendChild(pubkey_text) + if self._write_to_file(pool_dir, filename, doc): print("File '%s/%s' successfuly created." % (pool_dir, filename)) else: sys.stderr.write("File '%s/%s' could not be opened " "or data written.\n" % (pool_dir, filename))
- def _get_connection(self, hostname, port): + def _get_connection(self, hostname, port, sec_params): """ Connects to machine @param hostname Hostname of the machine @param port Port of the machine @@ -342,7 +370,14 @@ class Wizard: """ try: sock = socket.create_connection((hostname, port)) - return sock + ret = CtlSecSocket(sock) + ret.handshake(sec_params) + return ret + except SecSocketException: + sys.stderr.write("Couldn't connect to host %s:%s, because "\ + "security negotiation failed.\n" % + (hostname, port)) + return None except socket.error: sys.stderr.write("Connection to remote host '%s:%s' failed\n" % (hostname, port)) @@ -495,6 +530,58 @@ class Wizard: except: sys.stderr.write("Invalid port entered\n")
+ def _query_sec_params(self, hostname): + """ Queries user for security parameters of the connection + @return Dictionary with the security parameters + """ + while True: + auth_type = raw_input("Enter authentication type (default: none): ") + if auth_type == "": + auth_type = "none" + elif auth_type not in ["none", "no-auth", "password", + "pubkey", "ssh"]: + sys.stderr.write("Invalid authentication type.") + continue + break + if auth_type == "none": + return {"auth_type": "none"} + elif auth_type == "no-auth": + return {"auth_type": "no-auth"} + elif auth_type == "ssh": + return {"auth_type": "ssh"} + elif auth_type == "password": + while True: + password = raw_input("Enter password: ") + if password == "": + sys.stderr.write("Invalid password.") + continue + break + return {"auth_type": "password", + "auth_passwd": password} + elif auth_type == "pubkey": + while True: + identity = raw_input("Enter identity: ") + if identity == "": + sys.stderr.write("Invalid identity.") + continue + break + while True: + privkey = raw_input("Enter path to Ctl private key: ") + if privkey == "" or os.path.isfile(privkey): + sys.stderr.write("Invalid path to private key.") + continue + break + while True: + srv_pubkey_path = raw_input("Enter path to Slave public key: ") + if srv_pubkey_path == "" or os.path.isfile(srv_pubkey_path): + sys.stderr.write("Invalid path to public key.") + continue + break + return {"auth_type": "pubkey", + "identity": identity, + "privkey": privkey, + "srv_pubkey_path": srv_pubkey_path} + def _write_to_file(self, pool_dir, filename, doc): """ Writes contents of XML to a file @param pool_dir Path to directory where the file will be created
Mon, Feb 29, 2016 at 05:17:27PM CET, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
This makes the Wizard use the CtlSecureSocket instead of the basic socket. In case of noninteractive mode it is automatically assumed that the communication between Slave and the wizard is not secured - it uses the "none" authentication method.
In case of interactive mode, the user will be asked to provide security parameters that will be used for the handshake. These will also be included in the resulting slave machine description XML file.
If the connection fails during the handshake the user will be notified of this.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com
Two issues so far for wizard.
When I do ./lnst-pool-wizard -v (interactive virtual mode), the wizard won't ask for security parameters.
Second, I got traceback when I specified 'ssh' auth for one of the slaves:
$ ./lnst-pool-wizard Enter path to a pool directory (default: '/home/igyn/.lnst/pool/'): /home/igyn/.lnst/pool-secure-2 Path '/home/igyn/.lnst/pool-secure-2' does not exist Create dir '/home/igyn/.lnst/pool-secure-2'? [Y/n]: y Dir '/home/igyn/.lnst/pool-secure-2' has been created Enter hostname: lnst1 Enter port (default: 9999): Enter authentication type (default: none): ssh Traceback (most recent call last): File "./lnst-pool-wizard", line 92, in <module> main() File "./lnst-pool-wizard", line 86, in main wizard.interactive(hostlist, pool_dir) File "/home/igyn/tmp/lnst/lnst/Controller/Wizard.py", line 74, in interactive sock = self._get_connection(hostname, port, sec_params) File "/home/igyn/tmp/lnst/lnst/Controller/Wizard.py", line 374, in _get_connection ret.handshake(sec_params) File "/home/igyn/tmp/lnst/lnst/Controller/CtlSecSocket.py", line 62, in handshake self._ssh_handshake() File "/home/igyn/tmp/lnst/lnst/Controller/CtlSecSocket.py", line 161, in _ssh_handshake ctl_ssh_key = load_pem_private_key(f.read(), None, backend) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 20, in load_pem_private_key return backend.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/multibackend.py", line 276, in load_pem_private_key return b.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 694, in load_pem_private_key password, File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 863, in _load_key raise password_func.exception TypeError: Password was not given but private key is encrypted.
On Tue, Mar 01, 2016 at 11:04:33AM +0100, Jan Tluka wrote:
Mon, Feb 29, 2016 at 05:17:27PM CET, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
This makes the Wizard use the CtlSecureSocket instead of the basic socket. In case of noninteractive mode it is automatically assumed that the communication between Slave and the wizard is not secured - it uses the "none" authentication method.
In case of interactive mode, the user will be asked to provide security parameters that will be used for the handshake. These will also be included in the resulting slave machine description XML file.
If the connection fails during the handshake the user will be notified of this.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com
Two issues so far for wizard.
When I do ./lnst-pool-wizard -v (interactive virtual mode), the wizard won't ask for security parameters.
That's because the Wizard doesn't connect to the virt machines... so it isn't opening a socket. If we want, we can add the security parameters query so that we can output it into the XML file, but they won't be "tested" since no connection will be created.
Second, I got traceback when I specified 'ssh' auth for one of the slaves:
$ ./lnst-pool-wizard Enter path to a pool directory (default: '/home/igyn/.lnst/pool/'): /home/igyn/.lnst/pool-secure-2 Path '/home/igyn/.lnst/pool-secure-2' does not exist Create dir '/home/igyn/.lnst/pool-secure-2'? [Y/n]: y Dir '/home/igyn/.lnst/pool-secure-2' has been created Enter hostname: lnst1 Enter port (default: 9999): Enter authentication type (default: none): ssh Traceback (most recent call last): File "./lnst-pool-wizard", line 92, in <module> main() File "./lnst-pool-wizard", line 86, in main wizard.interactive(hostlist, pool_dir) File "/home/igyn/tmp/lnst/lnst/Controller/Wizard.py", line 74, in interactive sock = self._get_connection(hostname, port, sec_params) File "/home/igyn/tmp/lnst/lnst/Controller/Wizard.py", line 374, in _get_connection ret.handshake(sec_params) File "/home/igyn/tmp/lnst/lnst/Controller/CtlSecSocket.py", line 62, in handshake self._ssh_handshake() File "/home/igyn/tmp/lnst/lnst/Controller/CtlSecSocket.py", line 161, in _ssh_handshake ctl_ssh_key = load_pem_private_key(f.read(), None, backend) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 20, in load_pem_private_key return backend.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/multibackend.py", line 276, in load_pem_private_key return b.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 694, in load_pem_private_key password, File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 863, in _load_key raise password_func.exception TypeError: Password was not given but private key is encrypted.
Right... the SecSocket classes don't support password encrypted keys... if they did, you'd have to include the password in the lnst-ctl.conf file which I don't think anyone will want to do... What are your thoughts on this?
If it's a different problem (your ssh key isn't encrypted) then I'll have to investigate further...
-Ondrej
Tue, Mar 01, 2016 at 11:21:13AM CET, olichtne@redhat.com wrote:
On Tue, Mar 01, 2016 at 11:04:33AM +0100, Jan Tluka wrote:
Mon, Feb 29, 2016 at 05:17:27PM CET, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
This makes the Wizard use the CtlSecureSocket instead of the basic socket. In case of noninteractive mode it is automatically assumed that the communication between Slave and the wizard is not secured - it uses the "none" authentication method.
In case of interactive mode, the user will be asked to provide security parameters that will be used for the handshake. These will also be included in the resulting slave machine description XML file.
If the connection fails during the handshake the user will be notified of this.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com
Two issues so far for wizard.
When I do ./lnst-pool-wizard -v (interactive virtual mode), the wizard won't ask for security parameters.
That's because the Wizard doesn't connect to the virt machines... so it isn't opening a socket. If we want, we can add the security parameters query so that we can output it into the XML file, but they won't be "tested" since no connection will be created.
Second, I got traceback when I specified 'ssh' auth for one of the slaves:
$ ./lnst-pool-wizard Enter path to a pool directory (default: '/home/igyn/.lnst/pool/'): /home/igyn/.lnst/pool-secure-2 Path '/home/igyn/.lnst/pool-secure-2' does not exist Create dir '/home/igyn/.lnst/pool-secure-2'? [Y/n]: y Dir '/home/igyn/.lnst/pool-secure-2' has been created Enter hostname: lnst1 Enter port (default: 9999): Enter authentication type (default: none): ssh Traceback (most recent call last): File "./lnst-pool-wizard", line 92, in <module> main() File "./lnst-pool-wizard", line 86, in main wizard.interactive(hostlist, pool_dir) File "/home/igyn/tmp/lnst/lnst/Controller/Wizard.py", line 74, in interactive sock = self._get_connection(hostname, port, sec_params) File "/home/igyn/tmp/lnst/lnst/Controller/Wizard.py", line 374, in _get_connection ret.handshake(sec_params) File "/home/igyn/tmp/lnst/lnst/Controller/CtlSecSocket.py", line 62, in handshake self._ssh_handshake() File "/home/igyn/tmp/lnst/lnst/Controller/CtlSecSocket.py", line 161, in _ssh_handshake ctl_ssh_key = load_pem_private_key(f.read(), None, backend) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 20, in load_pem_private_key return backend.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/multibackend.py", line 276, in load_pem_private_key return b.load_pem_private_key(data, password) File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 694, in load_pem_private_key password, File "/usr/lib64/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 863, in _load_key raise password_func.exception TypeError: Password was not given but private key is encrypted.
Right... the SecSocket classes don't support password encrypted keys... if they did, you'd have to include the password in the lnst-ctl.conf file which I don't think anyone will want to do... What are your thoughts on this?
Hmm, and if I use ssh-add to unlock my key? What's the difference? Or is this just what openssh provides for ssh tools only?
If it's a different problem (your ssh key isn't encrypted) then I'll have to investigate further...
The key IS encrypted. Thanks for your advice!
-Ondrej
Mon, Feb 29, 2016 at 05:17:27PM CET, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
This makes the Wizard use the CtlSecureSocket instead of the basic socket. In case of noninteractive mode it is automatically assumed that the communication between Slave and the wizard is not secured - it uses the "none" authentication method.
In case of interactive mode, the user will be asked to provide security parameters that will be used for the handshake. These will also be included in the resulting slave machine description XML file.
If the connection fails during the handshake the user will be notified of this.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com
lnst/Controller/Wizard.py | 101 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 7 deletions(-)
diff --git a/lnst/Controller/Wizard.py b/lnst/Controller/Wizard.py index 2f99a25..fca572a 100644 --- a/lnst/Controller/Wizard.py +++ b/lnst/Controller/Wizard.py @@ -21,6 +21,8 @@ import os from lnst.Common.Utils import mkdir_p, check_process_running from lnst.Common.Config import DefaultRPCPort from lnst.Common.ConnectionHandler import send_data, recv_data +from lnst.Controller.CtlSecSocket import CtlSecSocket +from lnst.Common.SecureSocket import SecSocketException from xml.dom.minidom import getDOMImplementation from lxml import etree
@@ -44,7 +46,8 @@ class Wizard: if hostname == -1: continue
sock = self._get_connection(hostname, port)
sec_params = {"auth_type": "none"}
sock = self._get_connection(hostname, port, sec_params) if sock is None: continue
@@ -66,8 +69,9 @@ class Wizard: while True: hostname = self._query_hostname() port = self._query_port()
sec_params = self._query_sec_params(hostname)
sock = self._get_connection(hostname, port)
sock = self._get_connection(hostname, port, sec_params) if sock is None: if self._query_continuation(): continue
@@ -85,7 +89,7 @@ class Wizard: self._create_xml(machine_interfaces=machine_interfaces, hostname=hostname, pool_dir=pool_dir, filename=filename, port=port,
mode="interactive")
mode="interactive", sec_params=sec_params) if self._query_continuation(): continue
@@ -131,7 +135,8 @@ class Wizard: sys.stderr.write("Skipping host '%s'\n" % host) continue
sock = self._get_connection(hostname, port)
sec_params = {"auth_type": "none"}
sock = self._get_connection(hostname, port, sec_params) if sock is None: sys.stderr.write("Skipping host '%s'\n" % host) continue
@@ -271,7 +276,7 @@ class Wizard:
def _create_xml(self, machine_interfaces=None, hostname=None, pool_dir=None, filename=None, mode=None,
port=None, libvirt_domain=None):
port=None, libvirt_domain=None, sec_params=None): """ Creates slave machine XML file @param machine_interfaces Dictionary with machine's interfaces @param hostname Hostname of the machine
@@ -328,13 +333,36 @@ class Wizard: "'%s' will be created\n" % filename) return
if sec_params:
security_el = doc.createElement("security")
top_el.appendChild(security_el)
auth_type_el = doc.createElement("auth_type")
security_el.appendChild(auth_type_el)
auth_type_text = doc.createTextNode(sec_params["auth_type"])
auth_type_el.appendChild(auth_type_text)
if sec_params["auth_type"] is "password":
password_el = doc.createElement("auth_passwd")
security_el.appendChild(password_el)
password_text = doc.createTextNode(sec_params["auth_password"])
password_el.appendChild(password_text)
elif sec_params["auth_type"] is "pubkey":
pubkey_el = doc.createElement("pubkey_path")
security_el.appendChild(pubkey_el)
pubkey_text = doc.createTextNode(sec_params["srv_pubkey_path"])
pubkey_el.appendChild(pubkey_text)
if self._write_to_file(pool_dir, filename, doc): print("File '%s/%s' successfuly created." % (pool_dir, filename)) else: sys.stderr.write("File '%s/%s' could not be opened " "or data written.\n" % (pool_dir, filename))
- def _get_connection(self, hostname, port):
- def _get_connection(self, hostname, port, sec_params): """ Connects to machine @param hostname Hostname of the machine @param port Port of the machine
@@ -342,7 +370,14 @@ class Wizard: """ try: sock = socket.create_connection((hostname, port))
return sock
ret = CtlSecSocket(sock)
ret.handshake(sec_params)
return ret
except SecSocketException:
sys.stderr.write("Couldn't connect to host %s:%s, because "\
"security negotiation failed.\n" %
(hostname, port))
return None except socket.error: sys.stderr.write("Connection to remote host '%s:%s' failed\n" % (hostname, port))
@@ -495,6 +530,58 @@ class Wizard: except: sys.stderr.write("Invalid port entered\n")
- def _query_sec_params(self, hostname):
""" Queries user for security parameters of the connection
@return Dictionary with the security parameters
"""
while True:
auth_type = raw_input("Enter authentication type (default: none): ")
if auth_type == "":
auth_type = "none"
elif auth_type not in ["none", "no-auth", "password",
"pubkey", "ssh"]:
sys.stderr.write("Invalid authentication type.")
continue
break
if auth_type == "none":
return {"auth_type": "none"}
elif auth_type == "no-auth":
return {"auth_type": "no-auth"}
elif auth_type == "ssh":
return {"auth_type": "ssh"}
elif auth_type == "password":
while True:
password = raw_input("Enter password: ")
if password == "":
sys.stderr.write("Invalid password.")
continue
break
return {"auth_type": "password",
"auth_passwd": password}
elif auth_type == "pubkey":
while True:
identity = raw_input("Enter identity: ")
if identity == "":
sys.stderr.write("Invalid identity.")
continue
break
while True:
privkey = raw_input("Enter path to Ctl private key: ")
if privkey == "" or os.path.isfile(privkey):
^^^^ should be: not os.path.isfile(privkey)
sys.stderr.write("Invalid path to private key.")
continue
break
while True:
srv_pubkey_path = raw_input("Enter path to Slave public key: ")
if srv_pubkey_path == "" or os.path.isfile(srv_pubkey_path):
^^^^
same here:
sys.stderr.write("Invalid path to public key.")
continue
break
return {"auth_type": "pubkey",
"identity": identity,
"privkey": privkey,
"srv_pubkey_path": srv_pubkey_path}
- def _write_to_file(self, pool_dir, filename, doc): """ Writes contents of XML to a file @param pool_dir Path to directory where the file will be created
-- 2.7.2 _______________________________________________ LNST-developers mailing list lnst-developers@lists.fedorahosted.org https://lists.fedorahosted.org/admin/lists/lnst-developers@lists.fedorahoste...
lnst-developers@lists.fedorahosted.org