Skip to main content
Version: v3.2

Requests and approvals application developer guide

Introduction

The Cyral API allows you to build custom access request and approval applications that use Cyral's approvals service. With such an application, database users request access to the data repositories they need, and administrators grant or deny access. See the Cyral app for Slack as an example of the type of application you can build.

The capabilities offered by the API are:

  • request access
  • amend an access request
  • check the status of an access request
  • approve / reject access
  • revoke a current access grant

Example application workflow

Here’s an example of how a custom access request and approval application could work:

  1. Nancy is an analyst who needs data from a database repository. To request access, she goes to a web page that serves as her team’s data access portal. This is the user-facing interface of the access request and approval application.

    • Nancy logs in to this page using her SSO credentials, so the application knows who she is. It proceeds to fetch all the repositories and optionally filter them based on some allow/block list it has for each user. In this case, Nancy is only able to see her team’s mysql repositories.

    • She chooses the repository she wants to access. At this point, the application fetches all the available database accounts for that repository, once again filtering them based on Nancy’s identity.

    • Nancy chooses the database account she’ll use to connect.

    • Nancy chooses the fields she’s interested in and clicks a Request button.

  2. The application packages this information (along with other information like the duration of access requested, comments, and so on) and sends it to the Cyral approvals service.

  3. The Cyral approvals service responds with the ID of the approval request and its current status of PENDING. (Note that administrators can also set up certain data repositories to allow instant, automatic approval of access requests, but for this example, we’ll assume the request requires an approval.)

  4. The application notifies the responsible administrator(s) - that is, someone with the authority to grant the approval - about this request. In this example, let’s assume Frank is the admin user who will act as approver:

    • The application sends Frank an email with the request details and a link to the application’s request management page with buttons for approving and denying requests. (Optionally, the application can provide a Revoke button to cancel any currently active access grant.)

    • Frank clicks the Approve button to grant access.

    • The application makes a Cyral API call, moving the request to GRANTED status.

  5. The application notifies Nancy that her access request has been approved, and it provides a link to the Cyral Access Portal.

  6. Nancy follows the link to the Cyral Access Portal, gets her access token, and connects to the requested mysql repository. This approval gives Nancy access to the repository using the database account she chose, and she has access for the period of time specified in the request. The Cyral sidecar enforces this access control.

Main components

The main components of an access request system built with Cyral are:

  • your access request and approvals application

  • optionally, your organization's messaging platform or web portal platform (as the platform for your application) where users will submit requests and approvers will approve or deny them.

  • the Cyral control plane, where your administrators will manage which repositories offer access, and which database user accounts provide that access

  • the Cyral REST API for handling requests and approvals.

  • the Cyral Access Portal where data users can get access once approved

  • the Cyral sidecar(s) to enforce access control on each data repository

Role and requirements of the access request and approvals application

Your application is the user-facing component. It gathers the information needed for an access request and submits that request to the Cyral API. From this point, you can use Cyral API calls to manage the access request through its lifecycle, typically from request to approval to expiry. (See "Request lifecycle," below.)

A typical request and approval application will serve both requesters and approvers, and its approval function will be available only to those admin users you have designated as approvers.

It's up to your application to authenticate users and determine who can act as approver, and who can request access to which repositories. Cyral provides an audit log of requests and approvals, and each action is recorded in the log with the associated user id that your app supplied.

Request terminology

  • Approvals are scoped to a triplet of (repoID, userAccountID, userIdentity) - userIdentity is actually two fields, but conceptually this is just one item. For a given triplet, at any point in time, there can be at most one PENDING approval and one GRANTED approval. There is no limit to how many REJECTED and REVOKED approvals there can be.

    The rules governing approval creation and amendment ensure that the above invariant is upheld at all times.

  • Data users submit approval requests to ask for access to a repository. Once received by the approvals service, they are embedded inside an approval object, which contains the details of the request and additional information.

  • Admin users manage approvals by granting, rejecting, and/or revoking them.

  • Approval status: An approval object can be in one of the following statuses: PENDING, GRANTED, REJECTED, or REVOKED.

Request lifecycle

New request lifecycle

New requests have the status PENDING. An approver (or an auto-approval rule) can approve the request, in which case its status becomes GRANTED, or deny the request, in which case its status becomes REJECTED. Once a request has been GRANTED, the access request application will typically give the user a link to the Cyral Access Portal, where they can get an access token and finish connecting to the repository. An approver can revoke any GRANTED access request, in which case its status becomes REVOKED.

Amended request lifecycle

Request amendment lifecycle for an amendment to a PENDING request:

Request amendment lifecycle for an amendment to an already-approved request:

Example API workflow

Below, we'll show the typical workflow from the creation of a request to its approval or rejection, based on the example application and users we introduced earlier.

Requesting access

The main entry point for users will be the functions to create an approval request:

post: /v1/repos/{repoID}/approvals

At a high level, the client application needs to gather from the data user (Nancy, in our example) all the details of the data repository she intends to access, in order to forward them to the approvals service. These details include, but are not limited to:

  • Repository

  • User account within it

  • Nancy’s identity

    Note that validating the user’s identity is the application’s responsibility. In general, this should be extracted from some trustworthy source (such as a JWT or from a signed request), but it’s entirely up to the application how to do this.

  • Time window for the access (expressed as a pair of timestamps)

  • Optional policy overrides, such as specific columns from a table (we refer to this as field-level access) Policy overrides are a list of strings that represent attributes from the Data Map.

Once this information is received by the approvals service, it is validated and stored. (It's possible that the user's request might be automatically granted, which means Nancy doesn’t need to wait until Frank gets around to granting her request -- this depends on the user account’s configuration.) Regardless of whether it was automatically granted or not, the client application will receive the approval's ID and its status.

Depending on the status, the client application may choose, for instance, to notify the approver, Frank, about a PENDING request so he can grant it, or notify both data user and approver about a GRANTED request. How to handle this is up to the client application, but besides gathering the data and sending it over, there is really no other application-side logic. Just as it was with other users, it is the application's responsibility to validate the identity of the approver and provide approval rights to only those users who are tasked with approving/denying/revoking access requests.

Sample: request access payload

post: "/v1/repos/{repoID}/approvals"

{
"approvalRequest": {
"repoID": "<some repo ID>",
"userAccountID": "<some user account ID>",
"identity": {
"type": "email",
"name": "nancy.drew@hhiu.us"
},
"validFrom": "2022-05-18T20:45:00Z",
"validUntil": "2022-05-18T20:50:00Z",
"overrides": {
"fields": [
"foo",
"bar"
]
}
},
"actor": {
"type": "email",
"name": "nancy.drew@hhiu.us"
},
"source": "slack",
"comments": "These are my comments"
}

Amending a request

It is possible that users might make mistakes when creating a request (for instance, maybe Nancy initially thought 4 hours of access would be enough, but later decided she actually needed 8 hours) so Cyral provides an endpoint through which the requesting user can amend their original request. Just as with the Access Request endpoint, the amendment can be automatically granted access or it may need an approver to grant access, depending on the repository user configuration.

Similarly to requesting access from scratch, client applications need to gather information from the user about the amendment and send it over to the approvals service. The service ensures that the amendment does not break any of the service's rules/invariants and, if the amendment falls within the rules, it will be created.

Depending on the status of the original (parent) approval and the amendment, different actions can be triggered, such as the parent being overwritten, or a second approval being created.

Here are the relevant combinations:

  • Parent is PENDING: regardless of the amendment’s status, the parent will be overwritten

  • Parent is GRANTED:

    • If the amendment is GRANTED as well (i.e from an automatic grant) then it will overwrite the parent, as there’s no need to wait for an approver's intervention

    • If the amendment is PENDING then a second approval object will be created. Just to be clear, the parent will remain GRANTED, and a second approval, with PENDING status, will be created. An approver will eventually need to grant this PENDING approval; when that happens, the parent will be overwritten.

  • There is a GRANTED approval and a PENDING approval (i.e path 2.b above was triggered already and a new amendment was created):

    • If the amendment is PENDING then it will overwrite the PENDING approval.

    • If the amendment is GRANTED, then it will overwrite the parent approval, and the old amendment (i.e the approval in PENDING status) will be deleted.

note

Changing the repoID, userAccountID, or the identity of an approval via an amendment is forbidden, and will result in an error. The rest of the fields are open for modification.

The endpoint for amendments is:

patch: /v1/repos/{repoID}/approvals/{approvalID}

Sample: amend request payload

patch: "/v1/repos/{repoID}/approvals/{approvalID}"

{
"approvalRequest": {
"repoID": "<some repo ID>",
"userAccountID": "<some user account ID>",
"identity": {
"type": "username",
"name": "nancydrew"
},
"validFrom": "2022-05-18T18:00:00Z",
"validUntil": "2022-05-18T18:02:00Z"
},
"actor": {
"type": "email",
"name": "nancy.drew@hhiu.us"
},
"comments": "Amending the request"
}

Managing the status of an approval

Once a request is materialized as an Approval object, approvers may interact with it through the “manage” endpoint. The main actions that the approver can take by using this endpoint are:

  • Granting a request

  • Rejecting a request

  • Revoking an already granted request

caution

It's up to your application to authenticate users and determine who can act as approver, and who can request access to which repositories. When creating buttons that grant, reject, or revoke access, take care to display them only to authorized users with approval rights.

Sample: manage approval payload

Here's an example showing an approval. That is, we perform a GRANT action on a request whose id is {approvalID}. The approver is identified as "frank.hardy@hhiu.us":

post: "/v1/repos/{repoID}/approvals/{approvalID}/manage"

{
"approvalAction": "GRANT",
"comments": "granting a request",
"modCounter": 1,
"actor": {
"type": "email",
"name": "frank.hardy@hhiu.us"
}
}

The payload here is very simple: it’s essentially the action to take, who took it, and the comments to add some context to the action.

Note that there’s a fourth piece, the modCounter. This is used to keep track of amendments to approvals. Basically, when an approval is created, it has modCounter = 0, and every time it is amended the modCounter is incremented by one. Finally, in order for the “managing” to go through, the modCounter it sends as part of the payload needs to match the modCounter that is stored in the Approval object. If it doesn’t then we error out.

This modCounter serves the purpose of keeping track of the “version” of an amendment. Consider the case where an approval is amended multiple times. We don’t want approvers accidentally granting an “old” version of the approval (as it is arguably wrong, which is precisely what prompted the creation of a newer amendment). Thus, we associate an approval with a modCounter, and approvers can only interact with the approval if they supply the correct modCounter. In other words, if your application provides the approver with some sort of button for granting an approval, then typically each time an amendment is created a new button is also created. In this case you want to make sure that only the most recent button works. By tying the modCounter to both the approval and the button, we can enforce this behavior.

Listing approvals

The endpoint to list approvals differs a bit from other “list” endpoints, in the sense that we did not implement it as a GET, but rather as a POST. The reason for this is that this endpoint can take as input a set of filters, and generating the REST API using gRPC gateway precludes GET endpoints from having a body. Besides that, this is a fairly typical listing endpoint with filtering and cursor-based pagination capabilities.

Sample: list approvals payload

post: "/v1/repos/{repoID}/approvals/search"

{
"filter": {
"predicates": [
{
"field": "approvalRequest.identity.name",
"relOp": "contains",
"stringVal": "foo"
},
...
]
},
"sortCriteria": "USER",
"descending": true,
"pageSize": 30,
"pageAfter": "myApprovalID"
}

Here’s the breakdown of this payload:

  • filter contains a single predicates field, which in turn consists of a bunch of conditions. All these conditions need to be met in order for an approval to be returned by this endpoint

    Default value if not supplied: an empty filter, which means “return all approvals”.

  • sortCriteria specifies what is the field to be used to calculate the order. As of today, it supports “USER” and “APPROVER”

    Default value if not supplied: “USER”

  • descending changes the direction of the sorting

    Default if not supplied: false

  • pageSize indicates how many approvals to return per call

    Default if not supplied: 20

  • pageAfter and pageBefore are used to implement cursor-based pagination.

    Default if not supplied: if neither pageAfter nor pageBefore are supplied, then we will return the first pageSize worth of approvals. Note that passing them both at the same time is an error.

Note that the above filters are somewhat flexible, allowing callers to specify almost every field from an Approval object as the target of a comparison.

The format of the predicates is as follows:

  • field: this is the thing that will be compared. It is specified as the dot-separated “path” to reach an element within the approval object. For instance, an approval has an approvalRequest field, which has an identity field, which in turn has a name field. Thus, if we want to filter by name, we pass approvalRequest.identity.name as the field. The currently supported fields that can be filtered on are the following (note that the nesting indicates that these fields should be split by a period while building them):

    • approvalID
    • approvalRequest
      • userAccountID
      • identity
        • name
        • type
      • validFrom
        • seconds
      • validUntil
        • seconds
      • source
      • comments
    • approvalStatus
    • granter
      • name
      • type
    • modCounter
    • isAmendment
    • parentApprovalID
    • hasAmendment
    • childApprovalID
  • relOp: this is the relational operator to use. These are the supported relOps:

    • lt (less-than, integers)
    • le (less-than or equal, integers)
    • eq (equals, integers)
    • ge (greater-than or equal, integers)
    • gt (greater-than, integers)
    • contains (substring match)
    • strEq (string equality)
    • containsCaseInsensitive (as above, but case insensitive)
    • strEqCaseInsensitive (as above, but case insensitive)
    • regexp (regular expression matching)
    • is (used for booleans)
  • stringVal: in this particular example, since we’re using contains, then the “value” with which the field will be compared has to be a string, so we supply a stringVal.

    • For all integer operations, this should be intVal
    • For all string operations, this should be stringVal
    • For boolean operations, this should be boolVal

Other APIs

The approvals service takes care of dealing with all the logic and nuances related to creating approval objects, which are consumed by the sidecar in order to provide access to data resources. However, in order to provide a complete user experience, customer applications need to do a few other things, such as showing all the available repositories or the fields for overrides. This section describes the APIs that will perform all these other duties.

Listing repositories and the local accounts within them

The first thing that most applications will deal with is letting users select which repository they want access to, as well as the local account within it that they want to use.

Listing all repositories is done with a single call

Listing repos:

curl -X GET <your_control_plane_address>/v1/repos | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 530 100 530 0 0 2774 0 --:--:-- --:--:-- --:--:-- 2760
{
"repos": [
{
"id": "<some ID>",
"repo": {
"name": "<repo name>",
"type": "postgresql",
"repoHost": "127.0.0.1",
"repoPort": 5432,
"connParams": {
"connDraining": {
"auto": true,
"waitTime": 0
}
},
"labels": [],
"properties": {
"mongodb-server-type": "standalone"
}
},
"sidecars": null
},
{
"id": "<another ID>",
"repo": {
"name": "<another repo name>",
"type": "mysql",
"repoHost": "127.0.0.1",
"repoPort": 3306,
"connParams": {
"connDraining": {
"auto": true,
"waitTime": 0
}
},
"labels": [],
"properties": {
"mongodb-server-type": "standalone"
}
},
"sidecars": null
}
]
}

The output is fairly easy to parse: each item represents a repository - as it exists in the Control Plane -, with all of its details. For client applications, the name - in order to display it to their users - and the ID - in order to pass it to the approvals service - are probably the most interesting pieces of data.

The second piece of this puzzle is listing the local accounts under each repo. This is done as follows.

Listing local (user) accounts under a repo

curl -X GET <your_control_plane_address>/v1/repos/{repoID}/userAccounts | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 277 100 277 0 0 1338 0 --:--:-- --:--:-- --:--:-- 1331
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 273 100 273 0 0 1351 0 --:--:-- --:--:-- --:--:-- 1351
{
"userAccountList": [
{
"userAccountID": <someUserAccountID>,
"name": "foobar",
"authDatabaseName": "",
"authScheme": {
"gcpSecretManager": {
"secretName": "GCP secret name"
}
},
"config": {
"approvalConfig": {
"automaticGrant": true,
"maxAutomaticGrantDuration": "3600s"
}
}
}
]
}

This endpoint simply returns a list of all the user accounts under the specified repo.

Extracting fields from the data map

An important part of what can be achieved through approvals is overriding an existing policy. In particular, a policy may disallow reads to some column of some table, but an approval can override this and allow them. We call this feature field-level access. These fields come from the data map - which is configured in the Control Plane -. Here’s a sample query against the datamaps endpoint.

Listing datamaps

curl -X GET <your_control_plane>/v1/repos/{repoID}/datamap -H "Authorization: Bearer $TOKEN" | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 183 100 183 0 0 436 0 --:--:-- --:--:-- --:--:-- 435
{
"labels": {
"ADDRESS": {
"attributes": [
"test.pii.address"
],
"endpoints": []
},
"AGE": {
"attributes": [
"test.pii.age"
],
"endpoints": []
},
"CCN": {
"attributes": [
"test.banking.ccn"
],
"endpoints": []
}
}
}

Permissions

Data users, approvers, and applications must have the right permissions to request and approve access in Cyral.

User permissions

Required Cyral permission roles for users:

  • Data users: To request access to a repository, data users must have the View Token role. Cyral provides this role by default for all users, admins, super admins, and Cyral admins (that is, the role exists in the out-of-the-box groups: UserGroup, AdminGroup, SuperAdminGroup, CyralAdminGroup, and EveryoneGroup).

  • Approvers: To read approvals and manage approvals (grant/reject/revoke/delete them), approvers need the Modify Sidecars and Repositories role. This role is assigned by default to AdminGroup, SuperAdminGroup, and CyralAdminGroup

Application permissions

Applications call Cyral APIs on behalf of users, so they need a Cyral API access key with the appropriate permissions, as well as the ability to call the the non-/self endpoints.

Required Cyral permission roles for apps:

  • Approval Management: This role allows the app to make access requests and manage approvals (grant, revoke, reject, and delete them). This allows the app to make requests on behalf of users, and it allows the app to manage approvals on behalf of approvers.
  • View Sidecars and Repositories: This role allows the app to list user accounts, repos, and sidecars.
  • View Datamaps: This role allows the app to list sensitive resources. This is required for all apps that support field-level access requests.