Security Guide (PDF)

Security Guide — Chapter 18

« Previous chapter
Next chapter »

Sample Security Scenarios

This chapter describes some common scenarios for defining security policies in your applications. The scenarios shown here are by no means exhaustive. There are many possibilities for how to set up security in your applications. The following sections are included:

Protecting the Execution of XQuery Modules

One simple way to restrict access to your MarkLogic Server application is to limit the users that have permission to run the application. If you load your Xquery code into a modules database, you can use an execute permission on the XQuery document itself to control who can run it. Then, a user must possess execute permissions to run the module. To set up a module to do this, perform the following steps:

  1. Using the Admin Interface, specify a modules database in the configuration for the App Server (HTTP or WebDAV) that controls the execution of your XQuery module.
  2. Load the XQuery module into the modules database, using a URI with an .xqy extension, for example my_module.xqy.
  3. Set execute permissions on the XQuery document for a given role. For example, if you want users with the run_application role to be able to execute an XQuery module with the URI http://modules/my_module.xqy, run a query similar to the following:
    xdmp:document-set-permissions("http://modules/my_module.xqy",
          xdmp:permission("run_application", "execute") )
  4. Create the run_application role.
  5. Assign the run_application role to the users who can run this application.

Now only users with the run_application role can execute this document.

Because your application could also contain amped functions, this technique can help restrict access to applications that use amps.

Choosing the Access Control for an Application

The role-based security model in MarkLogic Server combined with the supported authentication schemes provides numerous options for implementing application access control. This section describes common application access control alternatives:

For details on the different authentication schemes, see Types of Authentication.

Open Access, No Log In

This approach may be appropriate if security is not a concern for your MarkLogic Server implementation or if you are just getting started and want to explore the capabilities of MarkLogic Server before contemplating your security architecture. This scenario provides all of your users with the admin role.

You can turn off access control for each HTTP or WebDAV server individually by following these steps using the Admin Interface:

  1. Go to the Configure tab for the HTTP server for which you want to turn off access control.
  2. Scroll down to the authentication field and choose application-level for the authentication scheme.
  3. Choose a user with the admin role for the default user. For example, you may choose the admin user you created when you installed MarkLogic.

    To assist with identifying users with the admin role, the default user selection field places (admin) next to admin users.

In this scenario, all users accessing the application server are automatically logged in with a user that has the admin role. By default, the admin role has the privileges and permissions to perform any action and access any document in the server. Therefore, security is essentially turned off for the application. All users have full access to the application and database associated with the application server.

Providing Uniform Access to All Authenticated Users

This approach allows you to restrict application access to users in your security database, and gives those users full access to all application servers defined in MarkLogic Server. There are multiple ways to achieve the same objective but this is the simplest way.

  1. In the Admin Interface, go to the Users tab under Security.
  2. Give all users in the security database the admin role.
  3. Go to the Configuration tab for all HTTP and WebDAV servers in the system.
  4. Go to the authentication field and choose digest, basic or digest-basic authentication.
  5. Leave the privilege field blank since it has no effect in this scenario. This field specifies the privilege that is needed to log into application server. However, the users are assigned the admin role and are treated as having all privileges.

In this scenario, all users must authenticate with a username and password. Once they are authenticated, however, they have full access to all functions and data in the server.

Limiting Access to a Subset of Users

This application access control method can be modified or extended to meet the requirements in many application scenarios. It uses more of the available security features and therefore requires a better understanding of the security model.

To limit application access to a subset of the users in the security database, perform the following steps using the Admin Interface:

  1. Create an execute privilege named exe-priv-app1 to represent the privilege to access the App Server.
  2. Create a role named role-app1 that has exe-priv-app1 execute privilege.
  3. Add role-app1 to the roles of all users in the security database who should have access to this App Server.
  4. In the Configuration page for this App Server, scroll down to the authentication field and select digest, basic or digest-basic. If you want to use application-level authentication to achieve the same objective, a custom login page is required. See the next section for details.
  5. Select exe-priv-app1 for the privilege field. Once this is done, only the users who have the exe-priv-app1 by virtue of their role(s) are able to access this App Server.

    If you want any user in the security database to be able to access the application, leave the privilege field blank.

At this point, the application access control is configured.

This method of authentication also needs to be accompanied by the appropriate security configuration for both users and documents associated with this App Server. For example, functions such as xdmp:document-insert and xdmp:document-loadthrow exceptions unless the user possesses the appropriate execute privileges. Also, users must have the appropriate default permissions (or specify the appropriate permissions with the API) when creating new documents in a database. Documents created by a user who does not have the admin role must be created with at least one update permission or else the transaction throws an XDMP-MUSTHAVEUPDATE exception. The update permission is required because otherwise once the documents are created no user (except users with the admin role) would be able to access them, including the user who created them.

Using Custom Login Pages

Digest and basic authentication use the browser's username and password prompt to obtain user credentials. The server then authenticates the credentials against the security database. There is no good way to create a custom login page using digest and basic authentication. To create custom login pages, you need to use application-level authentication.

To configure MarkLogic Server to use a custom login page for an App Server, perform the following steps using the Admin Interface:

  1. Go to the Configuration tab for the HTTP App Server for which you want to create a custom login page.
  2. Scroll down to the authentication field and select application-level.
  3. Choose nobody as the default user. The nobody user is automatically created when MarkLogic Server is installed. It is created with the following roles: rest-reader, rest-extension-user, app-user, harmonized-reader and is given a password which is randomly generated.
  4. Create a custom login page that meets your needs. We refer to this page as login.xqy.
  5. Make login.xqy the default page displayed by the application server. Do not require any privilege to access login.xqy (that is, do not place xdmp:security-assert() in the beginning of the code for login.xqy. This makes login.xqy accessible by nobody, the default user specified above, until the actual user logs in with his credentials.
The login.xqy page likely contains a snippet of code as shown below:
...return
if xdmp:login($username, $password) then
  ... protected page goes here...
else
  ... redirect to login page or display error page...
The rest of this example assumes that all valid users can access all the pages and functions within the application.

If you are using a modules database to store your code, the login.xqy file still needs to have an execute permission that allows the nobody (or whichever is the default) user to access the module. For example, you can put an execute permission paired with the app-user role on the login.xqy module document, and make sure the nobody user has the app-user role (which it does by default).

  1. Create a role called application-user-role.
  2. Create an execute privilege called application-privilege. Add this privilege to the application-user-role.
  3. Add the application-user-role to all users who are allowed to access the application.
  4. Add this snippet of code before the code that displays each of the pages in the application, except for login.xqy:
    try 
    {
      xdmp:security-assert("application-privilege","execute")
    }
    catch($e)
    {
      xdmp:redirect-response("login.xqy")
    }

or

if(not(xdmp:has-privilege("application-privilege","execute")))
then
(
  xdmp:redirect-response("login.xqy")
)
else ()

This ensures that only a user who has the application-privilege by virtue of his role can access these protected pages.

Similar to the previous approach, this method of authentication requires the appropriate security configuration for users and documents. See Introduction to Security for background on the security model.

Access Control Based on Client IP Address

MarkLogic Server supports deployments in which a user is automatically given access to the application based on the client IP address.

Consider a scenario in which a user is automatically logged in if he is accessing the application locally (as local-user) or from an approved subnet (as site-user). Otherwise, the user is asked to login explicitly. The steps below describe how to configure MarkLogic Server to achieve this access control.

  1. Using the Admin Interface, configure the App Server to use a custom login page:
    1. Go to the Configuration tab for the HTTP or WebDAV App Server for which you want to create a custom login page.
    2. Scroll down to the authentication field and select application-level.
    3. For this example, choose nobody as the default user. The nobody user is automatically created when MarkLogic Server is installed. It is created with the following roles: rest-reader, rest-extension-user, app-user, harmonized-reader and is given a password which is randomly generated.
  2. Add the following code snippet to the beginning of the default page displayed by the application, for example, default.xqy.
    xquery version "1.0-ml"
    
    declare namespace widget ="http://widget.com"
    import module "http://widget.com" at "/login-routine.xqy"
    let $login := widget:try-ip-login()
    return
    if($login) then
      <html>
        <body>
          The protected page goes here.  
          You are {xdmp:get-current-user()}
        </body>
      </html>
    else
      xdmp:redirect-response("login.xqy")

The try-ip-login function is defined in login-routine.xqy. It is used to determine if the user can be automatically logged in based on the client IP address. If the user cannot be logged in automatically, he is redirected to a login page called login.xqy where he has to log in explicitly. See Using Custom Login Pages for example code for login.xqy.

  1. Define try-ip-login:
    1. Create a file named login-routine.xqy and place the file in the Modules directory within the MarkLogic Server program directory. You create an amp for try-ip-login in login-routine.xqy in the next code sample. For security reasons, all amped functions must be located in the specified Modules directory or in the Modules database for the App Server.
    2. Add the following code to login-routine.xqy:
      xquery version "1.0-ml"
      
      module "http://widget.com"
      declare namespace widget ="http://widget.com"define function try-ip-login()as xs:boolean
      {
        let $ip := xdmp:get-request-client-address()
        return 
        if(compare($ip,"127.0.0.1") eq 0) then (:local host:)
          xdmp:login("localuser",())
        else if(starts-with($ip,<approved-subnet>)) then
          xdmp:login("site-user",())
        else
          false()
      }

If the user is accessing the application from an approved IP address, try-ip-login logs in the user with username local-user or site-user as appropriate and returns true. Otherwise, try-ip-login returns false.

In the code snippet above, the empty sequence () is supplied in place of the actual passwords for local-user and site-user. The pre-defined xdmp-login execute privilege grants the right to call xdmp:login without the actual password. This makes it possible to create deployments in which users can be automatically logged in without storing user passwords outside the system.

  1. Finally, to ensure that the code snippet above is called with the requisite xdmp-login privilege, configure an amp for try-ip-login:
    1. Using the Admin Interface, create a role called login-role.
    2. Assign the pre-defined xdmp-login execute privilege to login-role. The xdmp-login privilege gives a user of the login-role the right to call xdmp:login for any user without supplying the password.
    3. Create an amp for try-ip-login as shown below:

An amp temporarily assigns additional role(s) to a user only for the execution of the specified function. The amp above gives any user who is executing try-ip-login() the login-role temporarily for the execution of the function.

In this example, default.xqy is executed as nobody, the default user for the application. When the try-ip-login function is called, the nobody user is temporarily amped to the login-role. The nobody user is temporarily assigned the xdmp:login execute privilege by virtue of the login-role. This enables nobody to call xdmp:login in try-ip-login for any user without the corresponding password. Once the login process is completed, the user can access the application with the permissions and privileges of local-user or site-user as appropriate.

  1. The remainder of the example assumes that local-user and site-user can access all the pages and functions within the application.
    1. Create a role called application-user-role.
    2. Create an execute privilege called application-privilege. Add this privilege to the application-user-role.
    3. Add the application-user-role to local-user and site-user.
    4. Add this snippet of code before the code that displays each of the subsequent pages in the application:
      try 
      {
        xdmp:security-assert("application-privilege","execute")
        ...
      }
      catch($e)
      {
        xdmp:redirect-response("login.xqy")
      }

or

if(not(xdmp:has-privilege("application-privilege","execute")))
then
(
  xdmp:redirect-response("login.xqy")
)
else ()

This ensures that only the user who has the application-privilege by virtue of his role can access these protected pages.

Implementing Security for a Read-Only User

In this scenario, assume that you want to implement a security model that enables your users to run any XQuery code stored in the modules database for a specific App Server with read-only permissions on all documents in the database.

Reviewing the MarkLogic security model, recall that users do not have permissions, documents have permissions. And permissions are made up of a role paired with a capability. Additionally, execute privileges protect code execution and URI privileges protect the creation of documents in a specific URI namespace. This example shows one way to implement the read-only user and is devided into the following parts:

Steps For Example Setup

To set up this example scenario, perform the following steps, using the Admin Interface:

  1. Create a role named ReadsStuff.
  2. Create a user named ReadOnly and grant this user the ReadsStuff role.
  3. Create a role named WritesStuff and grant this role the ReadsStuff role.
  4. Grant the WritesStuff role the any-uri privilege, as well as any execute privileges needed for your application code.
  5. Create a user named LoadsStuff and grant this user the WritesStuff role. When you load documents, load them as the LoadsStuff user and give each document an update and insert permission for the WritesStuff role and a read permission for the ReadsStuff role.

    Here is sample code to create a set of permissions to do this as on option to either the xdmp:document-insert function or the xdmp:document-load function:

    (xdmp:permission("ReadsStuff", "read"),
    xdmp:permission("WritesStuff", "insert"),
    xdmp:permission("WritesStuff", "update"))

An alternative to specifying the permissions when you load documents is to assign default permissions to the LoadsStuff user or the WritesStuff role.

Troubleshooting Tips

If you are running a URL rewriter (or an error handler), you need to give the ReadsStuff role to the nobody user or whichever user is the default user for your App Server. When the URL rewriter executes, the request has not yet been authenticated, so it runs as the default user. The default user is nobody unless you have specified a different default for your App Server. The best practice is to create another role, for example my-app-user and add an execute permission for the URL rewriter and your error handler (if any) for the my-app-user role. This is better because you do not want the nobody user to have access to your database.

« Previous chapter
Next chapter »
Powered by MarkLogic Server | Terms of Use | Privacy Policy