-
Notifications
You must be signed in to change notification settings - Fork 6
ABAC Guard Details
This page documents how ABAC-based guards on method invocations on GENI Federation Services (MA, SA, Logging, Credential Store) are implemented.
The GENI Federation Services (also known as the Clearinghouse) consist of a set of methods served up at XMLRPC end points. Each method invocation is checked by policy to ensure that the caller of the method is allowed to make that call and receive its results based on the context (arguments, options) of that call.
The rules that determine whether a given caller is authorized to make the call are captured in ABAC credentials and computed using the ABAC logic engine.
To first order, the process of determining whether a user is authorized to make a given call consists of:
- Collecting a set of assertions about the caller both with respect to and independent of the arguments to the call
- Collecting a set of policies about what assertions/attributes are required of a caller of a particular method
- These two collections are provided to the ABAC method who seeks to prove whether the caller is permitted to make this call in this context.
- If the call is authorized, the call is made and result is returned. If not, an !AuthorizationError is raised.
ABAC consists of a set of credentials and a prover. The credentials are a set of signed statements that the signer asserts a relationship between an object and a set.
A typical credential looks like:
SIGNER.SET<-OBJECT # "SIGNER asserts that OBJECT is a member of SET".
or
SIGNER1.SET1<-SIGNER2.SET2 # "SIGNER asserts that anyone that SIGNER2 asserts is a member of SET2 is a member of SET1".
ABAC credentials can be assertions ("I claim this IS true") and policies ("I assert this MUST be true for authorization") but both are of the same essential format.
By convention, we use ME as the name of the current authority (assertions made by me), though any other name could be used instead.
The first step of authorization is extracting a set of 'subjects' from the method invocation. These are extracted from a series of sources:
- options[match] : Any explicitly named object (referenced by ID or URN) in the options('match'] argument (for lookup)
- options[fields] : Any explicitly named object (referenced by ID or URN) in the options('fields'] argument (for update, delete, create)
- arguments: All method arguments are collapsed into a {name : value…} dictionary at the start of the invocation, and by name these may be extracted as subjects (e.g. the update method takes a 'urn' argument)
The following is the set of subject types extracted from the method context:
- MEMBER_URN: The URN of the member(s) arguments of the invocation
- SLICE_URN: The URN of the slice(s) arguments of the invocation
- PROJECT_URN: The URN of the project(s) arguments of the invocation
- REQUEST_ID: The identifier of a project join request (not a URN but treated as a unique identifier for the same processing)
It is assumed that there will be only one subject TYPE for a given invocation. An !ArgumentError is raised otherwise.
For a given subject, a set of bindings may or may not be able to be computed.
Binding Name | Meaning |
---|---|
$SLICE | Bound to the slice argument (if any) of the invocation |
$PROJECT | Bound to the project argument (if any) of the invocation |
$MEMBER | Bound to he member argument (if any) of the invocation |
$ROLE | Bound to the role of the caller in the given slice or project (if any) |
$SELF | Bound to the URN of the caller |
$SHARES_SLICE | Bound to "SHARES_SLICE" if the caller and given member argument (if any) share membership in an unexpired slice |
$SHARES_PROJECT | Bound to "SHARES_PROJECT" if the caller and given member argument (if any) share membership in an unexpired0 project |
$PROJECT_LEAD | Bound to "PROJECT_LEAD" if the caller is the lead of some unexpired project |
$PROJECT_ADMIN | Bound to "PROJECT_ADMIN" if the caller is the admin of some unexpired project |
$SEARCHING_BY_EMAIL | Bound to "SEARCHING_BY_EMAIL" if the caller is searching for member information by email |
$SEARCHING_FOR_PROJECT_LEAD_BY_UID | Bound to "SEARCHING FOR_PROJECT_LEAD_BY_UID" if the caller is searching for member info of a project lead by member UID |
$PENDING_REQUEST_TO_MEMBER | Bound to "PENDING_REQUEST_TO_MEMBER" if the caller has a pending project join request for which the member subject (if any) is the lead. |
$REQUEST_ROLE | Bound to the role the caller has in the project of the given project join request_id (if any) |
$REQUESTOR | Bound to "REQUESTOR" if the caller is the requestor for the given project join request_id (if any) |
An assertion is a statement made by an authority about the caller. We compose assertions from templates (parameterized assertions) and bindings. If the bindings specified in the template are all bound, they are substituted and a complete assertion is is made. If some bindings required of the assertion template are unbound, the assertion is not made.
For example,
ME.IS_$SELF<-CALLER
is a templated assertion: The presence of the binding parameter ($SELF) makes this a template and not a complete assertion. If the parameter $SELF is bound (typically to the caller's URN, e.g. urn:publicid:IDN+ch-mb.gpolab.bbn.com+user+mbrinn), then the assertion can be completed as, e.g.
ME.IS_urn_publicidIDN_ch_mb_gpolab_bbn_com_user_mbrinn<-CALLER
Note that the URN is 'flattened' to change all punctuation (+, :, -) to an underscore to make it conform with ABAC syntax (see below).
The following assertions are attempted to be made for each caller depending on their privileges:
Assertion | Meaning |
---|---|
ME.IS_OPERATOR<-CALLER | The given caller has operator privileges |
ME.IS_PI<-CALLER | The given caller has project lead (PI) privileges |
ME.IS_AUTHORITY<-CALLER | The given caller has authority privileges |
ME.IS_$SELF<-CALLER | The given operator has the given URN associated with the $SELF binding. |
In addition, as a convenience, some assertions are generated to bundle other sets of assertions. Specifically, at initialization time, we generate , for each possible role (LEAD, ADMIN, MEMBER, AUDITOR),
ME.BELONGS_TO_$SLICE<-ME.IS_$ROLE_$SLICE
ME.BELONGS_TO_$PROJECT<-ME.IS_$ROLE_$PROJECT
This a policy that states ME.BELONGS_TO_$PROJECT is satisfied by any of ME.IS_LEAD_$PROJECT, ME.IS_ADMIN_$PROJECT, etc.
Much like assertions, policies are assertions about who can make what method calls. They are typically templates with the binding $METHOD bound to the name of the currently called method. Thus when invoking the 'get_credentials' method,
ME.MAY_$METHOD<-ME.IS_OPERATOR
transforms into
ME.MAY_GET_CREDENTIALS<-ME.IS_OPERATOR
With the subjects and assertion/policy templates extracted for the method invocation, the authorization proceeds as follows:
- For each subject in the method subjects,
- Generate bindings based on the given subject
- Instantiate each assertion and policy template based on these bindings
- Attempt to prove either of the following ABAC queries:
- ME.MAY_$METHOD<-CALLER # The caller may call the method independent of context
- ME.MAY_$METHOD_$SUBJECT<-CALLER # The caller may call the method in this context
- If no proof can be generated as 'true' for any subject, the entire authorization fails.
- If all subjects have a successful proof, the entire authorization succeeds.
The logic of which assertions and policies are invoked to make authorization decisions for a given method are contained in an external JSON file (one per Federation service, e.g. logging_policy.json, slice_authority_policy.json) which are kept in /etc/geni-chapi.
The format of these files is a list of method names, and for each, a list of ABAC assertion and policy templates, as follows:
{
"method_name" : {
"__DOC__" : "documentation about policy for this method",
"assertions" : [
"ME.ASSERTION<-CALLER", ...
],
"policies" : [
"ME.MAY_$METHOD<-ME.IS_OPERATOR", ...
]
}
}
The following is an example of a policy file for the GENI logging service:
{
"__DOC__" : "ABAC policies for CHAPI Logging service",
"log_event" : {
"__DOC__" : [
"user_id must be self",
"must belong to slice or project of context (if any)",
"or is about self and only about self"
],
"assertions" : [
"ME.IS_$ROLE_$PROJECT<-CALLER",
"ME.IS_$ROLE_$SLICE<-CALLER",
"ME.INVOKING_ON_$MEMBER<-CALLER"
],
"policies" : [
"ME.MAY_$METHOD<-ME.IS_AUTHORITY",
"ME.MAY_$METHOD<-ME.IS_OPERATOR",
"ME.MAY_$METHOD_$SLICE<-ME.BELONGS_TO_$SLICE",
"ME.MAY_$METHOD_$PROJECT<-ME.BELONGS_TO_$PROJECT",
"ME.MAY_$METHOD<-ME.INVOKING_ON_$SELF"
]
},
"get_log_entries_by_author" : {
"__DOC__" : "user_id must be self",
"policies" : [
"ME.MAY_$METHOD<-ME.IS_AUTHORITY",
"ME.MAY_$METHOD<-ME.IS_OPERATOR",
"ME.MAY_$METHOD_$MEMBER<-ME.IS_$SELF"
]
},
"get_log_entries_for_context" : {
"__DOC__" : "Must be member of project of slice",
"assertions": [
"ME.IS_$ROLE_$PROJECT<-CALLER",
"ME.IS_$ROLE_$SLICE<-CALLER",
"ME.INVOKING_ON_$MEMBER<-CALLER"
],
"policies" : [
"ME.MAY_$METHOD<-ME.IS_AUTHORITY",
"ME.MAY_$METHOD<-ME.IS_OPERATOR",
"ME.MAY_$METHOD<-ME.INVOKING_ON_$SELF",
"ME.MAY_$METHOD_$SLICE<-ME.BELONGS_TO_$SLICE",
"ME.MAY_$METHOD_$PROJECT<-ME.BELONGS_TO_$PROJECT"
]
},
"get_log_entries_by_attributes" : {
"__DOC__" : "For now, leave open. We do not think anyone uses this",
"policies" : [
"ME.MAY_$METHOD<-CALLER"
]
},
"get_log_entries_by_log_entry" : {
"__DOC__" : "For now, leave open",
"policies" : [
"ME.MAY_$METHOD<-CALLER"
]
}
}
The following is a worked-through example of how ABAC authorization works in this scheme.
Imagine that the caller invokes the Slice Authority call get_credentaials (to receive a slice_credential).
The relevant policies and assertion templates are:
"get_credentials" : {
"__DOC__" : "Slice lead/admin/member or operators can get slice cred",
"assertions" : [
"ME.IS_$ROLE_$SLICE<-CALLER"
],
"policies" : [
"ME.MAY_$METHOD<-ME.IS_OPERATOR",
"ME.MAY_$METHOD_$SLICE<-ME.IS_LEAD_$SLICE",
"ME.MAY_$METHOD_$SLICE<-ME.IS_ADMIN_$SLICE",
"ME.MAY_$METHOD_$SLICE<-ME.IS_MEMBER_$SLICE"
]
}
The call signature for get_credentials is:
def get_credentials(self, slice_urn, credentials, options):
The 'slice_urn' is selected as the subject of the call (the object on which the method is working. Thus, $SLICE is bound to the slice_urn argument.
The ABAC Guard then tries to bind the variables in the associated templates, namely $ROLE. It computes this from the role that the caller plays in the given slice ($SLICE=slice_urn argument). If the member plays no role, the $ROLE binding is unbound and none of the assertion "ME.IS_$ROLE_$SLICE<-CALLER" is unbound and unasserted.
Depending on the role (LEAD, ADMIN, MEMBER, AUDITOR), the assertion will be asserted, e.g. ME.IS_LEAD_$SLICE<-CALLER (where $SLICE is replaced by a "flattened" version of the URN, see below).
The policies are then instantiated, again replacing $SLICE with the flattening of the slice_urn, and the $METHOD replaced with get_credentials.
In this context, the query ME.MAY_GET_CREDENTIALS<-CALLER will succeed only if the caller has operator privileges. And the query ME.MAY_GET_CREDENTIALS_$SLICE<-CALLER will only succeed if the caller has LEAD, ADMIN or MEMBER privileges on the slice.
Thus the ABAC queries in the context of the instantiated assertions and policies are proven if and only if the caller is authorized by policy to be allowed to get credentials on the given slice.
GENI services support the Speaks for protocol. In so doing, it is often not the caller per se whose attributes are computed and whose authorization is determined, but that of the "spoken for" entity. Typically the caller is a tool in a speaks-for context, and the spoken-for entity is the user using the tool.
The bindings of variables are often to URN's which have syntactic structure that is incompatible with ABAC. As a result, we ''flatten'' the URNs to simple underscore-separated strings by replacing all punctuation in the URN to underscores. E.g.
urn:publicid:IDN+ch-mb.gpolab.bbn.com+user+mbrinn
changes to
urn_publicid_IDN_ch_mb_gpolab_bbn_com_user_mbrinn
This statements (policies or assertions) templates with URN values can be converted into legal ABAC statements.