Query-based access control (QBAC) is a mechanism to provide policy enforcement for access to resources based on security markings, metadata, or data in the records themselves. It works by associating queries with roles and users, and adds these automatically to security queries to constrain access. It integrates with the existing MarkLogic security model, which is a role-based security model.
Advanced Security License option is required when using Query-Based Access Control.
The following topics are covered in this chapter:
Prior to the addition of this feature, a secure data access query is formed solely based on permissions from the effective user roles. QBAC augments this security query with more general cts queries to provide more flexible data access rules. These queries are associated with roles and users, and are added to the security queries to constrain and check access permissions. This allows you to define access policies based on document contents or metadata, and to change those policies without re-processing the document permissions, and without having to write triggers or code to monitor when document contents change.
There are two types of QBAC queries: queries on roles and queries on users. Queries on roles are definitional: a document that passes the role query is treated as having the corresponding permission for that role, so a user with that role may also see the document. Queries on users are restrictive: the user may only see the documents that pass the query. When the server checks queries, the queries on uncompartmented roles are ORed, and the queries on users are ANDed. So queries on roles expand the scope what is authorized for that role, while queries on users restrict the scope of what is authorized.
Secure data access at the fundamental level in MarkLogic Server is constrained by a security query. Unsecured data access is used only for the admin user or for certain internal lookups or fetches. All user-facing APIs that access data stored in the database are secured in this fashion, whether using a cts:search, a lexicon call such as cts:values, a SQL or SPARQL query that accesses triples, an update operation such as xdmp:node-replace, or the execution of a module.
As a result, QBAC can integrate with all the existing MarkLogic security features, such as Compartment Security, Element Level Security (ELS), triples and protected collections. For example, when a path is protected by ELS, QBAC will not leak information about the contents. However, extra care should be taken when setting up security model combining both queries and other security features.
Users with QBAC document access are not able to read document properties. This is a design limitation. Users with QBAC document access do not have properties access by default, unless the QBAC query explicitly matches document properties through a cts query. However, QBAC access to document properties gives access to the document itself by default.
This section describes several scenarios that use QBAC. They are not meant to demonstrate the correct way to set up QBAC, as your situation is likely to be unique. However, it demonstrates how QBAC works and may give you some ideas on how to implement your own security model.
Description: A security architect Sammy from company ABC wishes to enforce up a policy that people in each of the regions can see documents relevant to their region by inspecting metadata in each document to determine if someone can access it.
To setup QBAC for this scenario, you need to create the necessary roles and users, and insert documents through Query Console or REST Management APIs.
To run through the example, perform the steps in each of the following sections:
Sammy sets up some roles: region-APAC
, region-EMEA
, and region-NA
.
xquery version "1.0-ml"; import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; sec:create-role("can-read", "General read", (), (), ()), sec:create-role("region-APAC", "Can see APAC documents.", (), (), (), (), (), map:map()=>map:with( "read", cts:element-query(xs:QName("metadata"), cts:element-word-query(xs:QName("region"), "APAC"))) ), sec:create-role("region-EMEA", "Can see EMEA documents.", ( ), (), (), (), (), map:map()=>map:with( "read", cts:element-query(xs:QName("metadata"), cts:element-word-query(xs:QName("region"), "EMEA"))) ), sec:create-role("region-NA", "Can see NA documents.", (), (), (), (), (), map:map()=>map:with( "read", cts:element-query(xs:QName("metadata"), cts:element-word-query(xs:QName("region"), "NA"))) )
Using the Admin Interface > Security > Users > Create, Query Console, or REST Management APIs (RMAs), Sammy creates the users and assign them the roles indicated in the following table:
User | Roles |
---|---|
Edna | region-NA, can-read |
Fred | region-EMEA, can-read |
Peter | region-APAC, can-read |
Using Query Console, insert the following documents to the database. /doc5.xml
and /doc6.xml
are added with read permissions for can-read
, so that they are visible to anyone that has can-read
role.
xquery version "1.0-ml"; xdmp:document-insert("/doc1.xml", <root> <metadata> <region>region-NA</region> <group>group-engineering</group> </metadata> <email>jane@companyabc.com</email> <feature>New feature</feature> </root>), xdmp:document-insert("/doc2.xml", <root> <metadata> <region>region-NA</region> <group>group-finance</group> </metadata> <email>matt@companyabc.com</email> <price>100</price> </root>), xdmp:document-insert("/doc3.xml", <root> <metadata> <region>region-EMEA</region> <group>group-engineering</group> </metadata> <email>jim@companyabc.com</email> <feature>Another new feature</feature> </root>), xdmp:document-insert("/doc4.xml", <root> <metadata> <region>region-APAC</region> <group>group-finance</group> </metadata> <email>jeff@companyabc.com</email> <price>10</price> </root>), xdmp:document-insert("/doc5.xml", <root> <metadata> <region>region-all</region> <group>group-all</group> </metadata> <email>dummy@companyabc.com</email> </root>), xdmp:document-insert("/doc6.xml", <root> <metadata> <region>region-all</region> <group>group-finance</group> </metadata> <email>dummy@companyabc.com</email> </root>), xdmp:document-add-permissions("/doc5.xml", xdmp:permission("can-read","read")), xdmp:document-add-permissions("/doc6.xml", xdmp:permission("can-read","read"))
The definitional queries on the roles will effectively treat documents as having permissions for that role. As a result, when Edna, Fred and Peter perform a search (read) against the database, they are able to read the following documents:
Description: Another security architect Carly from Company XYZ now wants to enforce a policy that only folks in the engineering group should be able to see feature design specifications, and that only folks in the finance group should be able to read and update documents with pricing information. This scenario will show the interaction between QBAC and Compartment Security. For more information about Compartment Security, see Compartment Security.
Carly didn't need to use compartment security here because there is only one dimension of access, but she thinks she may have others and wants them to be intersectional. Since the update policy is of the form if (query) then Deny
, we need to also put the negated queries on the roles that we want to exclude, so the implementation is a little more complicated.
Mike is a contractor who works for Company XYZ. He is only able to read the documents marked with "group-all" in the metadata. He cannot see any other documents in the database. Carly sets up a user for him and grants permissions through user queries, which are restrictive.
To run through the example, perform the steps in each of the following sections:
Carly sets up some roles: can-update
, can-read
, group-all
, group-engineering
, group-finance
.
xquery version "1.0-ml"; import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; (: Uncompartmented roles can-read and can-update for compartment setup :) sec:create-role("can-read", "General read", (), (), ()), sec:create-role("can-update", "General update", (), (), ()), (: Compartment role group-all for compartment permissions :) sec:create-role("group-all", "All groups.", (), (), (), "compartment-group"), sec:create-role("group-engineering", "Engineering.", (), (), (),"compartment-group", (), map:map()=>map:with( "node-update", cts:not-query(cts:element-query(xs:QName("price"), cts:true-query())) )=>map:with( "read", cts:element-query(xs:QName("feature"),cts:true-query()) ) ), sec:create-role("group-finance", "Finance.", (), (), (), "compartment-group", (), map:map()=>map:with( "node-update", cts:element-query(xs:QName("price"), cts:true-query()) )=>map:with( "read", cts:element-query(xs:QName("price"), cts:true-query()) ) ); xquery version "1.0-ml"; import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; sec:create-user("Mike", "Contractor", "Mike", ("can-read"), (), (), (), map:map()=>map:with( "read",cts:element-query(xs:QName("metadata"), cts:element-word-query(xs:QName("group"), "group-all")) ) )
Using the Admin Interface > Security > Users > Create, Query Console or RMAs, Carly creates the users and assign them the roles indicated in the following table:
User | Roles |
---|---|
John | |
Pari | |
Mike |
For simplicity, we will reuse the 6 documents in Scenario 1: Region Restrictions. Here are the new permissions that need to be added:
xquery version "1.0-ml"; (: Doc 1 to 4 have compartment compartment-group. Doc 5 and 6 don't :) let $permissions := (xdmp:permission("can-read","read"), xdmp:permission("can-update","node-update"), xdmp:permission("group-all","read")) for $i in 1 to 4 return xdmp:document-add-permissions("/doc"||$i||".xml", $permissions), xdmp:document-add-permissions("/doc5.xml", (xdmp:permission("can-read","read"), xdmp:permission("can-update","node-update"))), xdmp:document-add-permissions("/doc6.xml", (xdmp:permission("can-read","read"), xdmp:permission("can-update","node-update")))
When John and Pari perform a read or a node-update against the database, the results are shown in the following table:
Mike is not able to read /doc6.xml
although he has the can-read
role. The access to /doc6.xml
is further restricted by the user queries.
Some existing Security APIs have been modified to support query-based access control. In addition, several new APIs have been added.
The following security APIs are updated to allow for queries to be added to users and roles, sec:create-user and sec:create-role:
sec:create-user( $user-name as xs:string, $description as xs:string?, $password as xs:string, $role-names as xs:string* $permissions as element(sec:permission)*, $collections as xs:string*, [$external-names as xs:string*], [$queries as map:map] ) as xs:unsignedLong sec:create-role( $role-name as xs:string, $description as xs:string?, $role-names as xs:string*, $permissions as element(sec:permission)*, $collections as xs:string*, [$compartment as xs:string?], [$external-names as xs:string*], [$queries as map:map] ) as xs:unsignedLong
Queries are a mapping from capabilities to cts queries.
Capabilities associated through permissions are read
, insert
, update
, node-update
, and execute
. For more information about Document Permissions, see Capabilities Associated Through Permissions. Please note that, in terms of QBAC queries, operations that need a node-update
capability will use the node-update
query, and those that need update
capability will use update
query to reduce complexity. The node-update
capability does not serve as a subset of the update
capability.
These new APIs are added to support QBAC:
sec:role-get-queries($role-name as xs:string) as map:map
The sec:role-get-queries function requires the privilege http://marklogic.com/xdmp/privileges/role-get-queries.
sec:role-set-queries( $role-name as xs:string, $queries as map:map ) as empty-sequence()
The sec:role-set-queries functions requires the privilege "http://marklogic.com/xdmp/privileges/role-set-queries"
sec:role-set-query( $role-name as xs:string, $capability as xs:string, $query as cts:query? ) as empty-sequence()
The sec:role-set-query function requires the privilege http://marklogic.com/xdmp/privileges/role-set-queries
sec:user-get-queries($user-name as xs:string) as map:map
The sec:user-get-queries requires the privilege http://marklogic.com/xdmp/privileges/user-get-queries
sec:user-set-queries( $user-name as xs:string, $queries as map:map ) as empty-sequence()
The sec:user-set-queries function requires the privilege "http://marklogic.com/xdmp/privileges/user-set-queries"
sec:user-set-query( $user-name as xs:string, $capability as xs:string, $query as cts:query? ) as empty-sequence()
The sec:user-set-query function requires the privilege http://marklogic.com/xdmp/privileges/user-set-queries.
Roles and users will get an additional query map. There is a new read-only property on the corresponding roles and users page on Admin GUI that shows the queries and capabilities for debugging purpose.
A query may raise an error. For example, if a range index is referenced but not available, or stemmed searches are used but not enabled. A failed query will lead to denial of access.