Visualizing IoT Data from Aurora DSQL with Grafana Cloud Free Tier
My home LoRaWAN sensors (water meter, pressure sensor, energy meter) push their measurements through AWS IoT Core into an Amazon Aurora DSQL database. To visualize this data I use Grafana Cloud on the free tier, which connects to DSQL through the standard PostgreSQL datasource.
This sounds straightforward, until you hit one detail: Aurora DSQL does not have database passwords. Authentication is done with IAM tokens that expire after a few hours at most. Grafana, on the other hand, stores a static password in its datasource configuration and has no idea the password it holds is a token with an expiry date.
This article covers:
- why the DSQL “password” is a token and why it must be refreshed often,
- a Lambda function that refreshes the token and pushes it to Grafana through the Grafana HTTP API,
- an EventBridge rule that invokes this Lambda every hour, with a retry because the Grafana free tier cluster can be asleep,
- why least privilege access matters a lot with DSQL, and how I created a read-only user for Grafana.
All the infrastructure is defined with AWS CDK in Python.
The architecture
Sensor data flows from AWS IoT Core into DSQL through an ingestion Lambda. On the visualization side, Grafana Cloud queries DSQL with the PostgreSQL datasource, using a dedicated read-only user. In the middle, a small Lambda keeps the datasource credentials fresh: every hour, EventBridge invokes it, the Lambda generates a new DSQL authentication token and pushes it to Grafana as the new datasource password.
Aurora DSQL authentication: the password is a token
Aurora DSQL is a serverless, PostgreSQL-compatible database. It is designed to scale, in the words of AWS, virtually without limits. There are no instances to size, no storage to provision, and no CREATE USER alice WITH PASSWORD '...' either. Every connection is authenticated with IAM: you generate a signed token with the AWS SDK or CLI and pass it as the PostgreSQL password.
A token is valid for a limited duration that you choose at generation time. In my case I generate tokens valid for 2 hours:
|
|
This model is great for security: credentials are short-lived, tied to an IAM identity, and never stored anywhere. But it clashes with tools like Grafana that were built around long-lived passwords. If you paste a token into the Grafana datasource configuration by hand, your dashboards will stop working a couple of hours later with an authentication error.
The fix is simple: automate the paste.
The DSQL cluster in CDK
For completeness, this is all it takes to create the cluster. Note the deletion protection, this database holds years of sensor history and I do not want a cdk destroy to take it away:
|
|
Least privilege: a read-only user for Grafana
This part is important. DSQL is built to scale very, very hard, “to infinity” as the marketing goes. That is exactly what you want for ingestion, and exactly what you do not want from a dashboard tool with a badly written query, or worse, a leaked credential. A runaway workload does not hit a connection limit or a saturated instance, it just scales, and so does the bill.
To be fair, my current usage is free: DSQL has a monthly free tier (100,000 DPUs and 1 GB of storage) and my handful of sensors stays well below it. I would like to keep it that way, so Grafana gets its own PostgreSQL role with the strict minimum:
|
|
The grafana_ro role can read the sensor tables and nothing else: no INSERT, no UPDATE, no DDL. On the IAM side, the same principle applies. The token-refresh Lambda is only allowed to generate regular (non-admin) connection tokens for this specific cluster:
|
|
dsql:DbConnectAdmin is deliberately absent. Even if the Grafana API key and the datasource were both compromised, the attacker would end up connected as a role that can only run SELECT.
There is one DSQL-specific detail: to log in as grafana_ro, the IAM identity must also be associated with the PostgreSQL role inside the database (AWS IAM GRANT), which is a one-time setup step done with an admin token. See the DSQL documentation on database roles for the details.
The token-refresh Lambda
The Lambda does two things: generate a fresh token, then update the Grafana datasource with it. The Grafana API key is stored as a SecureString in SSM Parameter Store and read at cold start:
|
|
The Grafana side uses the datasource HTTP API. There is no PATCH endpoint for a single field, so the function first fetches the current datasource definition, then sends it back with the new password in secureJsonData:
|
|
Fields like jsonData (which holds the SSL mode, PostgreSQL version, and so on) must be sent back as-is, otherwise the PUT would reset them.
Scheduling with EventBridge, and why the retry matters
An EventBridge rule invokes the Lambda every hour. Since the token is valid for 2 hours, there is always a full hour of overlap: even if one refresh fails completely, the dashboards keep working until the next one.
|
|
The retry_attempts=1 is not decorative. On the Grafana Cloud free tier, an instance that receives no traffic for a while is put to sleep. When the Lambda then calls the Grafana API, the first request can fail (or time out) while the cluster wakes up. The EventBridge retry, which happens a bit later thanks to exponential backoff, lands on a Grafana instance that is awake again and succeeds. In practice this is enough, I have not needed a more elaborate retry strategy inside the Lambda itself.
The Grafana API key lives in SSM Parameter Store as a SecureString, and the Lambda is only granted read access to that one parameter:
|
|
Setting up the Grafana side
Two manual steps are needed in Grafana Cloud, both well covered by the official documentation:
- Create a service account with a token. The service account only needs the “Data sources / Writer” permission, again least privilege. Follow the service accounts documentation, then store the generated token in SSM:
|
|
-
Create a PostgreSQL datasource pointing to the DSQL cluster endpoint, following the PostgreSQL datasource documentation. The settings that matter for DSQL:
- Host:
your-cluster-id.dsql.eu-west-1.on.aws:5432 - Database:
postgres - User:
grafana_ro - TLS/SSL mode:
require - Password: anything, the Lambda will overwrite it within the hour
- Host:
Once the datasource is saved, note its UID (visible in the URL of the datasource page) and pass it to the Lambda through the GRAFANA_DATASOURCE_UID environment variable.
Conclusion
Aurora DSQL and Grafana Cloud free tier make a nice combination for a home IoT project: the database costs nothing under the free tier (100,000 DPUs and 1 GB of storage per month), scales if the project grows, and Grafana provides the dashboards without hosting anything myself.
The one impedance mismatch is authentication: DSQL only speaks short-lived IAM tokens, Grafana only stores static passwords. A 40-line Lambda invoked hourly by EventBridge bridges the two, with a retry to absorb the free tier cluster waking up from sleep. And because a database that can scale “to infinity” can also bill accordingly, everything runs under least privilege: a SELECT-only PostgreSQL role for Grafana, an IAM role that can only generate non-admin tokens for one cluster, and a service account token scoped to datasource updates.
Comments