DEV Community

Cover image for How to Build an Audit Trail System Using DynamoDB Transactions and Streams
Sidra Saleem for SUDO Consultants

Posted on • Originally published at sudoconsultants.com

How to Build an Audit Trail System Using DynamoDB Transactions and Streams

Introduction

In today's digital landscape, maintaining an audit trail is crucial for ensuring data integrity, compliance, and accountability. An audit trail system logs every change made to the data, providing a historical record that can be used for debugging, auditing, and compliance purposes. Amazon DynamoDB, a fully managed NoSQL database service, offers powerful features like transactions and streams that can be leveraged to build a robust audit trail system.

In this article, we will walk through the process of building an audit trail system using DynamoDB transactions and streams. We will cover both CLI-based and AWS Console-based steps, including micro-steps, to ensure a comprehensive understanding of the implementation.

Prerequisites

Before we dive into the implementation, ensure that you have the following prerequisites in place:

  1. AWS Account: You need an AWS account to create and manage DynamoDB tables, streams, and other related resources.
  2. AWS CLI: Install and configure the AWS CLI on your local machine.
  3. IAM Permissions: Ensure that your IAM user or role has the necessary permissions to create and manage DynamoDB tables, streams, and Lambda functions.
  4. Basic Knowledge of DynamoDB: Familiarity with DynamoDB concepts like tables, items, and streams is recommended.

Architecture Overview

The audit trail system we will build consists of the following components:

  1. DynamoDB Table: This is the primary table where the application data will be stored.
  2. DynamoDB Stream: A stream that captures every change (insert, update, delete) made to the items in the DynamoDB table.
  3. Lambda Function: A Lambda function that processes the stream records and logs the changes to an audit trail table.
  4. Audit Trail Table: A separate DynamoDB table that stores the audit trail records.

Step 1: Create the Primary DynamoDB Table

AWS Console

  1. Log in to the AWS Management Console and navigate to the DynamoDB service.
  2. Click on "Create table".
  3. Enter the table name (e.g., PrimaryTable).
  4. Set the primary key:
    • Partition keyUserId (String)
    • Sort keyOrderId (String)
  5. Configure settings:
    • Table settings: Choose "Customize settings" if you want to configure throughput, encryption, and other advanced settings.
    • Read/write capacity mode: Choose "Provisioned" or "On-demand" based on your requirements.
  6. Click "Create" to create the table.

AWS CLI

aws dynamodb create-table \
    --table-name PrimaryTable \
    --attribute-definitions \
        AttributeName=UserId,AttributeType=S \
        AttributeName=OrderId,AttributeType=S \
    --key-schema \
        AttributeName=UserId,KeyType=HASH \
        AttributeName=OrderId,KeyType=RANGE \
    --billing-mode PROVISIONED \
    --provisioned-throughput \
        ReadCapacityUnits=5,WriteCapacityUnits=5

Step 2: Enable DynamoDB Streams on the Primary Table

AWS Console

  1. Navigate to the DynamoDB table you just created (PrimaryTable).
  2. Click on the "Exports and streams" tab.
  3. Under "DynamoDB stream details," click "Enable".
  4. Choose the stream view type: New and old images: Captures both the new and old versions of the item.
  5. Click "Enable stream".

AWS CLI

aws dynamodb update-table \
    --table-name PrimaryTable \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

Step 3: Create the Audit Trail Table

AWS Console

  1. Navigate to the DynamoDB service and click on "Create table".
  2. Enter the table name (e.g., AuditTrailTable).
  3. Set the primary key:
  4. Partition keyEventId (String) , Sort keyEventTimestamp (String)
  5. Configure settings: Table settings: Choose "Customize settings" if you want to configure throughput, encryption, and other advanced settings.
  6. Read/write capacity mode: Choose "Provisioned" or "On-demand" based on your requirements.
  7. Click "Create" to create the table.

AWS CLI

aws dynamodb create-table \
    --table-name AuditTrailTable \
    --attribute-definitions \
        AttributeName=EventId,AttributeType=S \
        AttributeName=EventTimestamp,AttributeType=S \
    --key-schema \
        AttributeName=EventId,KeyType=HASH \
        AttributeName=EventTimestamp,KeyType=RANGE \
    --billing-mode PROVISIONED \
    --provisioned-throughput \
        ReadCapacityUnits=5,WriteCapacityUnits=5

Step 4: Create a Lambda Function to Process Stream Records

AWS Console

  1. Navigate to the Lambda service and click on "Create function".
  2. Choose "Author from scratch".
  3. Enter the function name (e.g., AuditTrailProcessor).
  4. Choose the runtime (e.g., Python 3.8).
  5. Click "Create function".
  6. In the function code editor, paste the following Python code:
import json
import boto3
from datetime import datetime

dynamodb = boto3.resource('dynamodb')
audit_trail_table = dynamodb.Table('AuditTrailTable')

def lambda_handler(event, context):
    for record in event['Records']:
        if record['eventName'] in ['INSERT', 'MODIFY', 'REMOVE']:
            event_id = record['eventID']
            event_timestamp = datetime.utcnow().isoformat()
            event_name = record['eventName']
            user_id = record['dynamodb']['Keys']['UserId']['S']
            order_id = record['dynamodb']['Keys']['OrderId']['S']
            
            old_image = record['dynamodb'].get('OldImage', {})
            new_image = record['dynamodb'].get('NewImage', {})
            
            audit_trail_table.put_item(
                Item={
                    'EventId': event_id,
                    'EventTimestamp': event_timestamp,
                    'EventName': event_name,
                    'UserId': user_id,
                    'OrderId': order_id,
                    'OldImage': old_image,
                    'NewImage': new_image
                }
            )
    
    return {
        'statusCode': 200,
        'body': json.dumps('Audit trail recorded successfully')
    }
  1. Deploy the function by clicking the "Deploy" button.

AWS CLI

  1. Create a new file named lambda_function.py and paste the above Python code into it.
  2. Zip the file:
zip lambda_function.zip lambda_function.py
  1. Create the Lambda function:
aws lambda create-function \
    --function-name AuditTrailProcessor \
    --runtime python3.8 \
    --role arn:aws:iam::<your-account-id>:role/<your-lambda-execution-role> \
    --handler lambda_function.lambda_handler \
    --zip-file fileb://lambda_function.zip
  1. Update the function code (if needed):
aws lambda update-function-code \
    --function-name AuditTrailProcessor \
    --zip-file fileb://lambda_function.zip

Step 5: Configure the Lambda Trigger

AWS Console

  1. Navigate to the Lambda function you just created (AuditTrailProcessor).
  2. Click on "Add trigger".
  3. Choose "DynamoDB" as the trigger source.
  4. Select the DynamoDB table (PrimaryTable).
  5. Set the batch size (e.g., 100).
  6. Click "Add" to add the trigger.

AWS CLI

  1. Get the DynamoDB stream ARN:
aws dynamodb describe-table \
    --table-name PrimaryTable \
    --query "Table.LatestStreamArn" \
    --output text
  1. Add the DynamoDB trigger to the Lambda function:
aws lambda create-event-source-mapping \
    --function-name AuditTrailProcessor \
    --event-source <dynamodb-stream-arn> \
    --batch-size 100 \
    --starting-position LATEST

Step 6: Test the Audit Trail System

Insert an Item into the Primary Table

AWS Console

  1. Navigate to the DynamoDB table (PrimaryTable).
  2. Click on "Explore table items".
  3. Click "Create item".
  4. Enter the item details: UserIduser1 , OrderIdorder1
  5. Other attributes: Add any additional attributes as needed.
  6. Click "Create item".

AWS CLI

aws dynamodb put-item \
    --table-name PrimaryTable \
    --item '{
        "UserId": {"S": "user1"},
        "OrderId": {"S": "order1"},
        "ProductName": {"S": "Laptop"},
        "Quantity": {"N": "1"}
    }'

Verify the Audit Trail

AWS Console

  1. Navigate to the DynamoDB table (AuditTrailTable).
  2. Click on "Explore table items".
  3. Verify that a new audit trail record has been created with the details of the insert operation.

AWS CLI

aws dynamodb scan \
    --table-name AuditTrailTable \
    --filter-expression "UserId = :user_id" \
    --expression-attribute-values '{
        ":user_id": {"S": "user1"}
    }'

Update an Item in the Primary Table

AWS Console

  1. Navigate to the DynamoDB table (PrimaryTable).
  2. Click on "Explore table items".
  3. Select the item you want to update.
  4. Modify the item (e.g., change the Quantity to 2).
  5. Click "Save changes".

AWS CLI

aws dynamodb update-item \
    --table-name PrimaryTable \
    --key '{
        "UserId": {"S": "user1"},
        "OrderId": {"S": "order1"}
    }' \
    --update-expression "SET Quantity = :qty" \
    --expression-attribute-values '{
        ":qty": {"N": "2"}
    }'

Verify the Audit Trail

AWS Console

  1. Navigate to the DynamoDB table (AuditTrailTable).
  2. Click on "Explore table items".
  3. Verify that a new audit trail record has been created with the details of the update operation.

AWS CLI

aws dynamodb scan \
    --table-name AuditTrailTable \
    --filter-expression "UserId = :user_id" \
    --expression-attribute-values '{
        ":user_id": {"S": "user1"}
    }'

Delete an Item from the Primary Table

AWS Console

  1. Navigate to the DynamoDB table (PrimaryTable).
  2. Click on "Explore table items".
  3. Select the item you want to delete.
  4. Click "Delete item".
  5. Confirm the deletion.

AWS CLI

aws dynamodb delete-item \
    --table-name PrimaryTable \
    --key '{
        "UserId": {"S": "user1"},
        "OrderId": {"S": "order1"}
    }'

Verify the Audit Trail

AWS Console

  1. Navigate to the DynamoDB table (AuditTrailTable).
  2. Click on "Explore table items".
  3. Verify that a new audit trail record has been created with the details of the delete operation.

AWS CLI

aws dynamodb scan \
    --table-name AuditTrailTable \
    --filter-expression "UserId = :user_id" \
    --expression-attribute-values '{
        ":user_id": {"S": "user1"}
    }'

Step 7: Implement DynamoDB Transactions (Optional)

DynamoDB transactions allow you to perform multiple operations (insert, update, delete) as a single atomic operation. This can be useful if you want to ensure that both the primary table and the audit trail table are updated atomically.

Example: Insert an Item with Transaction

AWS CLI

aws dynamodb transact-write-items \
    --transact-items '[
        {
            "Put": {
                "TableName": "PrimaryTable",
                "Item": {
                    "UserId": {"S": "user2"},
                    "OrderId": {"S": "order2"},
                    "ProductName": {"S": "Smartphone"},
                    "Quantity": {"N": "1"}
                }
            }
        },
        {
            "Put": {
                "TableName": "AuditTrailTable",
                "Item": {
                    "EventId": {"S": "event2"},
                    "EventTimestamp": {"S": "2023-10-01T12:00:00Z"},
                    "EventName": {"S": "INSERT"},
                    "UserId": {"S": "user2"},
                    "OrderId": {"S": "order2"},
                    "OldImage": {"M": {}},
                    "NewImage": {
                        "M": {
                            "UserId": {"S": "user2"},
                            "OrderId": {"S": "order2"},
                            "ProductName": {"S": "Smartphone"},
                            "Quantity": {"N": "1"}
                        }
                    }
                }
            }
        }
    ]'

Verify the Audit Trail

AWS CLI

aws dynamodb scan \
    --table-name AuditTrailTable \
    --filter-expression "UserId = :user_id" \
    --expression-attribute-values '{
        ":user_id": {"S": "user2"}
    }'

Conclusion

In this article, we have demonstrated how to build an audit trail system using DynamoDB transactions and streams. We covered the creation of DynamoDB tables, enabling streams, creating and configuring a Lambda function to process stream records, and testing the system. Additionally, we explored the use of DynamoDB transactions to ensure atomic updates across multiple tables.

By implementing this audit trail system, you can maintain a comprehensive record of all changes made to your data, ensuring data integrity, compliance, and accountability. This system can be further enhanced by adding features like real-time alerts, data archiving, and integration with other AWS services like CloudWatch and S3.

Additional Considerations

  1. Scalability: Ensure that your DynamoDB tables and Lambda function are configured to handle the expected load. Consider using on-demand capacity mode for DynamoDB and optimizing your Lambda function for performance.
  2. Security: Implement appropriate IAM policies and encryption to secure your data and Lambda function.
  3. Monitoring: Use CloudWatch to monitor the performance and health of your Lambda function and DynamoDB tables.
  4. Cost Optimization: Monitor and optimize the cost of your DynamoDB tables, streams, and Lambda function to ensure cost-effectiveness.

By following the steps outlined in this article, you can build a robust and scalable audit trail system using DynamoDB transactions and streams, ensuring that your data is always traceable and secure.

Top comments (0)