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))