"Fossies" - the Fresh Open Source Software Archive 
Member "eucalyptus-4.4.2/tools/authorize-migration-keys" (4 Aug 2017, 12598 Bytes) of package /linux/misc/eucalyptus-4.4.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "authorize-migration-keys":
4.4.1_vs_4.4.2.
1 #!/usr/bin/python -tt
2
3 #
4 # (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 3 of the License.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see http://www.gnu.org/licenses/.
17 #
18
19 import os
20 import re
21 import sys
22 import time
23 import shutil
24 import logging
25 import os.path
26 import argparse
27 import StringIO
28 import subprocess
29 from subprocess import CalledProcessError
30
31 MAX_RETRIES = 20
32 LIBVIRT_CONF = '/etc/libvirt/libvirtd.conf'
33 KEYPATH = "{}/var/lib/eucalyptus/keys/node-cert.pem".format(os.getenv('EUCALYPTUS', ''))
34 CERT_TOOL = ['certtool', '-i', '--infile', KEYPATH]
35 DN_ORDERS = (('C', 'O', 'L', 'CN'),
36 ('CN', 'O', 'L', 'C'))
37 EMPTY_TLS_ALLOWED = 'tls_allowed_dn_list = []\n'
38 TLS_ALLOWED_BEGIN = 'tls_allowed_dn_list = ['
39
40 def getLogger(name, verbose):
41 '''
42 Setup simple logging to stdout
43 '''
44 logger = logging.getLogger(name)
45 if verbose:
46 logger.setLevel(logging.DEBUG)
47 else:
48 logger.setLevel(logging.INFO)
49 ch = logging.StreamHandler()
50 if verbose:
51 ch.setLevel(logging.DEBUG)
52 else:
53 ch.setLevel(logging.INFO)
54 formatter = logging.Formatter('%(asctime)s - %(message)s')
55 ch.setFormatter(formatter)
56 logger.addHandler(ch)
57
58 return logger
59
60
61 def parse_dn(dn):
62 '''
63 Parse a string in the form: 'CN=v1,O=v2,L=secret...' into a dictionary.
64 strips off Subject: prefix if present.
65 '''
66 dn_split = dn.strip().replace('Subject: ', '').split(',')
67 dn_dict = {i: j for i, j in [(k.split('=')) for k in dn_split]}
68 return dn_dict
69
70
71 def get_cert_subject():
72 '''
73 Read the certificate using certtool and get the subject line to
74 parse out the components. Certificate errors are considered fatal
75
76 certificate: /var/lib/eucalyptus/keys/node-cert.pem
77
78 returns: Dictionary with component names as keys on success
79 Empty dictionary on failure
80 '''
81 subject_dict = {}
82
83 try:
84 cert_output = subprocess.check_output(CERT_TOOL)
85 except OSError as e:
86 logger.error("Unable to execute command: %s error: %s:%s"
87 % (CERT_TOOL[0],e.errno, e.strerror))
88 sys.exit(1)
89 except subprocess.CalledProcessError as e:
90 logger.error("Abnormal exit code from %s: returncode: %s"
91 % (e.cmd, e.returncode))
92 sys.exit(1)
93 except:
94 logger.error("Unknown error encountered: %s"
95 % sys.exc_info()[0])
96 sys.exit(1)
97
98 subject = None
99 for line in cert_output.split('\n'):
100 if "Subject:" in line:
101 subject = line
102 break
103
104 if subject is not None:
105 subject_dict = parse_dn(subject)
106
107 return subject_dict
108
109
110 def generate_new_dn(client, secret):
111 '''
112 Given the client and secret passed, construct the appropriate
113 Subject DNs to be used in the configuration file.
114 '''
115 new_dn = {'CN': client, 'L': secret}
116
117 subject = get_cert_subject()
118 if 'CN' in subject:
119 new_dn['O'] = subject['CN']
120 if 'C' in subject:
121 new_dn['C'] = subject['C']
122
123 # Make sure that we found an 'O' and 'C' attributes in the certificate
124 if 'O' not in new_dn and 'C' not in new_dn:
125 logger.error("Cannot construct new entry for %s %s exiting",
126 new_dn['CN'],
127 new_dn['L'])
128 sys.exit(1)
129
130 # multiple DNs are needed to accommodate different Linux release
131 # ordering output from certtool
132 new_dn_list = []
133 for dn_order in DN_ORDERS:
134 new_dn_list.append(','.join(["{}={}".format(i, new_dn[i]) for i in dn_order]))
135 return new_dn_list
136
137 def copy_file(src, dst):
138 '''
139 Copy a file, on failure exit program
140 '''
141 try:
142 shutil.copy(src, dst)
143 except IOError as e:
144 logger.error("Unable to copy %s to %s errno: %d error: %s",
145 src, dst, e.errno, e.strerror)
146 sys.exit(1)
147
148 def move_file(src, dst):
149 '''
150 Move file, exit program on failure
151 '''
152 try:
153 shutil.move(src, dst)
154 except IOError as e:
155 logger.error("Unable to move file %s to %s errno: %d error: %s",
156 src, dst, e.errno, e.strerror)
157 sys.exit(1)
158
159 def write_config(tls_allowed_dn_list, config_file):
160 '''
161 Modifies the configuration file on disk, in the default
162 case (deauthorizing all clients) libvirtd will
163 not be restarted if the list was originally empty.
164
165 Creates a backup of the distribution configuration
166 file to: config_file + '.orig', if not already present.
167
168 If the file did not need to be modified, then no changes
169 will be made to the file.
170
171 returns: True - if the configuration file was modified.
172 False - if there was no need to modify the configuration file
173 '''
174 updated = False
175
176 config_file_orig = config_file + '.orig'
177 config_file_bak = config_file + '.bak'
178 config_file_new = config_file + '.new'
179
180 if not os.path.exists(config_file):
181 logger.error("Configuration file: %s is missing, exiting", config_file)
182 sys.exit(1)
183
184 # Copy distribution original to .orig
185 if not os.path.exists(config_file_orig):
186 copy_file(config_file, config_file_orig)
187
188 # regex to pull the DNs within the buffer: ["dn1","dn2","dn3"]
189 dn_regex = re.compile('"(.*?)"', re.MULTILINE)
190
191 # buffer to run multiline regex against
192 buffer = StringIO.StringIO()
193
194 # Construct the list of CNs to possibly replace
195 remove_dn_list = [i['CN'] for i in
196 [parse_dn(j) for j in tls_allowed_dn_list]]
197
198 # Construct the new configuration file
199 with open(config_file) as cf:
200 try:
201 with open(config_file_new, 'wc') as nf:
202 for line in cf:
203 if 'tls_allowed_dn_list' in line:
204 #
205 # Read the list of DNs into a buffer to parse
206 # Note the call to cf.next()
207 #
208 line_is_commented = line.strip().startswith('#')
209
210 buffer.write(line)
211 if ']' not in line:
212 # Read lines until we get the end bracket
213 while True:
214 l = cf.next()
215 buffer.write(l)
216 if ']' in l:
217 break
218
219 # Construct our current list of DNs
220 DN_list = dn_regex.findall(buffer.getvalue())
221
222 # Current list is empty, see if it is commented out or not
223 if len(DN_list) == 0 and len(tls_allowed_dn_list) == 0:
224 if line_is_commented:
225 nf.write(EMPTY_TLS_ALLOWED)
226 updated = True
227 else:
228 break # No change needed to the configuration file
229 elif len(tls_allowed_dn_list) == 0:
230 nf.write(EMPTY_TLS_ALLOWED)
231 updated = True
232 else:
233 # Read each current dn, if it matches what we are trying to add, we'll replace
234 # it. Otherwise, we'll add onto the current list
235 if line_is_commented:
236 # commented config entry,
237 # need to disregard the contents
238 DN_list = []
239
240 new_dn_list = tls_allowed_dn_list[:]
241
242 for dn in DN_list:
243 dn_dict = parse_dn(dn)
244 if dn_dict['CN'] not in remove_dn_list:
245 new_dn_list.append(dn)
246
247 #
248 # Write out the new list of dns
249 #
250 nf.write(TLS_ALLOWED_BEGIN)
251 padding = len(TLS_ALLOWED_BEGIN)
252
253 for i, dn in enumerate(new_dn_list):
254 nf.write('"{}"'.format(dn))
255
256 # Append comma, except at end of list, add padding on new line
257 if i < len(new_dn_list)-1:
258 nf.write(",\n")
259 nf.write(" " * padding)
260 else:
261 nf.write("]\n")
262 updated = True
263 else:
264 nf.write(line)
265 except IOError as e:
266 logger.error("Unable to write to new file: %s [Errno:%d] %s",
267 config_file_new,
268 e.errno,
269 e.strerror)
270 sys.exit(1)
271
272 if updated:
273 # Copy current config to .bak
274 copy_file(config_file, config_file_bak)
275
276 # move .new file to real conf file
277 move_file(config_file_new, config_file)
278
279 elif os.path.exists(config_file_new):
280 # No differences, remove the generated config file
281 os.unlink(config_file_new)
282 return updated
283
284 if __name__ == '__main__':
285
286 tls_allowed_dn_list = []
287 config_updated = False
288
289 parser = argparse.ArgumentParser(description='Modify migration keys for libvirtd')
290 parser.add_argument('-v', action='store_true', dest='verbose',
291 help='Verbose')
292 parser.add_argument('-r', action='store_true', dest='restart',
293 help='Restart libvirtd, if file changes were made')
294 parser.add_argument('-c', metavar='filename', dest='config',
295 default=LIBVIRT_CONF,
296 help="Configuration file, default: %s" % LIBVIRT_CONF)
297
298 group = parser.add_mutually_exclusive_group(required=True)
299 group.add_argument('-a', nargs=2, metavar=('client', 'secret'),
300 help='Authorize client')
301 group.add_argument('-D', action='store_true',
302 help='Deauthorize all migration clients')
303
304 # Handle case where NC is calling with extra parameters that we don't need.
305 parser.add_argument('args', metavar='', nargs=argparse.REMAINDER,
306 help=argparse.SUPPRESS)
307 args = parser.parse_args()
308
309 logger = getLogger('authorize-migration-keys', args.verbose)
310
311 if not os.path.exists(args.config):
312 logger.error("Error, configuration file: %s not found, exiting", args.config)
313 sys.exit(1)
314 #
315 # If called with -D then we need to clear the tls_allowed_dn_list.
316 # If called with '-a client secret' then we need to *add*
317 # to the tls_allowed_dn_list or replace current entries.
318 #
319 if not args.D:
320 tls_allowed_dn_list = generate_new_dn(args.a[0], args.a[1])
321
322 config_updated = write_config(tls_allowed_dn_list, args.config)
323
324 if args.restart and config_updated:
325 logger.debug("Restarting libvirtd")
326 subprocess.call(['/usr/bin/systemctl', 'restart', 'libvirtd.service'])
327
328 #
329 # After restarting libvirtd, we need to wait for it to become available so
330 # that the node controller can connect with the hypervisor in startup
331 # situations.
332 #
333 connected = False
334 for i in range(0,MAX_RETRIES):
335 try:
336 subprocess.check_call(['virsh','connect'],stdin=open('/dev/null'), stdout=open('/dev/null','w'))
337 connected = True
338 break
339 except CalledProcessError as e:
340 logger.debug("Unable to connect to hypervisor on attempt number: [%d]", i+1)
341 time.sleep(1)
342 if not connected:
343 logger.error("Error, unable to connect to hypervisor after %d seconds",MAX_RETRIES)
344 sys.exit(1)
345 elif not config_updated:
346 logger.debug("No configuration change, will not restart libvirtd")
347 else:
348 logger.debug("Configuration file changed, libvirtd restart not requested")
349