all: $(ALL)
-ple: auto-ple-reg auto-ple-sa-lr.out
+ple: auto-ple-reg auto-ple-sa-lr
####################
define bundle_scan_target
%define name sfa
%define version 2.1
-%define taglevel 17
+%define taglevel 20
%define release %{taglevel}%{?pldistro:.%{pldistro}}%{?date:.%{date}}
%global python_sitearch %( python -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)" )
[ "$1" -ge "1" ] && service sfa-cm restart || :
%changelog
+* Mon Dec 03 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-2.1-20
+- fix 2 major bugs in PL importer
+- esp. wrt GID management against PLC key
+
+* Wed Nov 28 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-2.1-19
+- nicer sfi delegate, can handle multiple delegations and for authorities(pi) as well
+
+* Wed Nov 28 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-2.1-18
+- support fordelegation in sfaclientlib
+- sfi delegate fixed
+- other delegation-related sfi option trashed
+- new config (based on ini format)
+- new dummy driver and related package
+- pl importer has more explicit error messages
+- credential dump shows expiration
+
* Tue Oct 16 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sfa-2.1-17
- bugfix in forwarding Resolve requests
- various fixes in the nitos driver wrt keys and users
# see optimizing dependencies below
from sfa.trust.certificate import Keypair, Certificate
from sfa.trust.credential import Credential
+from sfa.trust.gid import GID
##########
# a helper class to implement the bootstrapping of crypto. material
# assuming we are starting from scratch on the client side
# the usage model is to reuse an existing keypair)
#
# there might be a more portable, i.e. less language-dependant way, to
-# implement this step by exec'ing the openssl command a known
-# successful attempt at this approach that worked for Java is
-# documented below
+# implement this step by exec'ing the openssl command.
+# a known successful attempt at this approach that worked
+# for Java is documented below
# http://nam.ece.upatras.gr/fstoolkit/trac/wiki/JavaSFAClient
#
####################
def private_key (self):
self.assert_private_key()
return self.private_key_filename()
+
+ def delegate_credential_string (self, original_credential, to_hrn, to_type='authority'):
+ """
+ sign a delegation credential to someone else
+
+ original_credential : typically one's user- or slice- credential to be delegated to s/b else
+ to_hrn : the hrn of the person that will be allowed to do stuff on our behalf
+ to_type : goes with to_hrn, usually 'user' or 'authority'
+
+ returns a string with the delegated credential
+
+ this internally uses self.my_gid()
+ it also retrieves the gid for to_hrn/to_type
+ and uses Credential.delegate()"""
+
+ # the gid and hrn of the object we are delegating
+ if isinstance (original_credential, str):
+ original_credential = Credential (string=original_credential)
+ original_gid = original_credential.get_gid_object()
+ original_hrn = original_gid.get_hrn()
+
+ if not original_credential.get_privileges().get_all_delegate():
+ self.logger.error("delegate_credential_string: original credential %s does not have delegate bit set"%original_hrn)
+ return
+
+ # the delegating user's gid
+ my_gid = self.my_gid()
+
+ # retrieve the GID for the entity that we're delegating to
+ to_gidfile = self.gid (to_hrn,to_type)
+# to_gid = GID ( to_gidfile )
+# to_hrn = delegee_gid.get_hrn()
+# print 'to_hrn',to_hrn
+ delegated_credential = original_credential.delegate(to_gidfile, self.private_key(), my_gid)
+ return delegated_credential.save_to_string(save_parents=True)
("version", ""),
("list", "authority"),
("show", "name"),
- ("add", "record"),
- ("update", "record"),
+ ("add", "[record]"),
+ ("update", "[record]"),
("remove", "name"),
("slices", ""),
("resources", "[slice_hrn]"),
("shutdown", "slice_hrn"),
("get_ticket", "slice_hrn rspec"),
("redeem_ticket", "ticket"),
- ("delegate", "name"),
+ ("delegate", "to_hrn"),
("gid", "[name]"),
("trusted", "cred"),
("config", ""),
action="callback", callback=optparse_dictvalue_callback, nargs=1,
help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
- # user specifies remote aggregate/sm/component
- if command in ("resources", "slices", "create", "delete", "start", "stop",
- "restart", "shutdown", "get_ticket", "renew", "status"):
- parser.add_option("-d", "--delegate", dest="delegate", default=None,
- action="store_true",
- help="Include a credential delegated to the user's root"+\
- "authority in set of credentials for this call")
-
# show_credential option
if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
help="gives details, like user keys", default=False)
if command in ("delegate"):
parser.add_option("-u", "--user",
- action="store_true", dest="delegate_user", default=False,
- help="delegate user credential")
- parser.add_option("-s", "--slice", dest="delegate_slice",
- help="delegate slice credential", metavar="HRN", default=None)
+ action="store_true", dest="delegate_user", default=False,
+ help="delegate your own credentials; default if no other option is provided")
+ parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
+ metavar="slice_hrn", help="delegate cred. for slice HRN")
+ parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
+ metavar='auth_hrn', help="delegate cred for auth HRN")
+ # this primarily is a shorthand for -a my_hrn^
+ parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
+ help="delegate your PI credentials, so s.t. like -a your_hrn^")
+ parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
+ help="""by default the mandatory argument is expected to be a user,
+use this if you mean an authority instead""")
if command in ("version"):
parser.add_option("-R","--registry-version",
sys.exit(-1)
return self.client_bootstrap.authority_credential_string (self.authority)
+ def authority_credential_string(self, auth_hrn):
+ return self.client_bootstrap.authority_credential_string (auth_hrn)
+
def slice_credential_string(self, name):
return self.client_bootstrap.slice_credential_string (name)
- # xxx should be supported by sfaclientbootstrap as well
- def delegate_cred(self, object_cred, hrn, type='authority'):
- # the gid and hrn of the object we are delegating
- if isinstance(object_cred, str):
- object_cred = Credential(string=object_cred)
- object_gid = object_cred.get_gid_object()
- object_hrn = object_gid.get_hrn()
-
- if not object_cred.get_privileges().get_all_delegate():
- self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
- return
-
- # the delegating user's gid
- caller_gidfile = self.my_gid()
-
- # the gid of the user who will be delegated to
- delegee_gid = self.client_bootstrap.gid(hrn,type)
- delegee_hrn = delegee_gid.get_hrn()
- dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
- return dcred.save_to_string(save_parents=True)
-
#
# Management of the servers
#
return
def add(self, options, args):
- "add record into registry from xml file (Register)"
+ "add record into registry by using the command options (Recommended) or from xml file (Register)"
auth_cred = self.my_authority_credential_string()
if options.show_credential:
show_credentials(auth_cred)
return self.registry().Register(record_dict, auth_cred)
def update(self, options, args):
- "update record into registry from xml file (Update)"
+ "update record into registry by using the command options (Recommended) or from xml file (Update)"
record_dict = {}
if len(args) > 0:
record_filepath = args[0]
server = self.sliceapi()
# creds
creds = [self.my_credential_string]
- if options.delegate:
- delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
- creds.append(delegated_cred)
# options and call_id when supported
api_options = {}
api_options['call_id']=unique_call_id()
# set creds
creds = []
if args:
- creds.append(self.slice_credential_string(args[0]))
+ the_credential=self.slice_credential_string(args[0])
+ creds.append(the_credential)
else:
- creds.append(self.my_credential_string)
- if options.delegate:
- creds.append(self.delegate_cred(cred, get_authority(self.authority)))
+ the_credential=self.my_credential_string
+ creds.append(the_credential)
if options.show_credential:
show_credentials(creds)
# creds
slice_cred = self.slice_credential_string(slice_hrn)
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
# options and call_id when supported
api_options = {}
# creds
slice_cred = self.slice_credential_string(slice_hrn)
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
# options and call_id when supported
api_options = {}
# cred
slice_cred = self.slice_credential_string(args[0])
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
# xxx Thierry - does this not need an api_options as well ?
result = server.Start(slice_urn, creds)
value = ReturnValue.get_value(result)
# cred
slice_cred = self.slice_credential_string(args[0])
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
result = server.Stop(slice_urn, creds)
value = ReturnValue.get_value(result)
if self.options.raw:
# cred
slice_cred = self.slice_credential_string(args[0])
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
result = server.reset_slice(creds, slice_urn)
value = ReturnValue.get_value(result)
if self.options.raw:
# creds
slice_cred = self.slice_credential_string(args[0])
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
# options and call_id when supported
api_options = {}
api_options['call_id']=unique_call_id()
# creds
slice_cred = self.slice_credential_string(slice_hrn)
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
result = server.Shutdown(slice_urn, creds)
value = ReturnValue.get_value(result)
if self.options.raw:
# creds
slice_cred = self.slice_credential_string(slice_hrn)
creds = [slice_cred]
- if options.delegate:
- delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
- creds.append(delegated_cred)
# rspec
rspec_file = self.get_rspec_file(rspec_path)
rspec = open(rspec_file).read()
GID(string=gid).save_to_file(filename)
- def delegate(self, options, args):
+ def delegate (self, options, args):
"""
(locally) create delegate credential for use by given hrn
"""
- delegee_hrn = args[0]
- if options.delegate_user:
- cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
- elif options.delegate_slice:
- slice_cred = self.slice_credential_string(options.delegate_slice)
- cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
- else:
- self.logger.warning("Must specify either --user or --slice <hrn>")
- return
- delegated_cred = Credential(string=cred)
- object_hrn = delegated_cred.get_gid_object().get_hrn()
+ if len(args) != 1:
+ self.print_help()
+ sys.exit(1)
+ to_hrn = args[0]
+ # support for several delegations in the same call
+ # so first we gather the things to do
+ tuples=[]
+ for slice_hrn in options.delegate_slices:
+ message="%s.slice"%slice_hrn
+ original = self.slice_credential_string(slice_hrn)
+ tuples.append ( (message, original,) )
+ if options.delegate_pi:
+ my_authority=self.authority
+ message="%s.pi"%my_authority
+ original = self.my_authority_credential_string()
+ tuples.append ( (message, original,) )
+ for auth_hrn in options.delegate_auths:
+ message="%s.auth"%auth_hrn
+ original=self.authority_credential_string(auth_hrn)
+ tuples.append ( (message, original, ) )
+ # if nothing was specified at all at this point, let's assume -u
+ if not tuples: options.delegate_user=True
+ # this user cred
if options.delegate_user:
- dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
- + get_leaf(object_hrn) + ".cred")
- elif options.delegate_slice:
- dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
- + get_leaf(object_hrn) + ".cred")
-
- delegated_cred.save_to_file(dest_fn, save_parents=True)
-
- self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
+ message="%s.user"%self.user
+ original = self.my_credential_string
+ tuples.append ( (message, original, ) )
+
+ # default type for beneficial is user unless -A
+ if options.delegate_to_authority: to_type='authority'
+ else: to_type='user'
+
+ # let's now handle all this
+ # it's all in the filenaming scheme
+ for (message,original) in tuples:
+ delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
+ delegated_credential = Credential (string=delegated_string)
+ filename = os.path.join ( self.options.sfi_dir,
+ "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
+ delegated_credential.save_to_file(filename, save_parents=True)
+ self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
def trusted(self, options, args):
"""
#!/usr/bin/python
import sys
+from datetime import datetime
from sfa.util.xrn import get_authority, hrn_to_urn
from sfa.generic import Generic
generic=Generic.the_flavour()
importer_class = generic.importer_class()
if importer_class:
- self.logger.info ("Using flavour %s for importing (class %s)"%\
- (generic.flavour,importer_class.__name__))
+ begin_time=datetime.now()
+ self.logger.info ("Starting import on %s, using class %s from flavour %s"%\
+ (begin_time,importer_class.__name__,generic.flavour))
testbed_importer = importer_class (auth_hierarchy, self.logger)
if testbed_importer:
testbed_importer.add_options(options)
testbed_importer.run (options)
+ end_time=datetime.now()
+ duration=end_time-begin_time
+ self.logger.info("Import took %s"%duration)
['person_id', 'email', 'key_ids', 'site_ids', 'role_ids'])
# create a hash of persons by person_id
persons_by_id = dict ( [ ( person['person_id'], person) for person in persons ] )
+ # also gather non-enabled user accounts so as to issue relevant warnings
+ disabled_persons = shell.GetPersons({'peer_id': None, 'enabled': False}, ['person_id'])
+ disabled_person_ids = [ person['person_id'] for person in disabled_persons ]
# Get all plc public keys
# accumulate key ids for keys retrieval
key_ids = []
self.remember_record (node_record)
except:
self.logger.log_exc("PlImporter: failed to import node %s"%node_hrn)
+ continue
else:
# xxx update the record ...
pass
site_pis=[]
# import persons
for person_id in site['person_ids']:
- try:
- person = persons_by_id[person_id]
- except:
- self.logger.warning ("PlImporter: cannot locate person_id %s - ignored"%person_id)
+ proceed=False
+ if person_id in persons_by_id:
+ person=persons_by_id[person_id]
+ proceed=True
+ elif person_id in disabled_person_ids:
+ pass
+ else:
+ self.logger.warning ("PlImporter: cannot locate person_id %s in site %s - ignored"%(person_id,site_hrn))
+ # make sure to NOT run this if anything is wrong
+ if not proceed: continue
+
person_hrn = email_to_hrn(site_hrn, person['email'])
# xxx suspicious again
if len(person_hrn) > 64: person_hrn = person_hrn[:64]
person_gid = self.auth_hierarchy.create_gid(person_urn, create_uuid(), pkey)
person_gid.set_email(person['email'])
user_record = RegUser (hrn=person_hrn, gid=person_gid,
- pointer=person['person_id'],
- authority=get_authority(person_hrn),
- email=person['email'])
+ pointer=person['person_id'],
+ authority=get_authority(person_hrn),
+ email=person['email'])
if pubkey:
user_record.reg_keys=[RegKey (pubkey['key'], pubkey['key_id'])]
else:
self.remember_record ( user_record )
else:
# update the record ?
- # if user's primary key has changed then we need to update the
+ #
+ # if a user key has changed then we need to update the
# users gid by forcing an update here
+ #
+ # right now, SFA only has *one* key attached to a user, and this is
+ # the key that the GID was made with
+ # so the logic here is, we consider that things are OK (unchanged) if
+ # all the SFA keys are present as PLC keys
+ # otherwise we trigger the creation of a new gid from *some* plc key
+ # and record this on the SFA side
+ # it would make sense to add a feature in PLC so that one could pick a 'primary'
+ # key but this is not available on the myplc side for now
+ # = or = it would be much better to support several keys in SFA but that
+ # does not seem doable without a major overhaul in the data model as
+ # a GID is attached to a hrn, but it's also linked to a key, so...
+ # NOTE: with this logic, the first key entered in PLC remains the one
+ # current in SFA until it is removed from PLC
sfa_keys = user_record.reg_keys
- def key_in_list (key,sfa_keys):
- for reg_key in sfa_keys:
- if reg_key.key==key['key']: return True
+ def sfa_key_in_list (sfa_key,plc_keys):
+ for plc_key in plc_keys:
+ if plc_key['key']==sfa_key.key:
+ return True
return False
- # is there a new key in myplc ?
+ # are all the SFA keys known to PLC ?
new_keys=False
- for key in plc_keys:
- if not key_in_list (key,sfa_keys):
- new_keys = True
+ if not sfa_keys and plc_keys:
+ new_keys=True
+ else:
+ for sfa_key in sfa_keys:
+ if not sfa_key_in_list (sfa_key,plc_keys):
+ new_keys = True
if new_keys:
(pubkey,pkey) = init_person_key (person, plc_keys)
person_gid = self.auth_hierarchy.create_gid(person_urn, create_uuid(), pkey)
+ person_gid.set_email(person['email'])
if not pubkey:
user_record.reg_keys=[]
else:
user_record.reg_keys=[ RegKey (pubkey['key'], pubkey['key_id'])]
+ user_record.gid = person_gid
self.logger.info("PlImporter: updated person: %s" % user_record)
user_record.email = person['email']
dbsession.commit()
self.logger.log_exc("PlImporter: failed to import person %d %s"%(person['person_id'],person['email']))
# maintain the list of PIs for a given site
+ # for the record, Jordan had proposed the following addition as a welcome hotfix to a previous version:
+ # site_pis = list(set(site_pis))
+ # this was likely due to a bug in the above logic, that had to do with disabled persons
+ # being improperly handled, and where the whole loop on persons
+ # could be performed twice with the same person...
+ # so hopefully we do not need to eliminate duplicates explicitly here anymore
site_record.reg_pis = site_pis
dbsession.commit()
self.logger.log_exc("PlImporter: failed to import slice %s (%s)"%(slice_hrn,slice['name']))
else:
# xxx update the record ...
- self.logger.warning ("Slice update not yet implemented on slice %s (%s)"%(slice_hrn,slice['name']))
+ # given that we record the current set of users anyways, there does not seem to be much left to do here
+ # self.logger.warning ("Slice update not yet implemented on slice %s (%s)"%(slice_hrn,slice['name']))
pass
# record current users affiliated with the slice
slice_record.reg_researchers = \
record_dicts = record_list
# if we still have not found the record yet, try the local registry
+# logger.debug("before trying local records, %d foreign records"% len(record_dicts))
if not record_dicts:
recursive = False
if ('recursive' in options and options['recursive']):
if not api.auth.hierarchy.auth_exists(hrn):
raise MissingAuthority(hrn)
if recursive:
- records = dbsession.query(RegRecord).filter(RegRecord.hrn.startswith(hrn))
+ records = dbsession.query(RegRecord).filter(RegRecord.hrn.startswith(hrn)).all()
+# logger.debug("recursive mode, found %d local records"%(len(records)))
else:
- records = dbsession.query(RegRecord).filter_by(authority=hrn)
+ records = dbsession.query(RegRecord).filter_by(authority=hrn).all()
+# logger.debug("non recursive mode, found %d local records"%(len(records)))
# so that sfi list can show more than plain names...
for record in records: augment_with_sfa_builtins (record)
record_dicts=[ record.todict(exclude_types=[InstrumentedList]) for record in records ]
# is there a change in keys ?
new_key=None
if type=='user':
- if getattr(new_key,'keys',None):
+ if getattr(new_record,'keys',None):
new_key=new_record.keys
if isinstance (new_key,types.ListType):
new_key=new_key[0]
urn = hrn_to_urn(hrn,type)
gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
gid = gid_object.save_to_string(save_parents=True)
- record.gid = gid
- dsession.commit()
# xxx should do side effects from new_record to record
# not too sure how to do that
if isinstance (record, RegSlice):
researcher_hrns = getattr(new_record,'researcher',None)
if researcher_hrns is not None: record.update_researchers (researcher_hrns)
- dbsession.commit()
elif isinstance (record, RegAuthority):
pi_hrns = getattr(new_record,'pi',None)
if pi_hrns is not None: record.update_pis (pi_hrns)
- dbsession.commit()
# update the PLC information that was specified with the record
# xxx oddly enough, without this useless statement,
# anyway the driver should receive an object
# (and then extract __dict__ itself if needed)
print "DO NOT REMOVE ME before driver.update, record=%s"%record
- if not self.driver.update (record.__dict__, new_record.__dict__, hrn, new_key):
- logger.warning("driver.update failed")
-
+ (pointer, new_key_pointer) = self.driver.update (record.__dict__, new_record.__dict__, hrn, new_key)
+ if new_key and new_key_pointer:
+ record.reg_keys=[ RegKey (new_key, new_key_pointer)]
+ record.gid = gid
+
+ dbsession.commit();
# update membership for researchers, pis, owners, operators
self.update_driver_relations (record, new_record)
def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
pointer = old_sfa_record['pointer']
type = old_sfa_record['type']
+ new_key_pointer = None
# new_key implemented for users only
if new_key and type not in [ 'user' ]:
keys = person['key_ids']
keys = self.shell.GetKeys(person['key_ids'])
- # Delete all stale keys
key_exists = False
for key in keys:
- if new_key != key['key']:
- self.shell.DeleteKey(key['key_id'])
- else:
+ if new_key == key['key']:
key_exists = True
+ new_key_pointer = key['key_id']
+ break
if not key_exists:
- self.shell.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
+ new_key_pointer = self.shell.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
elif type == "node":
self.shell.UpdateNode(pointer, new_sfa_record)
- return True
+ return (pointer, new_key_pointer)
##########
# Call out to xmlsec1 to sign it
ref = 'Sig_%s' % self.get_refid()
filename = self.save_to_random_tmp_file()
- signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
- % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read()
+ command='%s --sign --node-id "%s" --privkey-pem %s,%s %s' \
+ % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)
+# print 'command',command
+ signed = os.popen(command).read()
os.remove(filename)
for gid_file in gid_files:
print " gidIssuer:"
self.get_signature().get_issuer_gid().dump(8, dump_parents)
+ if self.expiration:
+ print " expiration:", self.expiration.isoformat()
+
gidObject = self.get_gid_object()
if gidObject:
result += " gidObject:\n"
def __call__(self, *args, **kwds):
"""
- Main entry point for all SfaAPI functions. Type checks
+ Main entry point for all SFA API functions. Type checks
arguments, authenticates, and executes call().
"""