diff --git a/setup.py b/setup.py index 1a29375bd7ee2db91848a36394223476641ea06d..1303f354b9454056adf37277aeae7bd7c673288a 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,8 @@ setup(name=name, 'siptester = slapos.recipe.siptester:SipTesterRecipe', 'simplelogger = slapos.recipe.simplelogger:Recipe', 'slaprunner = slapos.recipe.slaprunner:Recipe', + 'sshkeys_authority = slapos.recipe.sshkeys_authority:Recipe', + 'sshkeys_authority.request = slapos.recipe.sshkeys_authority:Request', 'stunnel = slapos.recipe.stunnel:Recipe', 'testnode = slapos.recipe.testnode:Recipe', 'urlparse = slapos.recipe.urlparse_:Recipe', diff --git a/slapos/recipe/sshkeys_authority.py b/slapos/recipe/sshkeys_authority.py new file mode 100644 index 0000000000000000000000000000000000000000..1979ea8000a9ec4aaa93d073163f45a7967e190e --- /dev/null +++ b/slapos/recipe/sshkeys_authority.py @@ -0,0 +1,151 @@ +############################################################################## +# +# Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## +import json +import hashlib +import os +import subprocess + +from slapos.recipe.librecipe import GenericBaseRecipe +from slapos.recipe.librecipe.inotify import subfiles + +# This authority only works with dropbear sshkey generator +def sshkeys_authority(args): + requests_directory = args['requests'] + keygen_binary = args['sshkeygen'] + + for request_filename in subfiles(requests_directory): + + with open(request_filename) as request_file: + request = json.load(request_file) + + key_type = request.get('type', 'rsa') + size = str(request.get('size', 2048)) + try: + private_key = request['private_key'] + public_key = request['public_key'] + except KeyError: + break + + if not os.path.exists(private_key): + keygen_cmd = [keygen_binary, '-t', key_type, '-f', private_key, + '-s', size] + # If the keygeneration return an non-zero status, it means there's a + # big problem. Let's exit in this case + subprocess.check_call(keygen_cmd, env=os.environ.copy()) + + if not os.path.exists(public_key): + keygen_cmd = [keygen_binary, '-f', private_key, '-y'] + + keygen = subprocess.Popen(keygen_cmd, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=os.environ.copy()) + keygen.stdin.flush() + keygen.stdin.close() + + # If the keygeneration return an non-zero status, it means there's a + # big problem. Let's exit in this case + if keygen.wait() != 0: + raise subprocess.CalledProcessError("%r returned a non-zero status" % \ + ' '.join(keygen_cmd)) + # Line : "Public key portion is :" + keygen.stdout.readline() + public_key_value = keygen.stdout.readline().strip() + with open(public_key, 'w') as public_key_file: + public_key_file.write(public_key_value) + + + +class Recipe(GenericBaseRecipe): + + def install(self): + args = dict( + requests=self.options['request-directory'], + sshkeygen=self.options['keygen-binary'], + ) + + wrapper = self.createPythonScript(self.options['wrapper'], + __name__ + '.sshkeys_authority', args) + return [wrapper] + +class Request(GenericBaseRecipe): + + def _options(self, options): + if 'name' not in options: + options['name'] = self.name + + keys_directory = options['keys-directory'] + + self.private_key = os.path.join(keys_directory, + hashlib.sha256(options['name']).hexdigest()) + self.public_key = self.private_key + '.pub' + + if os.path.exists(self.public_key): + with open(self.public_key) as key: + options['public-key-value'] = key.read() + else: + options['public-key-value'] = '' + + def install(self): + requests_directory = self.options['request-directory'] + request_file = os.path.join(requests_directory, self.options['name']) + + request = dict( + private_key=self.private_key, + public_key=self.public_key, + ) + if 'size' in self.options: + request.update(size=int(self.options['size'], 10)) + if 'type' in self.options: + request.update(type=self.options['type']) + + with open(request_file, 'w') as file_: + json.dump(request, file_) + + public_key_link, private_key_link = (self.options['public-key'], + self.options['private-key'], + ) + # XXX: Copy and past from certificate_authority/__init__.py:Request + # We should factorize that + for link in [public_key_link, private_key_link]: + if os.path.islink(link): + os.unlink(link) + elif os.path.exists(link): + raise OSError("%r should be a symbolic link." % link) + + os.symlink(self.public_key, public_key_link) + os.symlink(self.private_key, private_key_link) + # end-XXX + + wrapper = self.createPythonScript( + self.options['wrapper'], + 'slapos.recipe.librecipe.execute.execute_wait', + [ [self.options['executable']], + [self.private_key, self.public_key] ]) + + + return [request_file, wrapper, public_key_link, private_key_link]