Skip to content

Commit

Permalink
📝 Update security tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
ujibang committed Jan 16, 2024
1 parent 81d72c9 commit bbae49e
Showing 1 changed file with 99 additions and 154 deletions.
253 changes: 99 additions & 154 deletions docs/security/tutorial.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,220 +5,165 @@ menu: security
---

== Introduction
This tutorial is designed to provide you with a clear understanding of how to secure your RESTHeart applications effectively.

RESTHeart offers robust security features. **Authentication** and **Authorization** are key components of these features, ensuring that only authenticated users can access your API and that they can only perform actions they are authorized to do.
This tutorial aims to provide a thorough understanding of securing RESTHeart applications. RESTHeart offers robust security features, with **Authentication** and **Authorization** being central to these. This guide will explore the basic authentication. We'll also delve into authorization, focusing on Access Control Lists (ACLs) and their role in defining permissions.

In this guide, we'll explore the various methods of client authentication, including basic authentication and token-based authentication. You'll learn how to use different tools such as httpie and curl for authentication, understand the importance of tokens, and how to manage them effectively.
By the end of this tutorial, you'll have a solid foundation in securing RESTHeart applications, ensuring data security and controlled access.

Additionally, we will delve into the concept of authorization in RESTHeart. You'll get to understand the role of Access Control Lists (ACLs) and how they define permissions within your RESTHeart setup. We'll provide examples of ACL configurations and explain the significance of each property in the ACL documents.
== Prerequisites

By the end of this tutorial, you will have a solid foundation in managing security for your RESTHeart applications. Whether you're developing a new project or enhancing an existing one, these skills will be invaluable in ensuring your data remains secure and accessible only to authorized users.
1. **Docker**: For running RESTHeart.
2. **HTTPie**: A user-friendly command-line HTTP client. Download at link:https://httpie.io/cli[httpie.io/cli^].

Let's get started on your journey to mastering authentication and authorization in RESTHeart!
== Starting RESTHeart and MongoDB with Docker Compose

== Authentication

RESTHeart allows clients to authenticate using various methods. This guide focuses on the basic authentication process where a client provides a *username* and *password* with each request.

=== Example http clients authentication

**With httpie**: Use the `-a` option:
To begin, create a directory for RESTHeart and use Docker Compose to start both RESTHeart and MongoDB:

[source,bash]
----
$ http -a userid:password GET 127.0.0.1:8080/
----

**With curl**: Use the `--user` option:

[source,bash]
----
$ curl -i --user userid:password -X GET 127.0.0.1:8080/
----

**With JavaScrip**:

Paste this in a browser console, with RESTHeart running on localhost:

[source,javascript]
$ mkdir restheart && cd restheart
$ curl https://raw.githubusercontent.com/SoftInstigate/restheart/master/docker-compose.yml --output docker-compose.yml
$ docker compose up --attach restheart
----
fetch("http://127.0.0.1:8080/", { method:'GET', headers: {'Authorization': 'Basic ' + btoa('admin:secret')}}).then(res => res.json()).then(collections => console.log(collections));
----

The list of MongoDB collection should be logged in the console.

=== Auth Tokens
== The Admin User

RESTHeart generates an auth token on successful authentication, which can used in subsequent requests in place of the actual password.
Upon first launch, `mongoRealmAuthenticator` creates an `admin` user with default password `secret` and the `admin` role. This role is configured as the root role in `mongoAclAuthorizer`, granting full permissions.

Auth token details are provided in response headers. Use the `Auth-Token-Location` URI to manage the token.
WARNING: **Always change the `admin` user's password** to maintain security.

TIP: Store auth tokens in web application cookies or session storage.
To change the `admin` password:

=== Verifying User Credentials

Use the **roles** service at `/roles/<userid>` to verify credentials passed via basic authentication request header, i.e. `Authorization: Basic base64(id:pwd)`. Possible responses:
[source,bash]
$ http -a admin:secret PATCH :8080/users/admin password="my-strong-password"

- **401 Unauthorized**: Invalid credentials.
- **403 Forbidden**: Userid mismatch.
- **200 OK**: Credentials valid, auth token included.
== Creating Collection /secrets

NOTE: To prevent browser login popups, avoid sending the `WWW-Authenticate` header by using the `No-Auth-Challenge` header or `noauthchallenge` query parameter.
Using `admin`, create the `/secrets` collection:

For an application example, watch link:https://www.youtube.com/watch?v=QVk0aboHayM&t=2262s[this video].
[source,bash]
$ http -a admin:secret PUT :8080/secrets

=== Users management
== Creating Users *alice* and *bob*

With the default configuration, a user is represented in the collection `/users` as follows:
Next, create two users, `alice` and `bob`, each with the `user` role:

[source,json]
[source,bash]
----
{
"_id": "username",
"roles": [ "list", "of", "roles" ],
"password": "secret"
}
$ http -a admin:secret POST :8080/users _id=alice password=secret roles:='["user"]'
$ http -a admin:secret POST :8080/users _id=bob password=secret roles:='["user"]'
----

**Get existing users**
== Understanding Status Codes

The `/users/{id}` endpoint can verify credentials. For example, using incorrect credentials for `alice`:

[source,bash]
----
$ http -a admin:secret /users
[
{
"_id": "admin",
"roles": [
"admin"
],
"_etag": {
"$oid": "5d2edb155883c050065d6a8a"
}
}
]
$ http -a alice:wrong GET :8080/secrets
HTTP/1.1 401 Unauthorized
----

NOTE: The password is always removed from `GET /users` response.

NOTE: For security reasons, it not possible to use the `filter` query parameter on the password field; the following request is forbidden and will cause an error: `GET /users?filter={"password":{"$regex":"^a.*"}}`
IMPORTANT: A `401 Unauthorized` response indicates failed authentication due to incorrect credentials. RESTHeart blocks requests to secure services without proper authentication.

**Create a user**
Attempting access with correct credentials:

[source,bash]
$ http -a admin:secret :8080/users _id=foo roles:='[ "user" ]' password=secret

NOTE: The password is automatically encrypted by RESTHeart.

**Update a user**
----
$ http -a alice:secret GET :8080/secrets
HTTP/1.1 403 Forbidden
----

[source,bash]
$ http PATCH :8080/users/foo password=betterSecret
IMPORTANT: A `403 Forbidden` response means authentication succeeded, but the client lacks permission to access the resource.

**Delete a user**
RESTHeart's default authorizer, `mongoAclAuthorizer`, enforces permissions based on user roles and ACL configurations.

[source,bash]
$ http -a admin:secret DELETE /users/foo
== Configuring Access for `user` Role on `/secrets`

**Create a permission**
We aim to allow `user` role to create and access their own documents in `/secrets`, and to modify only their documents.

[source,bash]
$ http -a admin:secret /acl _id="userCanGetInventory" predicate="path-prefix[/inventory] and method[GET]" roles:='["user"]' priority:=1
1. **Allow `GET` on `/secrets`**:
Users can only access documents they created.

== Authorization
----json
{
"_id": "userCanAccessOwnSecret",
"roles": [ "user" ],
"predicate": "method(GET) and path('/secrets')",
"priority": 100,
"mongo": { "readFilter": "{ author: @user._id }" }
}
----

RESTHeart uses `mongoAclAuthorizer` as the default authorizer, managing access based on MongoDB collection-defined Access Control Lists (ACL).
2. **Allow `POST` on `/secrets`**:
Users can create new documents, setting the `author` to their `_id`.

=== Understanding ACL Properties
----json
{
"_id": "userCanCreateOwnSecret",
"roles": [ "user" ],
"predicate": "method(POST) and path('/secrets')",
"priority": 100,
"mongo": { "mergeRequest": { "author": "@user._id" } }
}
----

An ACL is defined by a set of permissions with the following properties:
3. **Allow `PATCH` on `/secrets/{id}`**:
Users can modify only their documents.

- **Predicate**: An undertow predicate that determines when a request is authorized.
- **Roles**: An array of strings specifying roles that apply to the ACL document.
- **Priority**: Determines precedence when multiple ACL documents match a request.
- **Mongo**: Specifies special permissions for requests handled by `MongoService`.
----json
{
"_id": "userCanModifyOwnSecret",
"roles": [ "user" ],
"predicate": "method(PATCH) and path-template('/secrets/{id}')",
"priority": 100,
"mongo": { "writeFilter": { "author": "@user._id" } }
}
----

NOTE: The `fileAclAuthorizer` is an alternative that defines roles permissions in YAML file.
To create these permissions, use the following commands:

=== Example Permissions
[source,bash]
----
$ http -a admin:secret POST :8080/acl _id=userCanAccessOwnSecret roles:='["user"]' ...
$ http -a admin:secret POST :8080/acl _id=userCanCreateOwnSecret roles:='["user"]' ...
$ http -a admin:secret POST :8080/acl _id=userCanModifyOwnSecret roles:='["user"]' ...
----

Here are some example permissions provided in RESTHeart's link:https://github.com/SoftInstigate/restheart/blob/master/examples/example-conf-files/acl.json[example ACL^]:
== Creating Secret Documents

**powerUserCanDoEverything**
Let's have `alice` and `bob` create their secrets:

[source,json]
[source,bash]
----
{
"_id": "powerUserCanDoEverything",
"predicate": "path-prefix('/')",
"roles": ["power-user"],
"priority": 0,
"mongo": {
"allowManagementRequests": true,
"allowBulkPatch": true,
"allowBulkDelete": true,
"allowWriteMode": true
}
}
$ http -a bob:secret POST :8080/secrets message="Bob loves Alice"
$ http -a alice:secret POST :8080/secrets message="Alice loves Bob"
----
This permission grants `power-user` full system access.

**userCanGetOwnCollection**
Viewing with `admin`:

[source,json]
[source,bash]
----
{
"_id": "userCanGetOwnCollection",
"roles": ["user"],
"predicate": "method(GET) and path-template('/{userid}') and equals(@user._id, ${userid}) and qparams-contain(page) and qparams-blacklist(filter, sort)",
"priority": 100,
"mongo": {
"readFilter": {
"_$or": [{ "status": "public" }, { "author": "@user._id" }]
},
"projectResponse": { "log": 0 }
}
}
$ http -a admin:secret -b GET :8080/secrets
# Output includes both Alice's and Bob's messages
----

Grants `user` role permission to GET documents from `/{userid}`. The read filter applies so only documents with `status=public` or `author=userid` are returned. Users must use the `page` query parameter and cannot use `filter` and `sort` query parameters. The property `log` is removed from the response.
NOTE: The `author` property is correctly set for each document.

**userCanCreateDocumentsInOwnCollection**
Accessing `/secrets` as `alice`:

[source,json]
[source,bash]
----
{
"_id": "userCanCreateDocumentsInOwnCollection",
"roles": ["user"],
"priority": 100,
"predicate": "method(POST) and path-template('/{userid}') and equals(@user._id, ${userid}) and bson-request-whitelist(title, content) and bson-request-contains(title, content) and qparams-whitelist()",
"mongo": {
"mergeRequest": {
"author": "@user._id",
"status": "draft",
"log": "@request"
}
}
}
$ http -a alice:secret -b GET :8080/secrets
# Output includes only Alice's message
----

Allows `user` role to create documents under `/{userid}`. The request must contain `title` and `content`, and no other properties or query parameters are allowed. Properties `author`, `status`, and `log` are added server-side.
Similarly, accessing as `bob`:

**userCanModifyDraftsInOwnCollection**

[source,json]
[source,bash]
----
{
"_id": "userCanModifyDraftsInOwnCollection",
"roles": ["user"],
"priority": 100,
"predicate": "method(PATCH) and path-template('/{userid}/{docid}') and equals(@user._id, ${userid}) and bson-request-whitelist(title, content, status) and (bson-request-contains(title, content) or bson-request-contains(status)) and qparams-whitelist()",
"mongo": {
"mergeRequest": { "author": "@user._id" },
"writeFilter": { "status": "draft" }
}
}
$ http -a bob:secret -b GET :8080/secrets
# Output includes only Bob's message
----

Allows `user` role to modify documents under `/{userid}/{docid}`. The request must contain `title` and `content` or status, and no other properties or query parameters are allowed. Property author is added server-side, and a write filter ensures users can only modify their own documents.
Let's take a moment to acknowledge the story of Alice and Bob. These two characters are entwined in an 'impossible love' story that symbolizes the challenges of secure communication in the digital age. And RESTHeart is no exception keeping their love hidden in the /secrets collection.

0 comments on commit bbae49e

Please sign in to comment.