Add sshkeys_authority generic recipe
[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
32 from slapos.recipe.librecipe import GenericBaseRecipe
33 from slapos.recipe.librecipe.inotify import subfiles
34
35 # This authority only works with dropbear sshkey generator
36 def sshkeys_authority(args):
37 requests_directory = args['requests']
38 keygen_binary = args['sshkeygen']
39
40 for request_filename in subfiles(requests_directory):
41
42 with open(request_filename) as request_file:
43 request = json.load(request_file)
44
45 key_type = request.get('type', 'rsa')
46 size = str(request.get('size', 2048))
47 try:
48 private_key = request['private_key']
49 public_key = request['public_key']
50 except KeyError:
51 break
52
53 if not os.path.exists(private_key):
54 keygen_cmd = [keygen_binary, '-t', key_type, '-f', private_key,
55 '-s', size]
56 # If the keygeneration return an non-zero status, it means there's a
57 # big problem. Let's exit in this case
58 subprocess.check_call(keygen_cmd, env=os.environ.copy())
59
60 if not os.path.exists(public_key):
61 keygen_cmd = [keygen_binary, '-f', private_key, '-y']
62
63 keygen = subprocess.Popen(keygen_cmd, stdout=subprocess.PIPE,
64 stdin=subprocess.PIPE,
65 stderr=subprocess.STDOUT,
66 env=os.environ.copy())
67 keygen.stdin.flush()
68 keygen.stdin.close()
69
70 # If the keygeneration return an non-zero status, it means there's a
71 # big problem. Let's exit in this case
72 if keygen.wait() != 0:
73 raise subprocess.CalledProcessError("%r returned a non-zero status" % \
74 ' '.join(keygen_cmd))
75 # Line : "Public key portion is :"
76 keygen.stdout.readline()
77 public_key_value = keygen.stdout.readline().strip()
78 with open(public_key, 'w') as public_key_file:
79 public_key_file.write(public_key_value)
80
81
82
83 class Recipe(GenericBaseRecipe):
84
85 def install(self):
86 args = dict(
87 requests=self.options['request-directory'],
88 sshkeygen=self.options['keygen-binary'],
89 )
90
91 wrapper = self.createPythonScript(self.options['wrapper'],
92 __name__ + '.sshkeys_authority', args)
93 return [wrapper]
94
95 class Request(GenericBaseRecipe):
96
97 def _options(self, options):
98 if 'name' not in options:
99 options['name'] = self.name
100
101 keys_directory = options['keys-directory']
102
103 self.private_key = os.path.join(keys_directory,
104 hashlib.sha256(options['name']).hexdigest())
105 self.public_key = self.private_key + '.pub'
106
107 if os.path.exists(self.public_key):
108 with open(self.public_key) as key:
109 options['public-key-value'] = key.read()
110 else:
111 options['public-key-value'] = ''
112
113 def install(self):
114 requests_directory = self.options['request-directory']
115 request_file = os.path.join(requests_directory, self.options['name'])
116
117 request = dict(
118 private_key=self.private_key,
119 public_key=self.public_key,
120 )
121 if 'size' in self.options:
122 request.update(size=int(self.options['size'], 10))
123 if 'type' in self.options:
124 request.update(type=self.options['type'])
125
126 with open(request_file, 'w') as file_:
127 json.dump(request, file_)
128
129 public_key_link, private_key_link = (self.options['public-key'],
130 self.options['private-key'],
131 )
132 # XXX: Copy and past from certificate_authority/__init__.py:Request
133 # We should factorize that
134 for link in [public_key_link, private_key_link]:
135 if os.path.islink(link):
136 os.unlink(link)
137 elif os.path.exists(link):
138 raise OSError("%r should be a symbolic link." % link)
139
140 os.symlink(self.public_key, public_key_link)
141 os.symlink(self.private_key, private_key_link)
142 # end-XXX
143
144 wrapper = self.createPythonScript(
145 self.options['wrapper'],
146 'slapos.recipe.librecipe.execute.execute_wait',
147 [ [self.options['executable']],
148 [self.private_key, self.public_key] ])
149
150
151 return [request_file, wrapper, public_key_link, private_key_link]