QLDB Access Control
- 5 minutes read - 1028 wordsWhen QLDB was first launched, it provided a set of actions for interacting with the control plane API to manage ledgers (see here), but only a single action for interacting with a ledger over the data plane API. This meant any user or role required the qldb:sendCommand
permission for issuing a PartiQL
command against a ledger. With this IAM permission, you were able to execute all PartiQL
commands from simple lookups, to mutating current state with updates and deletes, and querying all revision history.
The latest release from the Amazon QLDB team provides support for fine-grained IAM permissions when interacting with a ledger, which helps enforce least-privilege. This blog post will show you how to get started, using the QLDB Shell
.
All code and setup instructions can be found in the QLDB access control demo repo
Pre-Requisites
In order to run the demo, the following is required:
-
The AWS Commmand Line Interface
AWS CLI
is installed. For more details see here -
The
jq
library is installed. For more details see here -
An AWS Profile is configured with a user with administrative permissions for initial setup.
-
QLDB Shell is installed - For more details see here
The current QLDB shell is written in Python, but there is also a branch available written in Rust that has additional features. A huge thank you to Mark Bowes and Ian Davies who rapidly turned around my feature request for multi-line support and added a tonne of new functionality. AWS provide prebuilt binaries for Linux, Windows and macOS. On macOS
the shell is integrated with the aws/tap
Homebrew tap:
xcode-select install # required to use Homebrew
brew tap aws/tap # Add AWS as a Homebrew tap
brew install qldbshell
qldb --ledger <your-ledger>
Setup
To setup the demo, clone the github repository and navigate to the shell-demo folder.
git clone https://github.com/AWS-South-Wales-User-Group/qldb-access-control-demo.git
cd qldb-access-control-demo/shell-demo
Follow the instructions to edit the qldb-access-control.yaml
CloudFormation template with your user, and create a new stack running the following command:
aws cloudformation deploy --template-file ./qldb-access-control.yaml --stack-name qldb-access-control --capabilities CAPABILITY_NAMED_IAM
This creates a new QLDB ledger with the name qldb-access-control
using the new STANDARD
permissions mode. The snippet that does this is shown below:
QLDBAccessControl:
Type: "AWS::QLDB::Ledger"
Properties:
DeletionProtection: false
Name: "qldb-access-control"
PermissionsMode: "STANDARD"
Tags:
- Key: "name"
Value: "qldb-access-control"
Before this release, the only permissions mode supported was ALLOW_ALL
, which allowed any user with this permission to execute any PartiQL command. This is now marked as legacy and should not be used. Deletion protection is disabled to allow simpler clean up at the end.
Role Permissions
As well as creating a QLDB Ledger with the name qldb-access-control
the cloudformation template sets up the following roles with associated permissions:
Each role has its own policy document setting out the permissions allowed. In order to execute any PartiQL command, permission must be given to the sendCommand
API action for the ledger resource. Explicit permission to PartiQL commands can then be given, taking into account that requests to run all PartiQL commands are denied unless explicitly allowed here. An example of a policy document is shown below:
Assuming a role
A number of helper scripts are provided to help assume the various roles:
source setupSuperUser.sh
source setupAdmin.sh
source setupAudit.sh
source setupReadOnly.sh
When running one of these scripts, it prints out details of the current user with the following command, which can also be run separately.
# print out the current identity
aws sts get-caller-identity
Finally, in order to assume another role, you will need to unset the current assumed role. This is because none of the roles have permission to perform the sts:AssumeRole
command. You can unset the current role using the following command:
source unset.sh
Testing Permissions
The demo gives a set of tasks with accompanying PartiQL statements to create tables, indexes, insert and update data, and query the revision history using various roles. Note how if the correct permission is not explicitly assigned to the role, then the command will fail with an error message like the following:
"Message":"Access denied. Unable to run the statement due to insufficient permissions or an improper variable reference"
Creating a policy for a specific table
Permissions can applied at a table level as well as a ledger level. The table-demo
folder in the repository shows an example of how this can be applied automatically using a custom resource.
This folder uses the Serverless Framework to create a custom resource and a new role with a policy attached to it that allows read access to a Keeper
table.
The original cloudformation stack in the shell-demo
folder outputs the value of the new QLDB ledger name it creates through the Outputs
section of the template as shown below:
Outputs:
qldbAccessControlLedger:
Value:
Ref: QLDBAccessControl
Export:
Name: qldb-access-control-demo
This value can then be referenced in the serverless.yml
file using the Fn::ImportValue
intrinsic function as follows:
!ImportValue qldb-access-control-demo
The custom resource lambda function is responsible for creating a Keeper
table and a Vehicle
table. When a table is created, the unique ID for the table is returned. This value is retrieved, and stored as a name/value pair. This gets returned in the optional data section as shown below:
const keeperResult = await createTable(txn, keeperTable);
const keeperIdArray = keeperResult.getResultList();
keeperId = keeperIdArray[0].get('tableId').stringValue();
const responseData = { requestType: event.RequestType,
'keeperId': keeperId };
await response.send(event, context, response.SUCCESS, responseData);
Finally, this value can be referenced using the Fn::GetAtt
instrinic function, and the full resource name created using the Fn::Join
instrinic function as follows:
- Effect: Allow
Action:
- 'qldb:PartiQLSelect'
Resource:
- !Join
- ''
- - 'arn:aws:qldb:#{AWS::Region}:#{AWS::AccountId}:ledger/'
- !ImportValue qldb-access-control-demo
- '/table/#{qldbTable.keeperId}'
Now when the new role is assumed, data can be queried successfully from the Keeper
table but not the Vehicle
table.
Conclusion
This blog post and associated code repository shows how you can take advantage of the new fine grained permissions now available with QLDB. This is a great addition, that enables the principle of least-privilege to be easily assigned to all resources in a QLDB ledger.
To find out more, read the QLDB Guide, follow the curated list of articles, tools and resources at Awesome QLDB or try it out our online demo to see QLDB in action at QLDB Demo