vcon-server

Retention and legal holds

How long a deleted vCon is kept, when it is erased, how a legal hold stops that, and what the legal routes can read and restore.

The rule

A version is purged, erased for good, when all of these hold:

  • it is hidden: its uuid was deleted, or a newer version replaced it;
  • its uuid is not under a legal hold;
  • its tenant's retention period has passed since the version was stored.

The period counts from when the version was stored, not from when it was deleted. So deleting an old vCon erases it at once (unless it is held), and deleting a new one keeps it, hidden, until its period runs out. A live version is never purged, however old.

A purge removes the version's catalog row, with its wrapped data key, and its object file. The catalog is a SQLCipher database with secure_delete on, so the row's bytes are overwritten. When a deleted uuid's last version goes, a tombstone keeps the uuid, so it stays gone and cannot be stored again.

The period

Each tenant has its own period, or the server's retention_default (7y unless configured). A period is calendar years, months or days, such as 7y, 18m or 90d, added to the stored time in UTC. Set a tenant's with PATCH /admin/tenants/{id} ({"retention": "10y"}, or null for the server default). A change applies to every version the tenant keeps, at the next sweep.

When purges run

  • The sweep: at start, then purge_interval after each sweep ends (an hour unless configured), every tenant is swept. A tenant that cannot be swept is counted in the sweep's log line and tried again next time.
  • On delete: DELETE /vcons/{uuid} purges that uuid's versions that are already due, before it answers. With a 0d period a delete erases the vCon inside the request.

A legal hold is set on a uuid, live or deleted, by a key with the legal scope:

curl -X PUT    https://vcon.example.com/legal/vcons/$UUID/hold -H "Authorization: Bearer $LEGAL_KEY"   # 204
curl -X DELETE https://vcon.example.com/legal/vcons/$UUID/hold -H "Authorization: Bearer $LEGAL_KEY"   # 204

While it is held, none of its versions is purged, by the sweep or by a delete. A hold does not stop a delete: the vCon is hidden from ordinary reads, and kept. A tenant with a held uuid cannot be removed over HTTP, even with force. A uuid already purged answers gone (410).

With the legal scope:

RouteAnswers
GET /legal/vcons/{uuid}the current version, or for a deleted uuid the latest kept one, with Vcon-State: live or deleted
GET /legal/vcons/{uuid}/versionsevery kept version, oldest first: id, stored_at, hidden_at (RFC 3339, UTC) and whether it is current
GET /legal/vcons/{uuid}/versions/{version}that kept version, as stored
POST /legal/vcons/{uuid}/restoremakes a deleted uuid live again, with its latest kept version; 200 with that version's ETag

Restoring a uuid that is not deleted is not-deleted (409). Every legal request is recorded in the tenant's audit log, like every other request.

What erasure means on disk

A purge erases the version from the live system. Copies made before it (old disks, freed blocks, backups) still hold the sealed file and the wrapped key, readable with the tenant key and master key of that time. Key rotation closes that: after a rotation, the keys that could open those copies are destroyed.

On an S3 bucket

With the objects in an S3 bucket (see keep vCons in an S3 bucket), a purge deletes the version's row and wrapped data key from the catalog, then sends DeleteObject for its object. What happens to the object's bytes after that is up to the storage service: it may keep them on its disks, in its replicas or in its own backups for a time the server cannot see or change. The server refuses a bucket whose versioning is on or suspended unless storage.s3.allow_versioning is set, because such a bucket keeps the deleted object as a noncurrent version. Without the catalog row the object cannot be opened, since its data key was kept only in that row, so a copy the service keeps is in the same place as an old disk: key rotation makes it unreadable. An object that is already missing when the purge runs does not stop the purge.

Source of this page: website/content/docs/retention-and-holds.md