Skip to main content
Version: v4.1

Service accounts for DynamoDB

For a user connecting to DynamoDB with native AWS credentials, you can create a DynamoDB service account in Cyral. This allows you to attribute extra identity elements to the user's requests.

You will bind the service account to its DynamoDB repository entry in Cyral. This binding identifies the AWS IAM role the application that's connecting to DynamoDB. When the user connects to DynamoDB, their connecting client application will pass a Cyral access_token value that identifies them as using the service account.

Create a service account

Follow the steps below to set up a DynamoDB service account in Cyral. Once a user has a service account, you can generate access tokens that allow that user to connect to DynamoDB through the Cyral sidecar.

note

You cannot create service accounts in the Cyral control plane UI. Instead, use the API calls shown below.

Get an API access token

As a Cyral administrator, get your API access token for access to the Cyral REST API.

  1. Log in to the Cyral control plane UI.

  2. In the upper right corner of the UI, click the icon with your initials, and click Profile.

  3. Scroll down to the API Access section.

  4. Click the copy icon to copy your JWT token. We'll refer to this JWT token as $TOKEN in the example API calls shown below.

Get the configuration extension ID

Get the ID for the default configuration extension that is automatically created in your Cyral control plane. Make the following request to the Cyral API:

curl -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" https://<$CONTROL_PLANE_ENDPOINT>/v1/integrations/confExtensions/instances?type=jwtIdentityValidation 

expected output:

{
"entries": [
{
"id": "2CA5XvU3D3iVLfnX51I5w0NRki5",
"attributes": {
"name": "BuiltInJWTIdentityExtractionAndValidationPlugin",
"parameters": "{}",
"purpose": "identity",
"templateID": "2CA5XstWXr3OKW0xsc2LW8ijQq6",
"templateType": "jwtIdentityValidation",
"category": "builtin"
}
}
]
}

This API call returns a single output with templateType: jwtIdentityValidation and category: builtin. The id shown in the output is your configuration extension ID, also known as the connectionDriverInstanceID. You will use this ID in subsequent API calls.

Create the DynamoDB service account

To create a new service account for your DynamoDB repository, make the following request to the Cyral API, where:

  • accessTokenLifeSpan defines the life span (in seconds) of the token you're about to generate. This field is optional. When undefined, defaults to 3600 (1 hour). The minimum allowed value is 60 (1 minute). You cannot change this value after the service account has been created.
  • connectionDriverInstanceIDs is the configuration extension ID from your earlier call to the /v1/integrations API endpoint. In our example, 2CA5XvU3D3iVLfnX51I5w0NRki5.
  • description is a description of this service account
  • name is the ARN of the IAM role this account will use. This role must have access to your Dynamo repository.
curl -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" https://<$CONTROL_PLANE_ENDPOINT>/v1/repos/2CIkzWJbdR581iV5PZAFALku9Ub/serviceAccounts -d '
{
"apiClient": {"create": true, "accessTokenLifeSpan": 3600},
"connectionDriverInstanceIDs": ["2CA5XvU3D3iVLfnX51I5w0NRki5"],
"description": "DynamoDB Example ServiceAccount",
"name": "arn:aws:iam::1234567890:role/DynamoRoleWithAccessToDynamo"
}
'

The output will look similar to:

{
"uuid": "2D5TqsTIDcxAgRKCPTfnf1W6LOp",
"apiCredential": {
"clientID": "drsa/sa/default/c33c6462-b3e5-4c81-96cf-86fef55c881b",
"clientSecret": "AoEr4LbqWsFNE893hh2Ewd2_NHY0cinKC7ssa5wBuqi7VJBa"
}
}

In this example, each generated token associated with the clientID drsa/sa/default/c33c6462-b3e5-4c81-96cf-86fef55c881b will be valid for 3600 seconds (1 hour).

Generate an access token

When users connect to DynamoDB, their connecting client application must pass a Cyral access_token value to authenticate them. This associates the user with the service account you created.

To generate an access token, make the following request to the Cyral API, where $CLIENT_ID and $CLIENT_SECRET are the clientID and clientSecret values you retrieved from the /v1/repos/<repo id>/serviceAccounts API endpoint. (See the preceding section for an example.)

curl https://<$CONTROL_PLANE_ENDPOINT>/v1/users/oidc/token -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET"

The output will look similar to:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJITTVISXAxZkI4UVNtdXAtNWx4bW1MTWZYYzFFcXBuZDF2R25oZzJkQXI4In0.eyJleHAiOjE2NTk5OTA3ODEsImlhdCI6MTY1OTk4ODgwMSwianRpIjoiN2M0NjVjY2EtYWRhMS00NzJhLTk3OTQtNGM1OTc5YTJmZGY3IiwiaXNzIjoiaHR0cHM6Ly9qYzA3MTktbWFpbi1hMDEtY3RsLms4LXNhbmRib3guZ2NwLmN5cmFsLmNvbS9hdXRoL3JlYWxtcy9kZWZhdWx0Iiwic3ViIjoiZTM1MDk3MGUtMGMxZi00M2ZiLTg4YjItODBlODQ4MDQ0ZGEzIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZHJzYS9zYS9kZWZhdWx0L2MzM2M2NDYyLWIzZTUtNGM4MS05NmNmLTg2ZmVmNTVjODgxYiIsInNlc3Npb25fc3RhdGUiOiI3NWY2NTA0OC1lMWJkLTQ2YTktYTgzYy1hMTFhMmYwNjIxYzUiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJjbGllbnRIb3N0IjoiMTI3LjAuMC42IiwiY2xpZW50SWQiOiJkcnNhL3NhL2RlZmF1bHQvYzMzYzY0NjItYjNlNS00YzgxLTk2Y2YtODZmZWY1NWM4ODFiIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LWRyc2Evc2EvZGVmYXVsdC9jMzNjNjQ2Mi1iM2U1LTRjODEtOTZjZi04NmZlZjU1Yzg4MWIiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC42In0.POdZCiNXqbEf_-E-fM_rCeUdI-rwPWSScn7865zcYo-BPDF9lI8E7tdaMSdzXdQol6QaeMHMop2atD8oLZHKh9V0X0_2lQ4zBRW0QPczeJP4FxgqvSV9iNSJ6lRdhYOnV86kWIJ-V4WZrpsWg4438bSeZMcMBfY_O_3gO_0PFysY-iyv__uoNcMu7_6PdslWmYy14whfSC8mq14t1g_NT4thZBrKcPdpYHZWrJntZGF2vdSWWVs-vUkYqI1gKnRubP1oRzfvcnPC9QdKETfPE6UU2wfBz5c_-iTkW2rgN1UQexdbGYxNs1Yn7zOk0Nph5lp_8ty4A4vClXca32109",
"expires_in": "3600",
"refresh_expires_in": "36000",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwOGJlZTU3ZC1mMjIzLTQyNDEtOGY4My1lZGZkNjg3MzMwMDgifQ.eyJleHAiOjE2NjAwMjQ4MDEsImlhdCI6MTY1OTk4ODgwMSwianRpIjoiMzQ2YmRlZjgtMGNmOS00Y2ZhLTg4NGEtOGVlN2MwMWMwYWYwIiwiaXNzIjoiaHR0cHM6Ly9qYzA3MTktbWFpbi1hMDEtY3RsLms4LXNhbmRib3guZ2NwLmN5cmFsLmNvbS9hdXRoL3JlYWxtcy9kZWZhdWx0IiwiYXVkIjoiaHR0cHM6Ly9qYzA3MTktbWFpbi1hMDEtY3RsLms4LXNhbmRib3guZ2NwLmN5cmFsLmNvbS9hdXRoL3JlYWxtcy9kZWZhdWx0Iiwic3ViIjoiZTM1MDk3MGUtMGMxZi00M2ZiLTg4YjItODBlODQ4MDQ0ZGEzIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImRyc2Evc2EvZGVmYXVsdC9jMzNjNjQ2Mi1iM2U1LTRjODEtOTZjZi04NmZlZjU1Yzg4MWIiLCJzZXNzaW9uX3N0YXRlIjoiNzVmNjUwNDgtZTFiZC00NmE5LWE4M2MtYTExYTJmMDYyMWM1Iiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIn0.4e5q5lp3J1IiwOdQy0C55pAx-FFa32109jfKh_59zgY",
"token_type": "bearer",
"not_before_policy": "0",
"session_state": "75f65048-e1bd-46a9-a83c-alla2f0621c5",
"scope": "email profile"
}
note

The request intentionally does not carry the extra headers Content-Type: application/json and Authorization: Bearer $TOKEN, as they are not applicable.

Attach the resulting access_token value to all DynamoDB requests made by your application. You'll pass this value in the HTTP header cyral.token. See the section below for an example.

Connect to DynamoDB with an access token

Once you've created a service account in Cyral and retrieved an access_token associated with that account, you set your client applications to pass the access_token when they connect to DynamoDB. This ensures the user's service account identity is logged.

Below, we provide an example based on the AWS Ruby SDK that passes the access_token value in the HTTP header field, cyral.token.

Create a plugin

Create a plugin for the HTTP client used by the SDK. In Ruby, this client is the Seahorse module:

class CyralCustomHeader < Seahorse::Client::Plugin
class Handler < Seahorse::Client::Handler
def call(context)
context.http_request.headers['cyral.token'] = 'access_token value'
@handler.call(context)
end
end
handler(Handler, step: :sign, priority: 0)
end

Add the plugin to your client

Continuing with the example of a DynamoDB SDK client, you must add the plugin to the client:

Aws::DynamoDB::Client.add_plugin(CyralCustomHeader)

Here's the full example:

require 'aws-sdk-dynamodb'

class CyralCustomHeader < Seahorse::Client::Plugin
class Handler < Seahorse::Client::Handler
def call(context)
context.http_request.headers['cyral.token'] = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJITTVISXAxZkI4UVNtdXAtNWx4bW1MTWZYYzFFcXBuZDF2R25oZzJkQXI4In0.eyJleHAiOjE2NTk5OTA3ODEsImlhdCI6MTY1OTk4ODgwMSwianRpIjoiN2M0NjVjY2EtYWRhMS00NzJhLTk3OTQtNGM1OTc5YTJmZGY3IiwiaXNzIjoiaHR0cHM6Ly9qYzA3MTktbWFpbi1hMDEtY3RsLms4LXNhbmRib3guZ2NwLmN5cmFsLmNvbS9hdXRoL3JlYWxtcy9kZWZhdWx0Iiwic3ViIjoiZTM1MDk3MGUtMGMxZi00M2ZiLTg4YjItODBlODQ4MDQ0ZGEzIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZHJzYS9zYS9kZWZhdWx0L2MzM2M2NDYyLWIzZTUtNGM4MS05NmNmLTg2ZmVmNTVjODgxYiIsInNlc3Npb25fc3RhdGUiOiI3NWY2NTA0OC1lMWJkLTQ2YTktYTgzYy1hMTFhMmYwNjIxYzUiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJjbGllbnRIb3N0IjoiMTI3LjAuMC42IiwiY2xpZW50SWQiOiJkcnNhL3NhL2RlZmF1bHQvYzMzYzY0NjItYjNlNS00YzgxLTk2Y2YtODZmZWY1NWM4ODFiIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LWRyc2Evc2EvZGVmYXVsdC9jMzNjNjQ2Mi1iM2U1LTRjODEtOTZjZi04NmZlZjU1Yzg4MWIiLCJjbGllbnRBZGRyZXNzIjoiMTI3LjAuMC42In0.POdZCiNXqbEf_-E-fM_rCeUdI-rwPWSScn7865zcYo-BPDF9lI8E7tdaMSdzXdQol6QaeMHMop2atD8oLZHKh9V0X0_2lQ4zBRW0QPczeJP4FxgqvSV9iNSJ6lRdhYOnV86kWIJ-V4WZrpsWg4438bSeZMcMBfY_O_3gO_0PFysY-iyv__uoNcMu7_6PdslWmYy14whfSC8mq14t1g_NT4thZBrKcPdpYHZWrJntZGF2vdSWWVs-vUkYqI1gKnRubP1oRzfvcnPC9QdKETfPE6UU2wfBz5c_-iTkW2rgN1UQexdbGYxNs1Yn7zOk0Nph5lp_8ty4A4vClXcvcKnHYA'
@handler.call(context)
end
end
handler(Handler, step: :sign, priority: 0)
end

def run_me
region = 'us-east-2'
table_name = 'ExampleTable'

Aws::DynamoDB::Client.add_plugin(CyralCustomHeader)

dynamodb_client = Aws::DynamoDB::Client.new(
region: region,
http_proxy: "http://<cyral-sidecar-endpoint>:<port>",
ssl_ca_bundle: "path_to_certificate_bundle",
)

table_item = {
table_name: table_name,
}

puts "Getting information from DynamoDB"
result = dynamodb_client.scan(table_item)
puts result
end

run_me if $PROGRAM_NAME == __FILE__

Log user activity

When a user interacts with DynamoDB after authenticating with their service account access token, the service account details appear in Cyral's data activity log.

When the custom header cyral.token is present and it has a valid JWT token for a given service account, the service account name is propagated to the identity.dbRole field in the Cyral data activity log, as shown in this example:

{
"activityId": "192.168.10.2:36008:1659960215291201575:1",
"activityTime": "2022-08-08 12:03:35.657840547 +0000 UTC",
"activityTimeNanos": 1659960215657840547,
"activityTypes": [
"query"
],
"identity": {
"endUser": "AKIAVHNN4RHCGULQJJMF",
"repoUser": "AKIAVHNN4RHCGULQJJMF",
"dbRole": "arn:aws:iam::1234567890:role/DynamoRoleWithAccessToDynamo"
},
"repo": {
"id": "2D4ZF95FQccL1WvoT4Y9SJ4O3wO",
"name": "dynamodb1659960209802756438",
"type": "dynamodb",
"host": "dynamodb.dynamic-cyral-1.amazonaws.com",
"port": 443
},
"client": {
"connectionId": "192.168.10.2:36008:1659960215291201575",
"connectionTime": "2022-08-08 12:03:35.291201575 +0000 UTC",
"connectionTimeNanos": 1659960215291201575,
"host": "192.168.10.2",
"port": 36008,
"applicationName": "Boto3/1.21.37 Python/3.8.13 Linux/5.10.0-16-cloud-amd64 Botocore/1.24.37"
},
"sidecar": {
"id": "2D4WCGO11HrFCU2m4d7pIUeu983",
"name": "e2e",
"autoScalingGroupInstance": "SINGLETON"
},
"request": {
"statement": "{\"TableName\": \"inpatient_charges\"}",
"statementType": "SCAN",
"isSensitive": false,
"statementData": {
"targetRegion": "us-east-1"
}
},
"response": {
"message": "OK",
"isError": false,
"records": 52,
"bytes": 34,
"executionTime": "93.136µs",
"executionTimeNanos": 93136
},
"policyViolated": false
}