From c8e5808daf91f8232032d25ef9959e160b7c8ba0 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 24 Apr 2018 14:44:45 -0400 Subject: [PATCH] Further advance the swarm concept This marks the start of attempting to connect the invitation to sockets and using the invitation to measure the certificates as well as proving client knowledge of an invitation token. --- .../confluent/multimanager/__init__.py | 0 .../confluent/multimanager/invites.py | 55 ------------- confluent_server/confluent/sockapi.py | 13 +-- confluent_server/confluent/swarm/invites.py | 2 +- confluent_server/confluent/swarm/manager.py | 82 +++++++++++++++++++ 5 files changed, 90 insertions(+), 62 deletions(-) delete mode 100644 confluent_server/confluent/multimanager/__init__.py delete mode 100644 confluent_server/confluent/multimanager/invites.py create mode 100644 confluent_server/confluent/swarm/manager.py diff --git a/confluent_server/confluent/multimanager/__init__.py b/confluent_server/confluent/multimanager/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/confluent_server/confluent/multimanager/invites.py b/confluent_server/confluent/multimanager/invites.py deleted file mode 100644 index 72187ab4..00000000 --- a/confluent_server/confluent/multimanager/invites.py +++ /dev/null @@ -1,55 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2018 Lenovo -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This handles the process of generating and tracking/validating invites - -import base64 -import hashlib -import hmac -import os -pending_invites = {} - -def create_server_invitation(servername): - invitation = os.urandom(66) - pending_invites[servername] = invitation - return base64.b64encode(invitation) - -def create_client_proof(invitation, mycert, peercert): - return hmac.new(invitation, peercert + mycert, hashlib.sha256).digest() - -def check_server_proof(invitation, mycert, peercert, proof): - validproof = hmac.new(invitation, mycert + peercert, hashlib.sha256 - ).digest() - return proof == validproof - -def check_client_proof(servername, mycert, peercert, proof): - invitation = pending_invites[servername] - validproof = hmac.new(invitation, mycert + peercert, hashlib.sha256 - ).digest() - if proof == validproof: - # We know that the client knew the secret, and that it measured our - # certificate, and thus calling code can bless the certificate, and - # we can forget the invitation - del pending_invites[servername] - # We now want to prove to the client that we also know the secret, - # and that we measured their certificate well - # Now to generate an answer...., reverse the cert order so our answer - # is different, but still proving things - return hmac.new(invitation, peercert + mycert, hashlib.sha256 - ).digest() - # The given proof did not verify the invitation - return False - diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index fd0ecc41..ef5f11e1 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -44,7 +44,7 @@ import confluent.exceptions as exc import confluent.log as log import confluent.core as pluginapi import confluent.shellserver as shellserver - +import confluent.swarm.manager as swarm tracelog = None auditlog = None @@ -66,9 +66,9 @@ try: # so we need to ffi that in using a strategy compatible with PyOpenSSL import OpenSSL.SSL as libssln from OpenSSL._util import ffi - import OpenSSL.crypto as crypto except ImportError: libssl = None + ffi = None plainsocket = None @@ -104,9 +104,6 @@ def sessionhdl(connection, authname, skipauth=False, cert=None): authenticated = False authdata = None cfm = None - if cert: - print(repr(crypto.dump_certificate(crypto.FILETYPE_ASN1, - cert))) if skipauth: authenticated = True cfm = configmanager.ConfigManager(tenant=None, username=authname) @@ -119,6 +116,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None): while not authenticated: # prompt for name and passphrase send_data(connection, {'authpassed': 0}) response = tlvdata.recv(connection) + if 'swarm' in response: + return swarm.handle_connection(connection, cert, response['swarm']) authname = response['username'] passphrase = response['password'] # note(jbjohnso): here, we need to authenticate, but not @@ -134,6 +133,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None): cfm = authdata[1] send_data(connection, {'authpassed': 1}) request = tlvdata.recv(connection) + if 'swarm' in request and skipauth: + swarm.handle_connection(connection, None, request['swarm'], local=True) while request is not None: try: process_request( @@ -192,7 +193,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth): if operation == 'start': return start_term(authname, cfm, connection, params, path, authdata, skipauth) - elif operation == 'shutdown': + elif operation == 'shutdown' and skipauth: configmanager.ConfigManager.shutdown() else: hdlr = pluginapi.handle_path(path, operation, cfm, params) diff --git a/confluent_server/confluent/swarm/invites.py b/confluent_server/confluent/swarm/invites.py index 72187ab4..7945ee6d 100644 --- a/confluent_server/confluent/swarm/invites.py +++ b/confluent_server/confluent/swarm/invites.py @@ -25,7 +25,7 @@ pending_invites = {} def create_server_invitation(servername): invitation = os.urandom(66) pending_invites[servername] = invitation - return base64.b64encode(invitation) + return base64.b64encode(servername + '@' + invitation) def create_client_proof(invitation, mycert, peercert): return hmac.new(invitation, peercert + mycert, hashlib.sha256).digest() diff --git a/confluent_server/confluent/swarm/manager.py b/confluent_server/confluent/swarm/manager.py new file mode 100644 index 00000000..cbac994a --- /dev/null +++ b/confluent_server/confluent/swarm/manager.py @@ -0,0 +1,82 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2018 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import confluent.swarm.invites as invites +import confluent.tlvdata as tlvdata +import confluent.util as util +import eventlet.green.socket as socket +import eventlet.green.ssl as ssl +try: + import OpenSSL.crypto as crypto +except ImportError: + # while not always required, we use pyopenssl required for at least swarm + crypto = None + +swarmcerts = {} + + +def handle_connection(connection, cert, swarmrequest, local=False): + operation = swarmrequest['operation'] + if cert: + cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) + else: + if not local: + return + if 'invite' == operation: + name = swarmrequest['invite']['name'] + invitation = invites.create_server_invitation(name) + tlvdata.send(connection, {'swarm': {'invitation': invitation}}) + if 'join' == operation: + invitation = swarmrequest['invitation'] + invitation = base64.b64decode(invitation) + name, invitation = invitation.split('@') + host = swarmrequest['server'] + remote = socket.create_connection((host, 13001)) + # This isn't what it looks like. We do CERT_NONE to disable + # openssl verification, but then use the invitation as a + # shared secret to validate the certs as part of the join + # operation + remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE, + keyfile='/etc/confluent/privkey.pem', + certfile='/etc/confluent/srvcert.pem') + mycert = util.get_certificate_from_file( + '/etc/confluent/srvcert.pem') + cert = remote.getpeercert(binary_form=True) + proof = base64.b64encode(invites.create_client_proof( + invitation, mycert, cert)) + tlvdata.recv(remote) # ignore banner + tlvdata.recv(remote) # ignore authpassed: 0 + tlvdata.send(remote, {'swarm': {'operation': 'joinchallenge', + 'name': name, 'hmac': proof}}) + rsp = tlvdata.recv(remote) + proof = rsp['swarm']['approval'] + j = invites.check_server_proof(invitation, mycert, cert, proof) + if not j: + return + if 'joinchallenge' == operation: + mycert = util.get_certificate_from_file('/etc/confluent/srvcert.pem') + proof = base64.b64decode(swarmrequest['hmac']) + myrsp = invites.check_client_proof(swarmrequest['name'], mycert, + cert, proof) + if not myrsp: + connection.close() + return + myrsp = base64.b64encode(myrsp) + swarmcerts[swarmrequest['name']] = cert + tlvdata.send(connection, {'swarm': {'approval': myrsp}}) + clientready = tlvdata.recv(connection) + print(repr(clientready))