Cyral
Get Started Sign In

Protect REST API endpoints with Cyral

Cyral monitors and protects REST API endpoints based on access policies you create.

Configure interception of REST API calls

To protect a REST endpoint, the main steps are:

  1. Install a Cyral sidecar to intercept and monitor REST API calls from your users and applications. REST endpoints are protected by the same sidecars as other repositories; you can skip this step if you’d like to use an existing sidecar to intercept traffic for your REST API service.

  2. Track your REST API server as a repository by telling Cyral how to connect to it.

  3. Assign your REST API server (recognized as a repository because you tracked it in Step 1) to the sidecar so that the sidecar can intercept API requests.

  4. In Cyral, create a data map and policy that specify who can take which actions on which API endpoint URIs. See below for syntax.

  5. Set all API clients to connect to your protected REST API endpoints through the Cyral sidecar

Track your REST API server and assign it to a sidecar

The first step in adding Cyral to protect a REST API server is to create an entry for it in Cyral's inventory or repositories.

  1. In the Cyral management console, navigate to the Data Repositories tab and click the plus button.

  2. In the pop-up dialog, specify the details needed to connect to the API server:

    1. set type to REST API

    2. hostname is the address or hostname of the API server 

    3. name is a name you choose to represent this API server in Cyral. It's the name you'll use in your data map, and it will appear in logs and other controls.

  3. Click Track.

Next, make sure a Cyral sidecar is installed in a location where it can receive all API requests bound for the API server you're tracking.

  1. In the Sidecars tab, select the sidecar to which you'd like to assign the API server and click its name to edit it.

  2. In the Data Repositories tab, click the plus sign.

  3. In the Assign a Repository window, choose the API server you tracked earlier.

  4. Click Track.

Identity Attribution

Attributing an identity to each incoming query or REST API call is known as "identity attribution." When monitoring a REST API service, Cyral provides JWT-based identity attribution. This is a built-in feature and comes configured by default. 


The default JWT-based Identity attribution module in sidecar does the following:

  1. Fetches the bearer token (JWT) from the Authorization header of the incoming REST API call

  2. Decodes the JWT

  3. Collects the emailpreferred_username, and realm_access.roles fields from the JWT and uses them as the identity values that are reported in the activity logs and used in Cyral's policy evaluation. In addition, it uses the contents of the field azp ("authorized party") from the claims as the service name.


Note that the default identity attribution module does not validate the signature of the JWT. Any changes to the default behavior can be made possible by writing a Rego-based custom identity plugin. Please contact the Cyral support team for more information on this feature.


Use a custom identity plugin (optional)

When you create a repository entry in Cyral to track your REST API service, it is configured to use Cyral's default identity attribution plugin. In most cases, no additional configuration is required. If you wish to use a custom identity plugin, open the repo in Cyral's control plane UI, go to the Advanced tab, and associate the desired plugin with the repo.



Data maps and policies to protect REST API endpoints

You protect a REST endpoint in Cyral in the same way that you protect any data repository: 

  • Create a data map listing the REST API endpoints to be protected, applying a label or labels to each endpoint. Policies refer to endpoints using only labels. Note that many endpoints can be grouped under one label, so you can write an easy-to-read policy that treats your endpoints consistently, even when your API has many possible endpoint URIs. 

  • Write your policy as you would for a data repository, specifying the users and groups who can access each label, and which actions they can perform. 

See the sections below to learn how to create data maps and policies that protect REST API endpoints. (For an introduction to the structure of data maps and policies, see the policy guide.)

Data maps for REST API endpoints

Data map structure

Data maps follow the structure shown below. Within a label declaration you can add as many repos and services as you like. Each service entry represents an API server you'll include in this label. (Each repo entry represents one or more locations in a database or data collection; the repo block is not used for API endpoints, but we include it here for completeness.)

{LABEL}:

  - repo: {REPOSITORY_NAME}

    attributes:

       - {ATTRIBUTE_LOCATION_1}

       - {ATTRIBUTE_LOCATION_2}

       ...

    ...

  - service: {SERVICE_NAME}

    endpoints:

       - uri: {REST_ENDPOINT_1}

         method: {OPERATION_1}

         deletedCount: {RESPONSE_FIELD}

         readCount: {RESPONSE_FIELD}

         updatedCount: {RESPONSE_FIELD}

       - uri: {REST_ENDPOINT_2}

         method: {OPERATION_1}

         updatedCount: {RESPONSE_FIELD}

       - uri: {REST_ENDPOINT_3}

         method: {OPERATION_1}

         deletedCount: {RESPONSE_FIELD}


         ...

    ...


The elements in the data map are:

  • the label is the name you'll use in your policy to refer to all the REST endpoints and repository locations you include in the repo and service blocks

  • the service block lists the REST endpoints in this label. Each entry in the list consists of:

    • the name of a REST API server whose endpoints you'll protect, as specified in the Data Repositories list in Cyral

    • endpoints is a list of all this API server's REST endpoints that you want to include in this label. Each endpoint consists of:

      • the uri of the API endpoint

      • the method (for example, GET)

      • a counter definition (readCount, createdCount, updatedCount, and/or deletedCount)

See the next section for details.

Note: Your data map can also include a repo block as a peer to the service block. The repo block lists the data repository locations covered by this label. For details, see the data map documentation


The endpoints specification

Specifying API endpoint URIs in data maps

uri field

The uri field specifies the endpoint. For variable portions of the URI (for instance, in /v1/patients/1qa32109, the value "1qa32109" is the patient id, which varies from call to call. You use wildcard elements to represent the variable parts of URIs. There are three ways to represent a wildcard:

  • a single asterisk (*) represents one variable field

  • a double asterisk (**) represents one or more variable fields. The double asterisk can only be placed at the end of a URI, and it means, "all URIs that fall under this URI"

  • a parameter name in curly braces (for example, {patient_id}). This acts the same as a single asterisk, but has the advantage of forcing Cyral to emit the {parameter-name} into the logs. For example, if you want to create a label that includes all operations on the "patients" endpoint, regardless of which "patient_id" is involved, then you would write:

- uri: /v1/patients/{patient_id}/items**

  method: GET


Method field

The method specifies the HTTP method that this label applies to. It can be GET, POST, PUT, PATCH or DELETE. 

Counter definition

The counter definition (readCount, createdCount, updatedCount, and/or deletedCount) specifies how Cyral will count the number of records accessed (cardinality) when a user calls this URI. 

Using counters

These counts appear in the activity log, and, more importantly, these counts are available for evaluation in your policy. This allows you to write policies that depend on the number of elements a REST API call creates, reads, updates, or deletes. The syntax for this follows the pattern:

<counter name>: <counter definition>


where:

  • counter name is one of readCount, updatedCount, or deletedCount

  • counter definition is one of:

    • the name of a request field whose elements are to be counted; 

    • the name of a response field whose elements are to be counted; or 

    • a constant. For some REST API calls, the number to be counted is a constant, so you would put that constant in the "count" field.

Computing cardinality or record count

The Cyral sidecar computes the cardinality or record count based on the counter definition in your data map. The following rules apply to this calculation:

  1. If the counter name and definition are omitted from the data map, then the cardinality is reported as 1.

  2. If the counter definition is a constant, then the specified constant value will be the cardinality.

  3. The counter definition can reference fields in the API request payload or the response payload:

    1. if the element is present in the response:

response.field1.field2. ... . fieldN 

  1. if the element is present in the request:

request.field1.field2. ... . fieldN

  1. The literal value of the specified field will be interpreted as record count if the counter definition is prefixed with an exclamatory sign !. For example, consider a response payload of a DELETE API with a field called recordsDeleted, indicating the number of records deleted.

{

    "Id": "123e4567-e89b-12d3-a456-426614174000",

    "recordsDeleted": 2

}

If the counter definition in the datamap is specified as
deletedCount: !response.recordsDeleted, then the sidecar would report a cardinality of 2

  1. When the request or response fields  are of type lists, the fields specified in the counter definition can include square brackets []. For example, consider a response payload

{

        "Id": "123e4567-e89b-12d3-a456-426614174000",

        "customers": [{

"name": "John Smith",

            "creditScore": 670

         },{

"name": "Frank Hardy"

        }]

}

Given the response payload above, 

- a counter definition response.customers[].creditScore will result in a cardinality of 1. 

- a counter definition response.customers[] will result in a cardinality of 2. 

- a counter definition response.customers will result in a cardinality of 1. 

  1. Consider another example, shown below, where the API response payload is a list (as opposed to a dictionary in the previous example):
    [

        {"name": "John Smith", "creditScore": 670},

        [

            {"name": "Frank Hardy", "creditScore": 710},

            {"name": "Nancy Drew","creditScore": 700} 

        ],

[

            {"name": "Frank Hardy"},

            {"name": "Nancy Drew","creditScore": 700} 

        ]

]


Below, we list different possible counter definitions one could specify in the data map. For each, we show the cardinality value the sidecar computes, based on the response payload shown above:

Counter Definition

Cardinality

None specified

1

Constant value

Specified Constant

response 

1

response[]

3

response[][]

4

response[][].creditScore

3

 

Data map example for protecting a REST API endpoint

In the below example, we assign a label, "PATIENT" to REST API actions on three endpoints on a REST API server called, "patient_manager". (This assumes we've tracked the API server in Cyral under the repository name, "patient_manager".) 

The label PATIENT is assigned to:

  • GET calls to the /v1/patients API endpoint, in which case Cyral returns a count of the records retrieved;

  • DELETE calls to the /v1/patients API endpoint, in which case Cyral returns a count of the records deleted;

  • PUT or POST calls to the /v1/patients URI and all URIs that begin with the pattern, "/v1/patients", in which case Cyral returns a count of the records created or updated;

PATIENT:

  - service: patient_manager

    endpoints:

      - uri: /v1/patients 

        method: GET

        readCount: response.patients

      - uri: /v1/patients/{patient_id}

        method: DELETE

        deletedCount: response.patients

      - uri: /v1/patients**

        method: PUT,POST

        updatedCount: response.patients


Tips for specifying API endpoints

Wildcards: In nearly all cases, you should use a double asterisk (**) when you want to include a wildcard in a URI. For example, to create a label that includes all possible REST calls whose URI starts with /v1/patients, you would specify the URI in your data map as: 

- uri: /v1/patients**

  method: GET


Policies for REST API endpoints

Policies work as they do for data repositories. Here's the policy syntax for protecting REST API endpoints:

data:

  - {LABEL}

rules:

  - identities: 

      users: [{USER_1}, {USER_2}, ...]  

      services: [{SERVICE_1}, {SERVICE_2}, ...] 

      groups: [{GROUP_1}, {GROUP_2}, ...] 

    reads:

      - data: [{LABEL_1}, {LABEL_2}, ...]

        rows: {ROW_LIMIT}

    updates:

      - data: [{LABEL_1}, {LABEL_2}, ...]

        rows: {ROW_LIMIT}

        severity: {SEVERITY_LEVEL}

    deletes:

      - data: [{LABEL_1}, {LABEL_2}, ...]

        rows: {ROW_LIMIT}

        severity: {SEVERITY_LEVEL}



For example:

data:

  - STORE_OPS

rules:

  - identities: 

      groups: [retail_analysts]

    reads:

      - data: [{LABEL_1}, {LABEL_2}, ...]

        rows: {ROW_LIMIT}

    updates:

      - data: [{LABEL_1}, {LABEL_2}, ...]

        rows: {ROW_LIMIT}

        severity: {SEVERITY_LEVEL}

    deletes:

      - data: [{LABEL_1}, {LABEL_2}, ...]

        rows: {ROW_LIMIT}

        severity: {SEVERITY_LEVEL}


 


Turn on logging for a REST API endpoint

To turn on logging for a REST endpoint:

  1. Open the REST API server's entry in the Data Repositories section of the Cyral control plane UI.

  2. Click the Log Settings tab. Under Volume Settings, select Log Everything.

  3. Click Save.




REST API activity in the Cyral logs

In the Cyral activity log, activity on a REST endpoint generates log entries similar to those for queries on a database. 

REST API call-related fields in the activity log

The following fields that are specific to log entries for API calls:

  • repo:type will show "rest" for all REST API calls

  • In the request block,

    • endpoint shows the REST API URI of the request. For example: https://hhiu.cyral.com:8000/v1/patients/1vOya32109

    • matchedRoute shows the URI from your data map that matched the user's API request. For example: /v1/patients/{patient_id}

    • method shows which HTTP operation the user requested. For example: GET

    • fieldsAccessed lists the labels (as established in your data map) that the request operated on, and what type of operation the API call performed on each. For example: "label": "PATIENT", "accessType": "read"

    • parameters:uri lists the arguments present in the REST API request. For example:
      "patient_id": "1vOya32109"

Example log entry for a GET request 

For example, let's consider the following REST API request, which is a GET call:

curl -H "Authorization: Bearer $token" https://hhiu.cyral.com:8000/v1/patients/1vOya32109


Datamap

We assume a data map exists to protect the URI, /v1/patients:

PATIENT:

  - service: patient_manager

    endpoints:

      - uri: /v1/patients/{patient_id}

        method: GET


Sample Log:

{

  "activityId": "127.0.0.1:53890:1627946100378537864:1",

  "activityTime": "2021-08-02 23:15:00.380682539 +0000 UTC",

  "activityTimeNanos": 1627946100380682500,

  "activityTypes": [

    "query"

  ],

  "identity": {

    "endUser": "Nancy Drew",

    "endUserEmail": "nancy.drew@hhiu.us",

    "group": "_Access Support",

    "repoUser": "management-console"

  },

  "repo": {

    "id": "1vY0Bt1JsRhb5LBQLiqUdYZdkSm",

    "name": "patient_manager",

    "type": "rest",

    "host": "hhiu.cyral.com",

    "port": 8000

  },

  "client": {

    "connectionId": "127.0.0.1:53890:1627946100378537864",

    "connectionTime": "2021-08-02 23:15:00.378537864 +0000 UTC",

    "connectionTimeNanos": 1627946100378538000,

    "host": "127.0.0.1",

    "port": 53890,

    "applicationName": "curl/7.68.0"

  },

  "sidecar": {

    "id": "1vOy4ZQ6ZDmIRhnfzmA8TSoycG0",

    "name": "sidecar-east",

    "autoScalingGroupInstance": "10.1.1.207"

  },

  "request": {

    "endpoint": "https://hhiu.cyral.com:8000/v1/patients/1vOyGa3210",

    "matchedRoute": "/v1/patients/{patient_id}", 

    "method": "GET",

    "isSensitive": yes,

    "fieldsAccessed": [ 

      { 

        "label": "PATIENT",

        "accessType": "read"

      }

    ],

    "parameters": {

      "uri": {

        "patient_id": "1vOyGa3210"

      }

    }

  },

  "response": {

    "message": "OK",

    "isError": false,

    "records": 1,

    "bytes": 1460,

    "executionTime": "0.941074599s",

    "executionTimeNanos": 941074599

  },

  "policyViolated": false

}



Did you find it helpful? Yes No

Send feedback
Sorry we couldn't be helpful. Help us improve this article with your feedback.