Skip to main content

How It Works

The R2 connector is a self-contained integration that runs entirely inside your Cloudflare account. ROOTKey publishes a Terraform module that you deploy once per bucket. The module wires a Cloudflare Worker to a Cloudflare Queue that receives R2 event notifications whenever a new object is created. When a new file is uploaded to the monitored bucket (and matches your filtering rules, if configured), the Worker reads the object directly from R2 via an in-cluster binding (zero egress) and streams it to the ROOTKey API authenticated with your Connector API Key. ROOTKey stores the file and anchors it on-chain, enabling both integrity verification and full file recovery in the event of corruption, ransomware, or accidental deletion.
ROOTKey’s cyber resilience guarantee includes full recovery — not just detection. For that reason the connector uploads the full file content, not only a hash. Anchoring a hash alone cannot restore a corrupted, encrypted, or deleted file.
Egress to ROOTKey is free when this connector runs on Cloudflare. The R2 → Worker read is in-cluster (no internet egress), and Cloudflare does not bill egress on Worker fetch responses. Compared to the equivalent S3 connector on AWS, large-volume customers see materially lower recurring cost.

What This Module Creates in Your Cloudflare Account

Full transparency on what lands in your account when you terraform apply: The module does not create the R2 bucket itself — you create that. It also does not modify any existing R2 event notifications on the bucket; the new notification config is additive (multiple notifications can coexist on the same bucket). For a typical bucket with a few thousand uploads per month, the total recurring cost added to your Cloudflare bill is well under one dollar per month, dominated by Queue operations.

Infrastructure Impact Summary

No bucket-level data or settings are mutated. The module adds an event notification binding on the bucket — additive, doesn’t replace anything. Any existing event notifications you have on the bucket are untouched.
No. The module creates one Worker, two Queues, and one event-notification binding, all uniquely named with a name_suffix you provide plus a hash of the bucket name. Pre-existing Workers, Queues, R2 buckets, and Pages projects in the same account are not touched.
Yes. The whole purpose of the connector is to forward file content to ROOTKey so it can be anchored and recovered. Transport is HTTPS-only (the module rejects non-https:// API URLs at plan time). Files are streamed from R2 to the ROOTKey API; the Worker never persists them anywhere else. Cloudflare bills $0 egress for this path.
The Connector API Key you paste into Terraform is stored as a Cloudflare Workers Secret in your own account, encrypted at rest. After creation it cannot be read back through the dashboard or API — only rotated via terraform apply with a new value. The Worker reads it from env.ROOTKEY_API_KEY at runtime.
The Worker calls message.retry() and Cloudflare Queues automatically retries the message with exponential backoff up to 5 times. After exhaustion the message is moved to the dead-letter queue for human inspection. Permanent failures (oversize files, 4xx from ROOTKey) bypass the DLQ deliberately and surface via a stable log marker rootkey.event.dlq_terminal_failure for alerting.
Yes. Running terraform destroy removes every resource the module created (Worker, secret, both queues, queue consumer, event notification). The R2 bucket itself is not deleted — it is your resource, not the module’s.

Prerequisites

Before starting, ensure you have:
  • A Cloudflare account with R2 enabled.
  • A pre-existing R2 bucket to monitor. The module does not create the bucket.
  • A Cloudflare API token with permissions to manage Workers, Queues, R2 event notifications, and Workers Secrets in the target account. Generate one at dash.cloudflare.com → My Profile → API Tokens.
  • Your Cloudflare Account ID (32-char hex, visible in the dashboard sidebar).
  • Terraform v1.3 or later.
  • Node.js 22+ on the machine running Terraform (used to compile the Worker at terraform apply time).
No additional Cloudflare-side configuration is needed. Unlike the AWS/Azure connectors, the Worker’s scoping is implicit in the Terraform-declared bindings — there is no separate IAM role or App Registration to create.

API Token Permissions

The Cloudflare API token used to run terraform apply needs the following permissions on the target account: For minimal-privilege deployments, scope the token to the specific account (not “All Accounts”).

Configuration Fields


Setup

The setup has a natural ordering dependency: the dashboard requires the Cloudflare Account ID and bucket name to create the connector, and the Worker requires the Connector API Key to call the ROOTKey API. The dashboard resolves this by generating a ready-to-run Terraform block with all values pre-filled.
1

Create the R2 bucket (if you don't have one)

In the Cloudflare dashboard → R2 → Create bucket, or via Wrangler:
Note the bucket name.
2

Generate a Cloudflare API token

Go to dash.cloudflare.com → My Profile → API Tokens → Create Token and create a token with the permissions listed in API Token Permissions above.Copy the token — you’ll export it before running Terraform.
3

Find your Cloudflare Account ID

In the Cloudflare dashboard, the Account ID is visible in the right sidebar of any account-level page (32-char hex string).
4

Create the connector in the dashboard

Go to app.rootkey.aiConnectorsNew Connector → select Cloudflare R2.Fill in all required fields (see Configuration Fields above). Save the connector.
5

Copy the Connector API Key and the Terraform block

At the end of the wizard, the dashboard displays:
  1. The Connector API Key.
  2. A ready-to-run Terraform block, pre-filled with your values.
The Connector API Key is shown only once and is already embedded in the Terraform block. Copy both now and store them securely before closing this screen. The key cannot be retrieved again.
The generated block looks like:
6

Deploy the Terraform module

Save the block into a .tf file in an empty directory, export your Cloudflare API token, and apply:
The module bundles the Worker source, uploads it to Cloudflare, creates both queues, wires the consumer binding, and configures the R2 event notification.
7

Validate the connector

Upload a test object to the R2 bucket:
Within a few seconds the object should appear anchored in the destination vault, and the connector status in the dashboard should be ACTIVE.To tail Worker logs in real time:

Reliability and observability

The connector leans on Cloudflare Queues’ native retry + DLQ semantics — there’s no in-Worker retry layer to debug.

Retry behaviour

Idempotency

Every upload to the ROOTKey API carries three headers extracted from the R2 object: The ROOTKey API uses these to deduplicate redelivered events.

What to monitor

To peek at the DLQ contents:

Security considerations

The module ships with a defensive default posture out of the box:
  • API key in a Workers Secret, not in code. Encrypted at rest, not readable after creation. Rotation = terraform apply with a new value.
  • HTTPS-only. The module rejects non-https:// API URLs at plan time.
  • In-cluster R2 read. The Worker reads R2 objects via a binding — no HTTP egress, no public network exposure for the data path.
  • Bucket-scoped binding. The Worker’s R2 binding is scoped to one bucket. If the Worker is ever compromised, the blast radius is one bucket — not the customer’s whole R2 footprint.
  • No additional IAM model to manage. Unlike the AWS/Azure connectors, there is no role or App Registration to audit; the Worker can only use the bindings declared in Terraform.

Filtering Rules

To anchor only specific files (e.g., only PDFs, or exclude temporary files), configure Filtering Rules on the connector after creation. Rules apply on the ROOTKey side — files filtered out are not stored in the vault. You can also restrict at the R2 side by setting the Prefix field, which translates into the event-notification server-side filter: events for objects whose key does not start with the prefix never invoke the Worker.

Troubleshooting

Check in this order:
  1. Event notification is configured. Cloudflare dashboard → R2 → your bucket → Settings → Event notifications must show the rootkey-r2-events-* queue subscribed.
  2. DLQ depth. Cloudflare dashboard → Queues → your DLQ. Non-zero means transient errors exhausted retries.
  3. Worker logs. wrangler tail $(terraform output -raw worker_name) — look for terminal-failure markers.
  4. Filter prefix. If you set prefix, events for keys not matching the prefix never reach the Worker.
Common causes:
  • The Connector API Key was modified or deleted as a Workers Secret. Retrieve a new key by deleting and recreating the connector.
  • The destination vault was deactivated or deleted. Reactivate it or change the connector’s vault binding.
The dashboard error panel shows the underlying message from the ROOTKey API.
The Workers paid-plan practical ceiling for streaming uploads is around 500 MiB, which is the module default. Above this you may hit CPU/memory limits on the Worker.Options:
  • Stay under 500 MiB (recommended).
  • Move to Cloudflare Workers Unbound or the Standard pricing tier with more headroom and adjust max_file_size_bytes accordingly.
  1. In the ROOTKey dashboard, delete the connector and create a new one (the Cloudflare resources can be reused).
  2. Update the rootkey_api_key Terraform variable with the new key.
  3. Run terraform apply — the module overwrites the Workers Secret with the new value, and the Worker picks it up on the next invocation.
Yes — deploy the module once per bucket. Each instance is fully isolated: its own Worker, queues, secret, and event notification binding. Namespacing is automatic via name_suffix + a hash of the bucket name. Reuse the same Cloudflare account.
The DLQ holds messages that the queue trigger could not process after all 5 retry attempts. Pull a message from the DLQ via the Cloudflare API (see Reliability and observability), inspect it, and decide:
  • If the cause was transient (e.g., ROOTKey API was down): re-publish the message to the main queue.
  • If the cause was a permanent issue (key rotated incorrectly, vault deleted): resolve the root cause first, then replay.
Permanent failures (oversize, 4xx) should never reach the DLQ — they are short-circuited and visible only as rootkey.event.dlq_terminal_failure markers in Worker logs.

Source code

The Terraform module and Worker source live in the public ROOT-Key/rootkey-connectors repository under the r2/ directory. The code is licensed under the Apache License 2.0 — you are free to fork it, audit it, or pin to a specific commit if your change-management process requires it.
→ Back to Connectors Overview