sshkeys_authority: don't allow to return None as parameter.
[slapos.git] / slapos / recipe / sshkeys_authority.py
1 ##############################################################################
2 #
3 # Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
4 #
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsibility of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # guarantees and support are strongly adviced to contract a Free Software
10 # Service Company
11 #
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 3
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #
26 ##############################################################################
27 import json
28 import hashlib
29 import os
30 import subprocess
31 import re
32
33 from slapos.recipe.librecipe import GenericBaseRecipe
34 from slapos.recipe.librecipe.inotify import subfiles
35
36 # This authority only works with dropbear sshkey generator
37 def sshkeys_authority(args):
38 requests_directory = args['requests']
39 keygen_binary = args['sshkeygen']
40
41 for request_filename in subfiles(requests_directory):
42
43 with open(request_filename) as request_file:
44 request = json.load(request_file)
45
46 key_type = request.get('type', 'rsa')
47 size = str(request.get('size', 2048))
48 try:
49 private_key = request['private_key']
50 public_key = request['public_key']
51 except KeyError:
52 break
53
54 if not os.path.exists(private_key):
55 if os.path.exists(public_key):
56 os.unlink(public_key)
57 keygen_cmd = [keygen_binary, '-t', key_type, '-f', private_key,
58 '-s', size]
59 # If the keygeneration return an non-zero status, it means there's a
60 # big problem. Let's exit in this case
61 subprocess.check_call(keygen_cmd, env=os.environ.copy())
62
63 if not os.path.exists(public_key):
64 keygen_cmd = [keygen_binary, '-f', private_key, '-y']
65
66 keygen = subprocess.Popen(keygen_cmd, stdout=subprocess.PIPE,
67 stdin=subprocess.PIPE,
68 stderr=subprocess.STDOUT,
69 env=os.environ.copy())
70 keygen.stdin.flush()
71 keygen.stdin.close()
72
73 # If the keygeneration return an non-zero status, it means there's a
74 # big problem. Let's exit in this case
75 if keygen.wait() != 0:
76 raise subprocess.CalledProcessError("%r returned a non-zero status" % \
77 ' '.join(keygen_cmd))
78 public_key_value = ''
79 for line in keygen.stdout:
80 # Perl programming !
81 # Don't worry, just regex to detect the ssh public key line
82 matchresult = re.match(r'ssh-.*?=+', line)
83 if matchresult:
84 public_key_value = matchresult.group(0)
85 break
86
87 with open(public_key, 'w') as public_key_file:
88 public_key_file.write(public_key_value)
89
90
91
92 class Recipe(GenericBaseRecipe):
93
94 def install(self):
95 args = dict(
96 requests=self.options['request-directory'],
97 sshkeygen=self.options['keygen-binary'],
98 )
99
100 wrapper = self.createPythonScript(self.options['wrapper'],
101 __name__ + '.sshkeys_authority', args)
102 return [wrapper]
103
104 class Request(GenericBaseRecipe):
105
106 def _options(self, options):
107 if 'name' not in options:
108 options['name'] = self.name
109
110 keys_directory = options['keys-directory']
111
112 self.private_key = os.path.join(keys_directory,
113 hashlib.sha256(options['name']).hexdigest())
114 self.public_key = self.private_key + '.pub'
115
116 options['public-key-value'] = ''
117 if os.path.exists(self.public_key):
118 key_content = open(self.public_key).read()
119 if key_content:
120 options['public-key-value'] = key_content
121
122 def install(self):
123 requests_directory = self.options['request-directory']
124 request_file = os.path.join(requests_directory, self.options['name'])
125
126 request = dict(
127 private_key=self.private_key,
128 public_key=self.public_key,
129 )
130 if 'size' in self.options:
131 request.update(size=int(self.options['size'], 10))
132 if 'type' in self.options:
133 request.update(type=self.options['type'])
134
135 with open(request_file, 'w') as file_:
136 json.dump(request, file_)
137
138 public_key_link, private_key_link = (self.options['public-key'],
139 self.options['private-key'],
140 )
141 # XXX: Copy and past from certificate_authority/__init__.py:Request
142 # We should factorize that
143 for link in [public_key_link, private_key_link]:
144 if os.path.islink(link):
145 os.unlink(link)
146 elif os.path.exists(link):
147 raise OSError("%r should be a symbolic link." % link)
148
149 os.symlink(self.public_key, public_key_link)
150 os.symlink(self.private_key, private_key_link)
151 # end-XXX
152
153 wrapper = self.createPythonScript(
154 self.options['wrapper'],
155 'slapos.recipe.librecipe.execute.execute_wait',
156 [ [self.options['executable']],
157 [self.private_key, self.public_key] ])
158
159
160 return [request_file, wrapper, public_key_link, private_key_link]