import repository from arizona
[raven.git] / apps / stork / storkusername.py
1 #! /usr/bin/env python
2
3 """
4 Stork Project (http://www.cs.arizona.edu/stork/)
5 Module: storkusername
6 Description:  Determines the prefix for configuration files
7 """
8
9 # Use arizonaconfig
10 #           [option, long option,              variable,               action,  data,     default, metavar,    description]
11 """arizonaconfig
12    options=[["-u",   "--username",             "username",             "store", "string", "",      "USERNAME", "use this username for configuration files"],
13             ["",     "--publickeyfile",        "publickeyfile",        "store", "string", "",      "file",     "use this public key file to determine which configuration files to use.  Recommended: /usr/local/stork/var/keys/[username].publickey"],
14             ["",     "--defaultusername",      "defaultusername",      "store", "string", "default",  None,    "default username to use if file starting with username does not exist"],
15             ["",     "--defaultpublickey",     "defaultpublickey",     "store", "string", "",  None,           "public key to go with --defaultusername"],
16             ["",     "--keydir",               "keydir",               "store", "string", "/usr/local/stork/var/keys", None, "place to store keys"],
17             ["",     "--noplckeys",            "noplckeys",            "store_true",  None,     False,      None,    "do not use SSH-keys from PLC"]]
18
19    includes=[]\r
20 """
21
22 import arizonaconfig
23 import arizonacrypt
24 import arizonareport
25 import arizonageneral
26 import sys
27 import xmlrpclib
28 import os
29 import ravenlib.platform.all
30 import ravenlib.files.sshkey
31
32 """ glo_keylist: a list of key specifications (see lib/ravenlib/platform/
33
34        ["kind"]   ... "ssl" or "ssh"
35        ["value"]  ... value of key as read from platform module
36        ["sl"]     ... stringlist of the arizonacrypt type
37        ["sslfn"]  ... filename of key in OpenSSL format
38        ["sslstr"] ... string containing key in OpenSSL format
39
40        One of either sslfn or sslstr should be defined. Both are not required
41 """
42
43 glo_keylist = []
44
45 """ glo_prefixlist: a list of config_prefixes
46
47         ["prefix"]   ... prefix string ("username.key")
48         ["username"] ... user name
49         ["key"]      ... key dictionary (see glo_keylist)
50 """
51
52 glo_prefixlist = []
53
54 def make_prefix_dict(username, prefix, key):
55     prefix_dict = {"username": username, "prefix": prefix, "key": key}
56     return prefix_dict
57
58 def convert_keys(keylist):
59    """ Convert SSH keys to SSL keys as necessary. Also write the SSL keys to
60        local files.
61    """
62    keydir = arizonaconfig.get_option("keydir")
63
64    # create the keydir if it doesn't exist
65    if not os.path.exists(keydir):
66        os.mkdir(keydir)
67
68    for i, key in enumerate(keylist):
69        if key["kind"] == "ssh":
70            sshkey = str(key["value"]).strip()
71            if len(sshkey) == 0: continue
72
73            sslfn = os.path.join(keydir, "sslkey" + str(i) + ".publickey")
74
75            ssl_key_str = ravenlib.files.sshkey.ssh_filestr_to_ssl(sshkey)
76            f = open(sslfn, "w")
77            f.write(ssl_key_str)
78            f.close()
79
80            # extract the username from the sshkey. The sshkey is in three parts:
81            # "ssh-rsa key username", so split it and take the third part
82            # TODO: support for DSA keys (ssh-dss?)
83            split_key = sshkey.split(" ")
84            if len(split_key) > 2:
85               sshuser = split_key[2]
86               # get rid of the hostname part of an email address
87               sshuser = sshuser.split("@")[0]
88            else:
89               sshuser = "unspecified"
90
91            key["kind"] = "ssl"
92            key["sshusername"] = sshuser
93            key["sslfn"] = sslfn
94
95        elif key["kind"] == "ssl":
96            sslfn = os.path.join(keydir, "sslkey" + str(i) + ".publickey")
97
98            if not "sl" in key:
99                 key["sl"] = arizonacrypt.fnstring_to_publickey_sl(key["value"])
100
101            sslf = file(sslfn, "w")
102            for item in key["sl"]:
103                sslf.write(item.rstrip('\r\n')+'\n')
104            sslf.close()
105
106            key["sslfn"] = sslfn
107
108        else:
109            arizonareport.send_error("unknown key kind: " + str(key["kind"]))
110
111
112 def build_default_prefix():
113    default_username = arizonaconfig.get_option("defaultusername")
114    default_pkstring = arizonaconfig.get_option("defaultpublickey")
115
116    if default_username and default_pkstring:
117       keydir = arizonaconfig.get_option("keydir")
118       default_sl = arizonacrypt.fnstring_to_publickey_sl(default_pkstring)
119       default_fn = os.path.join(keydir, "_default.publickey")
120
121       f = file(default_fn, "w")
122       for item in default_sl:
123          f.write(item.rstrip('\r\n')+'\n')
124       f.close()
125
126       key = {"kind": "ssl", "sslfn": default_fn, "sl": default_sl, "sslstr": default_pkstring}
127
128       prefix_dict = make_prefix_dict(default_username, default_username + "." + default_pkstring, key)
129
130       return prefix_dict
131
132    return None
133
134 def reset_key_database():
135    """
136    <Purpose>
137       Resets the key database. This should be called if an event occurs that
138       would change what public keys are usable -- for example if a new
139       publickey is downloaded from the repository.
140
141    <Arguments>
142       Slice name.
143
144    <Side Effects>
145       glo_keylist is set to an empty list
146
147    <Returns>
148       None
149    """
150    global glo_keylist
151    global glo_prefixlist
152
153    glo_keylist = []
154    glo_prefixlist = []
155
156 def get_usernames(ignore_username = False):
157    # get all slice names from the platform library
158    slice_names = ravenlib.platform.all.get_slice_names()
159
160    if not slice_names:
161        slice_names = ["noslice"]
162
163    arizonareport.send_out(4, "[DEBUG] slicenames = " + ",".join(slice_names))
164
165    user_names = slice_names
166    if not ignore_username:
167       username = arizonaconfig.get_option("username")
168       if username:
169           user_names = [username]
170
171    return user_names
172
173 def build_key_database(reset = False, ignore_username = False, tolerate_no_key = True):
174    """
175    <Purpose>
176       Builds a database of public keys. Keys are downloaded from PLC and
177       converted from ssh to openssl format (as long as --noplckeys is not
178       used). In addition, the --publickey file is read if it is present.
179
180    <Args>
181       reset - if True, resets the key database
182       ignore_username - If True, ignore the username= setting
183       tolerate_no_key - If True, do not exit if no public key found
184
185    <Side Effects>
186       glo_keylist is filled with a list of usable publickey tuples
187
188    <Returns>
189       glo_keylist
190    """
191    global glo_keylist
192    global glo_prefixlist
193
194    if reset:
195       reset_key_database()
196
197    # see if we are already done
198    if glo_prefixlist:
199       return glo_prefixlist
200
201    if arizonaconfig.get_option("noplckeys"):
202       ravenlib.platform.all.ignore_keys("planetlab")
203
204    # get all of the keys from the platform library
205    glo_keylist = ravenlib.platform.all.get_keys()
206
207    # convert SSH keys to SSL keys
208    convert_keys(glo_keylist)
209
210    user_names = get_usernames(ignore_username)
211
212    # the --publickeyfile option is treated like an additional key and is added
213    # to the list.
214    publickeyfile = arizonaconfig.get_option("publickeyfile")
215    if publickeyfile:
216       if os.path.exists(publickeyfile):
217          glo_keylist.append({"kind": "ssl", "sslfn": publickeyfile})
218       else:
219          arizonareport.send_out(0, "WARNING: the publickey specified with --publickey, ")
220          arizonareport.send_out(0, publickeyfile + ", does not exist.")
221
222    glo_prefixlist = []
223
224    for key in glo_keylist:
225       (success, publickey_sl) = arizonacrypt.publickey_fn_to_sl(key["sslfn"])
226
227       if not success:
228          # Oops, error and tell them
229          arizonareport.send_error(0, "ERROR: Invalid or missing publickeyfile: '" + key["sslfn"] + "'")
230          arizonareport.send_error(0, "This error came from key: " + str(key))
231          continue
232
233       key["sl"] = publickey_sl
234
235       for username in user_names:
236           # add two entries to the keylist: one using the old filename embedded key format
237           # for the publickey, the other using the new system where the hash is embedded
238
239           publickey_string_oldformat = arizonacrypt.publickey_sl_to_fnstring_compat(publickey_sl)
240           if len(publickey_string_oldformat) == 126:
241              glo_prefixlist.append(make_prefix_dict(username, username + "." + publickey_string_oldformat, key))
242
243           publickey_hash = arizonacrypt.publickey_sl_to_fnstring(publickey_sl)
244           glo_prefixlist.append(make_prefix_dict(username, username + "." + publickey_hash, key))
245
246    arizonareport.send_out(4, "[DEBUG] glo_prefixlist = " + str(glo_prefixlist))
247
248    # complain if we didn't come up with a publickey anywhere that we can use
249    if not glo_prefixlist:
250       if tolerate_no_key:
251          arizonareport.send_out(0, "No public keys available (yet). Returning empty key database.")
252       else:
253          arizonareport.send_error(0, "No public keys available. Try '--publickeyfile'.")
254          sys.exit(1)
255
256    return glo_prefixlist
257
258 def build_key_database_from_single_keystring(username, pkstring):
259     global glo_keylist
260     global glo_prefixlist
261
262     glo_keylist = []
263
264     keydir = arizonaconfig.get_option("keydir")
265     publickey_sl = arizonacrypt.fnstring_to_publickey_sl(pkstring)
266     publickey_fn = os.path.join(keydir, "_single.publickey")
267
268     f = file(publickey_fn, "w")
269     for item in publickey_sl:
270        f.write(item.rstrip('\r\n')+'\n')
271     f.close()
272
273     key = {"kind": "ssl", "sslfn": publickey_fn, "sl": publickey_sl, "sslstr": pkstring}
274     glo_keylist = [key]
275
276     publickey_string_oldformat = arizonacrypt.publickey_sl_to_fnstring_compat(publickey_sl)
277     if len(publickey_string_oldformat) == 126:
278        glo_prefixlist.append(make_prefix_dict(username, username + "." + publickey_string_oldformat, key))
279
280     publickey_hash = arizonacrypt.publickey_sl_to_fnstring(publickey_sl)
281     glo_prefixlist = [make_prefix_dict(username, username + "." + publickey_hash, key)]
282
283     return glo_prefixlist
284
285 def dump_prefix(prefixdict):
286    arizonareport.send_out(0, "  " + prefixdict["prefix"] + ":")
287    arizonareport.send_out(0, "    username: " + prefixdict["username"])
288    arizonareport.send_out(0, "    key:")
289
290    keykeys = prefixdict["key"].keys()
291    keykeys.sort()
292    for dictkey in keykeys:
293        arizonareport.send_out(0, "      " + dictkey + ": " + str(prefixdict["key"][dictkey]))
294
295 def dump():
296    prefixes = build_key_database()
297
298    arizonareport.send_out(0, "")
299    arizonareport.send_out(0, "user names: ")
300    for username in get_usernames():
301       arizonareport.send_out(0, "  " + username)
302
303    arizonareport.send_out(0, "")
304    arizonareport.send_out(0, "key database: ")
305    for prefixdict in prefixes:
306       dump_prefix(prefixdict)
307       arizonareport.send_out(0, "")
308
309    arizonareport.send_out(0, "")
310    arizonareport.send_out(0, "default key (used when none of the above exist):")
311    prefixdict = build_default_prefix()
312    if prefixdict:
313       dump_prefix(prefixdict)
314    else:
315       arizonareport.send_out(0, "  None")
316
317
318