Apache Cassandra / ScyllaDB Integration Guide
This guide covers how to connect a Cassandra or ScyllaDB cluster to BurnLedger, configure query templates, and set up read-only credentials. The connector is implemented and registered as connector type cassandra.
Connection Configuration
BurnLedger connects to Cassandra using the CQL native protocol.
Configuration fields:
| Field | Required | Description |
|---|---|---|
hosts |
Yes | Array of contact point addresses (e.g., ["cass1.example.com", "cass2.example.com"]). |
port |
No | CQL port (default: 9042). |
keyspace |
Yes | The keyspace containing the target table. |
username |
No | CQL username for authentication. |
password |
No | CQL password. |
datacenter |
No | Local datacenter for DCAwareRoundRobinPolicy. |
tls |
No | Enable TLS (default: true). Minimum TLS 1.2. |
read_only |
Conditional | Your assertion that the credential is read-only. Required when permissions cannot be listed — no username supplied, or the cluster uses AllowAllAuthorizer. Ignored when LIST ALL PERMISSIONS succeeds. See Read-Only User Setup. |
These are the exact JSON keys the connector reads. read_only is snake_case in both SDKs — do not write readOnly.
TypeScript
const system = await bl.registerSystem({
name: "user-events-cass",
connectorType: "cassandra",
connectionConfig: {
hosts: ["cass1.example.com", "cass2.example.com"],
keyspace: "myapp",
username: "burnledger_ro",
password: process.env.CASS_PASSWORD,
datacenter: "us-east-1",
},
subjectQuery: "SELECT * FROM user_events WHERE user_id = ?",
});
Python
system = bl.register_system(
name="user-events-cass",
connector_type="cassandra",
connection_config={
"hosts": ["cass1.example.com", "cass2.example.com"],
"keyspace": "myapp",
"username": "burnledger_ro",
"password": os.environ["CASS_PASSWORD"],
"datacenter": "us-east-1",
},
subject_query="SELECT * FROM user_events WHERE user_id = ?",
)
Query Template Format
CQL query templates use ? as the parameter placeholder (standard CQL prepared statement syntax).
Rules:
- Must contain exactly one
?placeholder — the subject identifier is bound to it as the single query parameter. A template with a different count of?is rejected by the CQL driver at query time (surfaced asCONNECTION_FAILED). - Should begin with
SELECT, and the?should sit in theWHEREclause on the partition key (Cassandra needs the partition key for an efficient, non-scanning read). - Do not use
ALLOW FILTERING— a full table scan will blow pastmax_recordsand cost you cluster time.
Not validated at registration. Unlike the SQL and JSON connectors, Cassandra templates are not checked when the system is registered: BurnLedger does not verify that the template starts with
SELECT, targets the partition key, or omitsALLOW FILTERING. Cassandra itself enforces the partition-key andALLOW FILTERINGrules at execution time, and a bad template surfaces as a query error rather than a registration error. Review your template carefully.
Examples:
-- Query by partition key
SELECT * FROM user_events WHERE user_id = ?
-- Specific columns
SELECT user_id, event_type, event_data, created_at FROM user_events WHERE user_id = ?
-- Compound partition key (first element)
SELECT * FROM user_data WHERE tenant_id = 'default' AND user_id = ?
Read-Only User Setup
BurnLedger detects write access during connection validation and rejects roles that have it — without ever writing. It runs LIST ALL PERMISSIONS OF <role> and allows only SELECT and DESCRIBE. Any other permission — MODIFY, CREATE, ALTER, DROP, AUTHORIZE, EXECUTE, or anything unrecognized — is treated as write-capable (default-deny) and construction is refused.
This works automatically when the cluster uses a permission-enforcing authorizer (e.g. CassandraAuthorizer). If no username is configured, or the cluster uses AllowAllAuthorizer (no permission system, so permissions cannot be listed), set "read_only": true in the connector config to assert read-only; otherwise construction is refused (fails closed).
Cassandra (native auth)
-- Create a role with login and no superuser
CREATE ROLE burnledger_ro WITH PASSWORD = 'your-secure-password' AND LOGIN = true AND SUPERUSER = false;
-- Grant SELECT on specific tables
GRANT SELECT ON myapp.user_events TO burnledger_ro;
GRANT SELECT ON myapp.user_profiles TO burnledger_ro;
-- Or grant SELECT on all tables in the keyspace
GRANT SELECT ON ALL TABLES IN KEYSPACE myapp TO burnledger_ro;
ScyllaDB
Same CQL syntax as Cassandra. ScyllaDB uses the same auth model.
Amazon Keyspaces (Managed Cassandra)
Use service-specific credentials from the IAM console. Attach a policy with cassandra:Select only:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["cassandra:Select"],
"Resource": "arn:aws:cassandra:us-east-1:123456789012:/keyspace/myapp/table/user_events"
}
]
}
Verify
-- Connect as read-only role
cqlsh cass1.example.com -u burnledger_ro -p 'your-secure-password'
-- Should succeed
SELECT * FROM myapp.user_events WHERE user_id = 'test-user' LIMIT 1;
-- Should fail with "Unauthorized"
INSERT INTO myapp.user_events (user_id, event_type) VALUES ('test', 'test');
DELETE FROM myapp.user_events WHERE user_id = 'test-user';
Hash Scope
hash_scope is accepted on the system record for every connector, but the Cassandra connector does not read it — it only changes behavior for the object/document-store connectors (S3, GCS, Azure Blob, MarkLogic). Setting hashScope: "existence" on a Cassandra system does not switch to a primary-key-only hash; it has no effect at all.
What actually varies is the proof mode of the attestation:
| Proof mode | Cassandra behavior |
|---|---|
| Count (default) | Executes the template and counts matching rows. No row content is hashed. |
| Merkle (opt-in) | Executes the template and hashes every column of each row, columns sorted by name for deterministic hashing. |
Cassandra-Specific Considerations
Partition key requirement
Cassandra queries should include the full partition key in the WHERE clause. This is enforced by Cassandra, not by BurnLedger: a template that omits it fails at query time (or demands ALLOW FILTERING) and surfaces as CONNECTION_FAILED. BurnLedger performs no partition-key analysis of the template.
TTL and tombstones
If your data uses TTLs, records may disappear between attestation and verification without explicit deletion. This is expected behavior — BurnLedger proves absence regardless of how the data was removed.
Consistency level
BurnLedger uses LOCAL_QUORUM consistency for reads to balance accuracy and availability. This means a majority of the replicas in the local datacenter must respond — not all of them. Set datacenter in the config so the driver routes to the intended local DC.
That level is now disclosed rather than left in the source: POST /v1/systems/test-connection reports replication.role: no_primary — every Cassandra node is a peer, so there is no primary to be behind — with the configured level in replication.detail. A write acknowledged at a quorum of the same datacenter cannot be missed by a LOCAL_QUORUM read; a keyspace replicated across datacenters can still answer from a local quorum that has not received a remote write. See endpoint role.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
CONNECTION_FAILED |
Cannot reach any contact point | Verify hosts and port, check firewall rules. |
CONNECTION_FAILED with "Unauthorized" |
Invalid credentials | Check username and password. |
WRITE_ACCESS_DETECTED |
Role holds a permission outside SELECT/DESCRIBE (e.g. MODIFY) |
Revoke it: REVOKE MODIFY ON myapp.user_events FROM burnledger_ro; |
CONNECTION_FAILED with "cannot verify the credential is read-only" |
No username configured, or the cluster uses AllowAllAuthorizer, so LIST ALL PERMISSIONS cannot run |
Enable CassandraAuthorizer and connect as a named role, or set "read_only": true in the connector config. |
CONNECTION_FAILED with "ALLOW FILTERING" |
Cassandra rejected the query as a full table scan | Rewrite the template to target the partition key. BurnLedger does not catch this at registration. |