Loading TOC...
Security Guide (PDF)

Security Guide — Chapter 8

Query-Based Access Control

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:

What is QBAC

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.

Example QBAC Applications

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.

Scenario 1: Region Restrictions

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:

Create Roles

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")))
)
Create Users

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
Insert the Documents and Add Permissions

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"))
Test It Out

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:

Document Metadata User with read Access
/doc1.xml region-NA Edna
/doc2.xml region-NA Edna
/doc3.xml region-EMEA Fred
/doc4.xml region-APAC Peter
/doc5.xml can-read permission key Edna, Fred, Peter
/doc6.xml can-read permission key Edna, Fred, Peter

Scenario 2: Group Restrictions

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:

Create Roles

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"))
    )
)
Create Users

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

group-engineering,

can-read,

can-update

Pari

group-finance,

can-read,

can-update

Mike
Insert the Documents and Add Permissions

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")))
Test It Out

When John and Pari perform a read or a node-update against the database, the results are shown in the following table:

Document Metadata User with read Access User with node-update Access
/doc1.xml feature John
/doc2.xml price Pari Pari
/doc3.xml feature John
/doc4.xml price Pari Pari
/doc5.xml can-read and can-update permission keys, group-all John, Pari, Mike John, Pari
/doc6.xml can-read and can-update permission keys John, Pari John, Pari

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.

Interfaces to Support QBAC

Some existing Security APIs have been modified to support query-based access control. In addition, several new APIs have been added.

Changes to Security Module APIs

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.

Admin GUI

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.

Errors

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.

Limitations

  • Queries run unfiltered. If a query has false positives that means that access may be granted where it is not intended to.
  • It is not recommended to use expensive QBAC queries (for example, wildcards with lexicon expansion), since they run on every database request.
  • Queries may depend on specific indexes (for example, range queries). If those indexes are deleted, the queries will fail and will lead to denial of access.
  • Configuration of QBAC queries is through security APIs and RMAs only. See the RMAs for configuring roles and users at https://docs.marklogic.com/REST/POST/manage/v2/roles, and https://docs.marklogic.com/REST/POST/manage/v2/users.
« Previous chapter
Next chapter »