Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

 

Table of Contents
 

...

  •  User stories documented (Bhooshan)
  •  User stories reviewed (Nitin)
  •  Design documented (Bhooshan)
  •  Design reviewed (Andreas/Terence)
  •  Feature merged (Bhooshan)
  •  Documentation (Bhooshan)
  •  Blog post 

...

The typical pattern in Sentry is to whitelist a set of users who the Sentry service can accept requests from. The property that dictates this is called sentry.service.allow.connect. The description for this property states: "List of users allowed to connect to the Sentry Server. These are usually service users such as hive and impala, and the list does not usually need to include end users." . As a result, the pattern in 3.4 was to whitelist the cdap user, which was fine, because all authorization requests to Sentry originate from the CDAP Master. However, the difference in 3.5 is that now, CDAP will make requests to Sentry for authorization enforcement from program containers. To add to that, programs will run as the user that starts the program, and this user is configured at the namespace level in 3.5. So,

  1. A user creates a namespace myspace, and assigns the principal 'myuser' to it
  2. The user deploys an app in 'myspace', and starts a program
  3. The program is spawned as 'myuser'
  4. During the program execution, requests need to be made to Sentry.

For 4. above, there are two options:

  1. Send the request as the 'cdap' user. This communication has been tested to work, and will always work, as long as the 'cdap' user is whitelisted using the property mentioned earlier in the Sentry Service. To achieve this however, we will need to create an extra hop in this request. So from the program container, an RPC request is made to another container (that also executes other operations like recording lineage, usage registry and run records and workflow tokens. This other container will have the cdap user's delegation token, and will make the request to Sentry.
  2. Send the request as the user running the program. This will not need the extra hop in 1. However, the disadvantages of this are:
    1. Every single user who will ever run a CDAP program will have to be whitelisted in the Sentry Service. An alternate approach, where a certain 'cdapprogramrunners' group is whitelisted, and all users who will run a program are part of that group does not work. Even the whitelist property description suggests the same, and an experiment proved it as well.
    2. Once a user is whitelisted, it is whitelisted for all operations in the Sentry Service. This property merely decides whether a request will be accepted or rejected solely based on the defined users. It makes no distinction based on the operation being performed. There are other parameters that influence that (viz: admin groups; the fact that only admin groups can list all roles, create a role, etc; granting/revoking privileges is also determined by a policy in CDAP, which ensures that only a user that has ADMIN rights on an entity can grant/revoke - the whitelist does not influence any of these operations).

Taking into consideration all the above, it seems like for communication with Sentry, the first approach of using an extra RPC call, but communicating as 'cdap' makes sense. Unless of course users are fine with going against the Sentry norm as well as the property description of whitelisting every single user (for 3.5, this number is effectively equal to the number of namespaces in CDAP).

Dependencies

Ability to distinguish between read and write operations in datasets

...

  • An authorization policy (or ACL) can only be stored for an existing entity in CDAPAny API that lists entities will filter is only valid if the entity exists in CDAP. There may be orphaned (invalid) policies, but they can only exist if entity deletion fails before or during policy revoke - Details in the deletion section. 
  • Any API that lists entities will filter all the entities to only return entities that the logged in user has access (READ/WRITE/ADMIN/ALL) to.
  • Any API that gets details of an entity will require that the user has access (READ/WRITE/ADMIN/ALL) to that entity.
  • Any API that creates a new entity will require that the user has WRITE access on the entity's parent (e.g. to create a dataset, the user will need WRITE access on the namespace where the dataset will be created).
    • While creating an entity, some Such APIs first perform a check if the entity exists. 
      • CDAP will allow this check to return the right response, even if the user does not have access to the entity. This is because per point 1 above, even if we do send an unauthorized response for such an entity, the user can infer that such an entity exists.
    Any API that deletes
    • for entity existence
      • Entity does not exist
        • There cannot be a valid enforcement check in this situation, because CDAP does not have privileges for non-existing entities. As a result, CDAP will try to get the metadata for that entity. Since it does not exist, CDAP will respond with Not Found
          Andreas: I don't understand. If user has WRITE access to parent (e.g., namespace), then the creation should succeed. Why NotFound?
        • Bhooshan: Nope, I got this wrong. I've missed a sentence before these bullet points. This only applies to an existence check during creation, not the actual creation process. e.g. Dataset creation during app deployment first makes a get() call to check if the dataset service. For this call to proceed with dataset creation, it expects the get() call to respond with Not Found. I've added that point in blue. 
      • Entity already exists
        • Since the entity exists, there can be valid enforcement checks for the entity. 
          • User does not have access on existing entity
            • CDAP will return an Unauthorized response
          • User has access on the entity
            • CDAP will return an Already Exists response
      • Bear in mind, that in both the above conditions, the user can infer (implicitly or explicitly) that the entity exists.
  • Any API that creates a new entity, grants ALL privileges to the user once it is determined that the user has privileges to create the entity. Once the privileges have been granted, CDAP proceeds to create the entity. If the process of creation of the entity fails, CDAP rolls back the privileges. This is done so that there may be orphaned privileges in rare scenarios, but there can never be orphaned entities.
    Andreas: Similar to delete, we don't want this to create an orphaned entity that nobody can ever see or delete again. So should we create the ACLs first, then create the entity, if that fails, attempt to remove the ACLs? That would cause, in rare situations, an orphaned ACL, but never an orphaned entity.  
    Bhooshan: Agreed. That makes it consistent. Updated
  • Any API that deletes an entity will require ADMIN privilege on that entity
    • Irrespective of whether the entity exists or not, an authorization check will first be performed. It will return an Unauthorized response if the user does not have the ADMIN privilege. If the user does have the required privilege, the API will respond with a Not Found if the entity does not exist (because of an Orphaned ACL from a previous deletion). Else, it will proceed with deletion. 
      Andreas: If the entity does not exist, then there cannot be an ACL for it (according to first point), so the user will never have the required privilege, right? That is, it will always return NotFound or succeed? I would think that if the user has any privilege (say READ), but not ADMIN, then this returns Unauthorized. If the user has no privileges at all, then it returns NotFound (irrespective of whether the entity exists). It will also return NotFound if the user has privileges but the entity does not exist, but that cannot happen according to the next bullet.
    • Bhooshan: Discussed this in person and updated. 
    • Just to confirm: Deleting an entity that does not exist will - under normal circumstances - return Unauthorized. Is that intended? Whereas check for existence (as pointed out above) will return NotFound. And what will getDetail() return if the entity does not exist?
    • Bhooshan: getDetail() returns NotFound if the entity does not exist, the authorization check is performed afterwards. Should we change delete also first do the same existence check?
  • Any API that deletes an entity will first remove all metadata for that entity, once it is determined by the previous policy that the user has privileges to delete the entity. This is so that entity can never subsequently be returned as a response to a list or get API. If deletion fails midway or while revoking privileges, CDAP may have orphaned privileges (for non-existing entities). There would be no easy way to clean up that entity or its privileges later. If someone re-creates that entity, it could have some rogue privileges. Until such operations can be transactional, the create operation will first delete any privileges on the entity that was successfully created, then grant the user ALL privileges on the entity.
    Andreas: That means if the deletion fails, then it can never be deleted again, because all privileges have already been removed? So it becomes an orphan invisible to everybody (because list calls will not show it any longer)? Seems weird. 
    Bhooshan: Updated per our discussion. Please review again.
  • Any API that changes the characteristics/properties (update properties, upgrade entity) of an entity will require ADMIN privilege on that entity
  • Any API that changes the characteristics/properties (update properties, upgrade entity) of an entity will require ADMIN privilege on that entity
  • .
    • Irrespective of whether the entity exists or not, an authorization check will first be performed. It will return an Unauthorized response if the user does not have the required privilege. If the user does have the required privilege, the API will respond with a Not Found if the entity does not exist. Else, it will proceed with the modifications to the entity's properties.
      Andreas: Same comment applies as for delete above

 

NOTE: Cells marked green were done in 3.4. Cells marked in yellow are in scope for 3.5.

refresh
EntityOperationRequired PrivilegesResultant Privileges
NamespacecreateADMIN (Instance)ADMIN ALL (Namespace)
 updateADMIN (Namespace) 
 listREAD/WRITE/ADMIN/ALL (Namespace) 
 getREAD/WRITE/ADMIN/ALL (Namespace) 
 deleteADMIN (Namespace) 
 set preferenceWRITE (Namespace) 
 get preferenceREAD (Namespace) 
 searchREAD/WRITE/ADMIN/ALL (Namespace) 
ArtifactaddWRITE (Namespace)ADMIN ALL (Artifact)
 deleteADMIN (Artifact) 
 getREAD/WRITE/ADMIN/ALL (Artifact) 
 listREAD/WRITE/ADMIN/ALL (Artifact) 
 write propertyADMIN (Artifact) 
 delete propertyADMIN (Artifact) 
 get propertyREAD/WRITE/ADMIN/ALL (Artifact)  
WRITE (Instance)  write metadataADMIN (Artifact) 
 read metadataREAD (Artifact) 
ApplicationdeployWRITE (Namespace)ADMIN ALL (Application)
 getREAD/WRITE/ADMIN/ALL (Application) 
 listREAD/WRITE/ADMIN/ALL (Application) 
 updateADMIN (Application) 
 deleteADMIN (Application) 
 set preferenceWRITE (Application) 
 get preferenceREAD (Application) 
 add metadataADMIN (Application) 
 get metadataREAD (Application) 
Programsstart/stop/debugEXECUTE (Program) 
 set instancesADMIN (Program) 
 listREAD/WRITE/ADMIN/ALL (Application) 
 set runtime argsEXECUTE ADMIN (Program) 
 get runtime argsREAD/WRITE/ADMIN/EXECUTE/ALL (Program) 
 get instancesREAD/WRITE/ADMIN/EXECUTE/ALL (Program) 
 set preferenceADMIN (Program) 
 get preferenceREAD (Program) 
 get statusREAD/WRITE/ADMIN/EXECUTE/ALL (Program) 
 get historyREAD/WRITE/ADMIN/EXECUTE/ALL (Program) 
 add metadataADMIN (Program) 
 get metadataREAD (Program) 
 emit logsWRITE (question) (Program) 
 view logsREAD (Program) 
 emit metricsWRITE (question) (Program) 
 view metricsREAD (Program) 
StreamscreateWRITE (Namespace)ADMIN ALL (Stream)
 update propertiesADMIN (Stream) 
 deleteADMIN (Stream) 
 truncateADMIN (Stream) 
 enqueue
asyncEnqueue
batch
WRITE (Stream) 
 getREAD/WRITE/ADMIN/ALL (Stream) 
 listREAD/WRITE/ADMIN/ALL (Namespace) 
 read eventsREAD (Stream) 
 set preferencesADMIN (Stream) 
 get preferencesREAD (Stream) 
 add metadataADMIN (Stream) 
 get metadataREAD (Stream) 
 view lineageREAD (Stream) 
 emit metricsWRITE (question) (Stream) 
 view metricsREAD (Stream) 
DatasetslistREAD/WRITE/ADMIN/ALL (Dataset) 
 getREAD/WRITE/ADMIN/ALL (Dataset) 
 createWRITE (Namespace)ADMIN ALL (Dataset)
 updateADMIN (Dataset) 
 dropADMIN (Dataset)  existsREAD/WRITE/ADMIN /ALL (Dataset) 
 truncateADMIN (Dataset) 
 upgradeADMIN (Dataset) 
 add metadataADMIN (Dataset) 
 get metadataREAD (Dataset) 
 view lineageREAD (Dataset) 
 emit metricsWRITE (question) (Dataset) 
 view metricsREAD (Dataset) 

...