in get_auth_cred, record type is 'authority' not 'sa'
[sfa.git] / sfa / client / sfi.py
1 #! /usr/bin/env python
2
3 # sfi -- slice-based facility interface
4
5 import sys
6 import os, os.path
7 import tempfile
8 import traceback
9 from types import StringTypes, ListType
10 from optparse import OptionParser
11
12 from sfa.trust.certificate import Keypair, Certificate
13 from sfa.trust.credential import Credential
14
15 from sfa.util.geniclient import GeniClient
16 from sfa.util.record import *
17 from sfa.util.rspec import Rspec
18 from sfa.util.xmlrpcprotocol import ServerException
19 from sfa.util.config import Config
20
21 # xxx todo xxx auto-load ~/.sfi/sfi_config
22
23 sfi_dir = os.path.expanduser("~/.sfi/")
24 slicemgr = None
25 registry = None
26 user = None
27 authority = None
28 verbose = False
29
30 #
31 # Establish Connection to SliceMgr and Registry Servers
32 #
33 def set_servers(options):
34    global slicemgr
35    global registry
36    global user
37    global authority
38
39    config_file = sfi_dir + os.sep + "sfi_config"
40    try:
41       config = Config (config_file)
42    except:
43       print "Failed to read configuration file",config_file
44       print "Make sure to remove the export clauses and to add quotes"
45       if not options.verbose:
46          print "Re-run with -v for more details"
47       else:
48          traceback.print_exc()
49       sys.exit(1)
50
51    errors=0
52    # Set SliceMgr URL
53    if (options.sm is not None):
54       sm_url = options.sm
55    elif hasattr(config,"SFI_SM"):
56       sm_url = config.SFI_SM
57    else:
58       print "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s"%config_file
59       errors +=1 
60
61    # Set Registry URL
62    if (options.registry is not None):
63       reg_url = options.registry
64    elif hasattr(config,"SFI_REGISTRY"):
65       reg_url = config.SFI_REGISTRY
66    else:
67       print "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s"%config_file
68       errors +=1 
69
70    # Set user HRN
71    if (options.user is not None):
72       user = options.user
73    elif hasattr(config,"SFI_USER"):
74       user = config.SFI_USER
75    else:
76       print "You need to set e.g. SFI_USER='plc.princeton.username' in %s"%config_file
77       errors +=1 
78
79    # Set authority HRN
80    if (options.auth is not None):
81       authority = options.auth
82    elif hasattr(config,"SFI_AUTH"):
83       authority = config.SFI_AUTH
84    else:
85       print "You need to set e.g. SFI_AUTH='plc.princeton' in %s"%config_file
86       errors +=1 
87
88    if errors:
89       sys.exit(1)
90
91    if options.verbose :
92       print "Contacting Slice Manager at:", sm_url
93       print "Contacting Registry at:", reg_url
94
95    # Get key and certificate
96    key_file = get_key_file()
97    cert_file = get_cert_file(key_file)
98
99    # Establish connection to server(s)
100    slicemgr = GeniClient(sm_url, key_file, cert_file, options.protocol)
101    registry = GeniClient(reg_url, key_file, cert_file, options.protocol)
102    return
103
104 #
105 # Get various credential and spec files
106 #
107 # Establishes limiting conventions
108 #   - conflates MAs and SAs
109 #   - assumes last token in slice name is unique
110 #
111 # Bootstraps credentials
112 #   - bootstrap user credential from self-signed certificate
113 #   - bootstrap authority credential from user credential
114 #   - bootstrap slice credential from user credential
115 #
116
117 def get_leaf(name):
118    parts = name.split(".")
119    return parts[-1]
120
121 def get_key_file():
122    file = os.path.join(sfi_dir, get_leaf(user) + ".pkey")
123    if (os.path.isfile(file)):
124       return file
125    else:
126       print "Key file", file, "does not exist"
127       sys.exit(-1)
128    return
129
130 def get_cert_file(key_file):
131    global verbose
132
133    file = os.path.join(sfi_dir, get_leaf(user) + ".cert")
134    if (os.path.isfile(file)):
135       return file
136    else:
137       k = Keypair(filename = key_file)
138       cert = Certificate(subject=user)
139       cert.set_pubkey(k)
140       cert.set_issuer(k, user)
141       cert.sign()
142       if verbose :
143          print "Writing self-signed certificate to", file
144       cert.save_to_file(file)
145       return file
146
147 def get_user_cred():
148    global user
149
150    file = os.path.join(sfi_dir, get_leaf(user) + ".cred")
151    if (os.path.isfile(file)):
152       user_cred = Credential(filename=file)
153       return user_cred
154    else:
155       # bootstrap user credential
156       user_cred = registry.get_credential(None, "user", user)
157       if user_cred:
158          user_cred.save_to_file(file, save_parents=True)
159          if verbose:
160             print "Writing user credential to", file
161          return user_cred
162       else:
163          print "Failed to get user credential"
164          sys.exit(-1)
165
166 def get_auth_cred():
167    global authority
168
169    if not authority:
170       print "no authority specified. Use -a or set SF_AUTH"
171       sys.exit(-1)
172
173    file = os.path.join(sfi_dir, get_leaf("authority") +".cred")
174    if (os.path.isfile(file)):
175       auth_cred = Credential(filename=file)
176       return auth_cred
177    else:
178       # bootstrap authority credential from user credential
179       user_cred = get_user_cred()
180       auth_cred = registry.get_credential(user_cred, "authority", authority)
181       if auth_cred:
182          auth_cred.save_to_file(file, save_parents=True)
183          if verbose:
184             print "Writing authority credential to", file
185          return auth_cred
186       else:
187          print "Failed to get authority credential"
188          sys.exit(-1)
189
190 def get_slice_cred(name):
191    file = os.path.join(sfi_dir, "slice_" + get_leaf(name) + ".cred")
192    if (os.path.isfile(file)):
193       slice_cred = Credential(filename=file)
194       return slice_cred
195    else:
196       # bootstrap slice credential from user credential
197       user_cred = get_user_cred()
198       slice_cred = registry.get_credential(user_cred, "slice", name)
199       if slice_cred:
200          slice_cred.save_to_file(file, save_parents=True)
201          if verbose:
202             print "Writing slice credential to", file
203          return slice_cred
204       else:
205          print "Failed to get slice credential"
206          sys.exit(-1)
207
208 def delegate_cred(cred, hrn, type = 'authority'):
209     # the gid and hrn of the object we are delegating
210     object_gid = cred.get_gid_object()
211     object_hrn = object_gid.get_hrn()
212     cred.set_delegate(True)
213     if not cred.get_delegate():
214         raise Exception, "Error: Object credential %(object_hrn)s does not have delegate bit set" % locals()
215        
216
217     records = registry.resolve(cred, hrn)
218     records = filter_records(type, records)
219     
220     if not records:
221         raise Exception, "Error: Didn't find a %(type)s record for %(hrn)s" % locals()
222
223     # the gid of the user who will be delegated too
224     delegee_gid = records[0].get_gid_object()
225     delegee_hrn = delegee_gid.get_hrn()
226     
227     # the key and hrn of the user who will be delegating
228     user_key = Keypair(filename = get_key_file())
229     user_hrn = cred.get_gid_caller().get_hrn()
230
231     dcred = Credential(subject=object_hrn + " delegated to " + delegee_hrn)
232     dcred.set_gid_caller(delegee_gid)
233     dcred.set_gid_object(object_gid)
234     dcred.set_privileges(cred.get_privileges())
235     dcred.set_delegate(True)
236     dcred.set_pubkey(object_gid.get_pubkey())
237     dcred.set_issuer(user_key, user_hrn)
238     dcred.set_parent(cred)
239     dcred.encode()
240     dcred.sign()
241
242     return dcred
243
244 def get_rspec_file(rspec):
245    if (os.path.isabs(rspec)):
246       file = rspec
247    else:
248       file = os.path.join(sfi_dir, rspec)
249    if (os.path.isfile(file)):
250       return file
251    else:
252       print "No such rspec file", rspec
253       sys.exit(1)
254
255 def get_record_file(record):
256    if (os.path.isabs(record)):
257       file = record
258    else:
259       file = os.path.join(sfi_dir, record)
260    if (os.path.isfile(file)):
261       return file
262    else:
263       print "No such registry record file", record
264       sys.exit(1)
265
266 def load_publickey_string(fn):
267    f = file(fn,"r")
268    key_string = f.read()
269
270    # if the filename is a private key file, then extract the public key
271    if "PRIVATE KEY" in key_string:
272        outfn = tempfile.mktemp()
273        cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
274        os.system(cmd)
275        f = file(outfn, "r")
276        key_string = f.read()
277        os.remove(outfn)
278
279    return key_string
280 #
281 # Generate sub-command parser
282 #
283 def create_cmd_parser(command, additional_cmdargs = None):
284    cmdargs = {"list": "name",
285               "show": "name",
286               "remove": "name",
287               "add": "record",
288               "update": "record",
289               "slices": "",
290               "resources": "[name]",
291               "create": "name rspec",
292               "delete": "name",
293               "reset": "name",
294               "start": "name",
295               "stop": "name",
296               "delegate": "name"
297              }
298
299    if additional_cmdargs:
300       cmdargs.update(additional_cmdargs)
301
302    if command not in cmdargs:
303       print "Invalid command\n"
304       print "Commands: ",
305       for key in cmdargs.keys():
306           print key+",",
307       print ""
308       sys.exit(2)
309
310    parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
311       % (command, cmdargs[command]))
312
313    if command in ("resources"):
314       parser.add_option("-f", "--format", dest="format",type="choice",
315            help="display format (dns|ip|xml)",default="xml",
316            choices=("dns","ip","xml"))
317       
318    if command in ("list", "show", "remove"):
319       parser.add_option("-t", "--type", dest="type",type="choice",
320            help="type filter (user|slice|sa|ma|node|aggregate)",
321            choices=("user","slice","sa","ma","node","aggregate", "all"),
322            default="all")
323       
324    if command in ("resources","show", "list"):
325       parser.add_option("-o", "--output", dest="file",
326            help="output XML to file", metavar="FILE", default=None)
327
328    if command in ("show", "list"):
329         parser.add_option("-f", "--format", dest="format", type="choice",
330            help="display format (text|xml)",default="text",
331            choices=("text","xml"))
332         
333    if command in ("delegate"):
334       parser.add_option("-u", "--user",
335         action="store_true", dest="delegate_user", default=False,
336         help="delegate user credential")
337       parser.add_option("-s", "--slice", dest="delegate_slice",
338         help="delegate slice credential", metavar="HRN", default=None)
339    return parser
340
341 def create_parser():
342    # Generate command line parser
343    parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
344         description="Commands: list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
345    parser.add_option("-r", "--registry", dest="registry",
346         help="root registry", metavar="URL", default=None)
347    parser.add_option("-s", "--slicemgr", dest="sm",
348         help="slice manager", metavar="URL", default=None)
349    parser.add_option("-d", "--dir", dest="dir",
350         help="working directory", metavar="PATH", default = sfi_dir)
351    parser.add_option("-u", "--user", dest="user",
352         help="user name", metavar="HRN", default=None)
353    parser.add_option("-a", "--auth", dest="auth",
354         help="authority name", metavar="HRN", default=None)
355    parser.add_option("-v", "--verbose",
356         action="store_true", dest="verbose", default=False,
357         help="verbose mode")
358    parser.add_option("-p", "--protocol",
359         dest="protocol", default="xmlrpc",
360         help="RPC protocol (xmlrpc or soap)")
361    parser.disable_interspersed_args()
362
363    return parser
364
365 def dispatch(command, cmd_opts, cmd_args):
366    globals()[command](cmd_opts, cmd_args)
367
368 #
369 # Main: parse arguments and dispatch to command
370 #
371 def main():
372    global verbose
373
374    parser = create_parser()
375    (options, args) = parser.parse_args()
376
377    if len(args) <= 0:
378         print "No command given. Use -h for help."
379         return -1
380
381    command = args[0]
382    (cmd_opts, cmd_args) = create_cmd_parser(command).parse_args(args[1:])
383    verbose = options.verbose
384    if verbose :
385       print "Resgistry %s, sm %s, dir %s, user %s, auth %s" % (options.registry,
386                                                                options.sm,
387                                                                options.dir,
388                                                                options.user,
389                                                                options.auth)
390       print "Command %s" %command
391       if command in ("resources"):
392          print "resources cmd_opts %s" %cmd_opts.format
393       elif command in ("list","show","remove"):
394          print "cmd_opts.type %s" %cmd_opts.type
395       print "cmd_args %s" %cmd_args
396
397    set_servers(options)
398
399    try:
400       dispatch(command, cmd_opts, cmd_args)
401    except KeyError:
402       raise 
403       print "Command not found:", command
404       sys.exit(1)
405
406    return
407
408 #
409 # Following functions implement the commands
410 #
411 # Registry-related commands
412 #
413
414 # list entires in named authority registry
415 def list(opts, args):
416    global registry
417    user_cred = get_user_cred()
418    try:
419       list = registry.list(user_cred, args[0])
420    except IndexError:
421       raise Exception, "Not enough parameters for the 'list' command"
422       
423    # filter on person, slice, site, node, etc.  
424    # THis really should be in the filter_records funct def comment...
425    list = filter_records(opts.type, list)
426    for record in list:
427        print "%s (%s)" % (record['hrn'], record['type'])     
428    if opts.file:
429        save_records_to_file(opts.file, list)
430    return
431
432 # show named registry record
433 def show(opts, args):
434    global registry
435    user_cred = get_user_cred()
436    records = registry.resolve(user_cred, args[0])
437    records = filter_records(opts.type, records)
438    if not records:
439       print "No record of type", opts.type
440    for record in records:
441        if record['type'] in ['user']:
442            record = UserRecord(dict = record)
443        elif record['type'] in ['slice']:
444            record = SliceRecord(dict = record)
445        elif record['type'] in ['node']:
446            record = NodeRecord(dict = record)
447        elif record['type'] in ['authority', 'ma', 'sa']:
448            record = AuthorityRecord(dict = record)
449        else:
450            record = GeniRecord(dict = record)
451
452        if (opts.format=="text"):
453             record.dump() 
454        else:
455             print record.save_to_string()
456    
457    if opts.file:
458        save_records_to_file(opts.file, records)
459    return
460
461 def delegate(opts, args):
462    global registry
463    user_cred = get_user_cred()
464    if opts.delegate_user:
465        object_cred = user_cred
466    elif opts.delegate_slice:
467        object_cred = get_slice_cred(opts.delegate_slice)
468    else:
469        print "Must specify either --user or --slice <hrn>"
470        return
471
472    # the gid and hrn of the object we are delegating
473    object_gid = object_cred.get_gid_object()
474    object_hrn = object_gid.get_hrn()
475
476    if not object_cred.get_delegate():
477        print "Error: Object credential", object_hrn, "does not have delegate bit set"
478        return
479
480    records = registry.resolve(user_cred, args[0])
481    records = filter_records("user", records)
482
483    if not records:
484        print "Error: Didn't find a user record for", args[0]
485        return
486
487    # the gid of the user who will be delegated too
488    delegee_gid = records[0].get_gid_object()
489    delegee_hrn = delegee_gid.get_hrn()
490
491    # the key and hrn of the user who will be delegating
492    user_key = Keypair(filename = get_key_file())
493    user_hrn = user_cred.get_gid_caller().get_hrn()
494
495    dcred = Credential(subject=object_hrn + " delegated to " + delegee_hrn)
496    dcred.set_gid_caller(delegee_gid)
497    dcred.set_gid_object(object_gid)
498    dcred.set_privileges(object_cred.get_privileges())
499    dcred.set_delegate(True)
500    dcred.set_pubkey(object_gid.get_pubkey())
501    dcred.set_issuer(user_key, user_hrn)
502    dcred.set_parent(object_cred)
503    dcred.encode()
504    dcred.sign()
505
506    if opts.delegate_user:
507        dest_fn = os.path.join(sfi_dir, get_leaf(delegee_hrn) + "_" + get_leaf(object_hrn) + ".cred")
508    elif opts.delegate_slice:
509        dest_fn = os.path_join(sfi_dir, get_leaf(delegee_hrn) + "_slice_" + get_leaf(object_hrn) + ".cred")
510
511    dcred.save_to_file(dest_fn, save_parents = True)
512
513    print "delegated credential for", object_hrn, "to", delegee_hrn, "and wrote to", dest_fn
514
515 # removed named registry record
516 #   - have to first retrieve the record to be removed
517 def remove(opts, args):
518    global registry
519    auth_cred = get_auth_cred()
520    return registry.remove(auth_cred, opts.type, args[0])
521
522 # add named registry record
523 def add(opts, args):
524    global registry
525    auth_cred = get_auth_cred()
526    rec_file = get_record_file(args[0])
527    record = load_record_from_file(rec_file)
528
529    return registry.register(auth_cred, record)
530
531 # update named registry entry
532 def update(opts, args):
533    global registry
534    user_cred = get_user_cred()
535    rec_file = get_record_file(args[0])
536    record = load_record_from_file(rec_file)
537    if record.get_type() == "user":
538        if record.get_name() == user_cred.get_gid_object().get_hrn():
539           cred = user_cred
540        else:
541           cred = get_auth_cred()
542    elif record.get_type() in ["slice"]:
543        try:
544            cred = get_slice_cred(record.get_name())
545        except ServerException, e:
546            # XXX smbaker -- once we have better error return codes, update this
547            # to do something better than a string compare
548            if "Permission error" in e.args[0]:
549                cred = get_auth_cred()
550            else:
551                raise
552    elif record.get_type() in ["authority"]:
553        cred = get_auth_cred()
554    elif record.get_type() == 'node':
555         cred = get_auth_cred()
556    else:
557        raise "unknown record type" + record.get_type()
558    return registry.update(cred, record)
559
560 #
561 # Slice-related commands
562 #
563
564 # list available nodes -- now use 'resources' w/ no argument instead
565 #def nodes(opts, args):
566 #   global slicemgr
567 #   user_cred = get_user_cred() 
568 #   if not opts.format:
569 #      context = None
570 #   else:
571 #      context = opts.format
572 #   results = slicemgr.list_nodes(user_cred)
573 #   if opts.format in ['rspec']:     
574 #      display_rspec(results)
575 #   else:
576 #      display_list(results)
577 #   if (opts.file is not None):
578 #      rspec = slicemgr.list_nodes(user_cred)
579 #      save_rspec_to_file(rspec, opts.file)
580 #   return
581
582 # list instantiated slices
583 def slices(opts, args):
584    global slicemgr
585    user_cred = get_user_cred()
586    results = slicemgr.get_slices(user_cred)
587    display_list(results)
588    return
589
590 # show rspec for named slice
591 def resources(opts, args):
592    global slicemgr
593    if args:
594        slice_cred = get_slice_cred(args[0])
595        result = slicemgr.get_resources(slice_cred, args[0])
596    else:
597        user_cred = get_user_cred()
598        result = slicemgr.get_resources(user_cred)
599    format = opts.format      
600    display_rspec(result, format)
601    if (opts.file is not None):
602       save_rspec_to_file(result, opts.file)
603    return
604
605 # created named slice with given rspec
606 def create(opts, args):
607    global slicemgr
608    slice_hrn = args[0]
609    slice_cred = get_slice_cred(slice_hrn)
610    rspec_file = get_rspec_file(args[1])
611    rspec=open(rspec_file).read()
612    return slicemgr.create_slice(slice_cred, slice_hrn, rspec)
613
614 # delete named slice
615 def delete(opts, args):
616    global slicemgr
617    slice_hrn = args[0]
618    slice_cred = get_slice_cred(slice_hrn)
619    
620    return slicemgr.delete_slice(slice_cred, slice_hrn)
621
622 # start named slice
623 def start(opts, args):
624    global slicemgr
625    slice_hrn = args[0]
626    slice_cred = get_slice_cred(args[0])
627    return slicemgr.start_slice(slice_cred, slice_hrn)
628
629 # stop named slice
630 def stop(opts, args):
631    global slicemgr
632    slice_hrn = args[0]
633    slice_cred = get_slice_cred(args[0])
634    return slicemgr.stop_slice(slice_cred, slice_hrn)
635
636 # reset named slice
637 def reset(opts, args):
638    global slicemgr
639    slice_hrn = args[0]
640    slice_cred = get_slice_cred(args[0])
641    return slicemgr.reset_slice(slice_cred, slice_hrn)
642
643 #
644 #
645 # Display, Save, and Filter RSpecs and Records
646 #   - to be replace by EMF-generated routines
647 #
648 #
649
650 def display_rspec(rspec, format = 'rspec'):
651     if format in ['dns']:
652         spec = Rspec()
653         spec.parseString(rspec)
654         hostnames = []
655         nodespecs = spec.getDictsByTagName('NodeSpec')
656         for nodespec in nodespecs:
657             if nodespec.has_key('name') and nodespec['name']:
658                 if isinstance(nodespec['name'], ListType):
659                     hostnames.extend(nodespec['name'])
660                 elif isinstance(nodespec['name'], StringTypes):
661                     hostnames.append(nodespec['name'])
662         result = hostnames
663     elif format in ['ip']:
664         spec = Rspec()
665         spec.parseString(rspec)
666         ips = []
667         ifspecs = spec.getDictsByTagName('IfSpec')
668         for ifspec in ifspecs:
669             if ifspec.has_key('addr') and ifspec['addr']:
670                 ips.append(ifspec['addr'])
671         result = ips 
672     else:     
673         result = rspec
674
675     print result
676     return
677
678 def display_list(results):
679     for result in results:
680         print result
681
682 def save_rspec_to_file(rspec, filename):
683    if not filename.startswith(os.sep):
684        filename = sfi_dir + filename
685    if not filename.endswith(".rspec"):
686        filename = filename + ".rspec"
687
688    f = open(filename, 'w')
689    f.write(rspec)
690    f.close()
691    return
692
693 def display_records(recordList, dump = False):
694    ''' Print all fields in the record'''
695    for record in recordList:
696       display_record(record, dump)
697
698 def display_record(record, dump = False):
699    if dump:
700        record.dump()
701    else:
702        info = record.getdict()
703        print "%s (%s)" % (info['hrn'], info['type'])
704    return
705
706 def filter_records(type, records):
707    filtered_records = []
708    for record in records:
709        if (record.get_type() == type) or (type == "all"):
710            filtered_records.append(record)
711    return filtered_records
712
713 def save_records_to_file(filename, recordList):
714    index = 0
715    for record in recordList:
716        if index>0:
717            save_record_to_file(filename + "." + str(index), record)
718        else:
719            save_record_to_file(filename, record)
720        index = index + 1
721
722 def save_record_to_file(filename, record):
723    if not filename.startswith(os.sep):
724        filename = sfi_dir + filename
725    str = record.save_to_string()
726    file(filename, "w").write(str)
727    return
728
729 def load_record_from_file(filename):
730    str = file(filename, "r").read()
731    record = GeniRecord(string=str)
732    return record
733
734 if __name__=="__main__":
735    main()