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.
Log in to the Cyral control plane UI.
In the upper right corner of the UI, click the icon with your initials, and click Profile.
Scroll down to the API Access section.
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 accountname
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
}