How do I keep Postgres credentials safe when an AI SRE looks into crash loops?
Tares keeps the Postgres connection URL on the source as a secret, and the source turns table rows into events. The dsn is redacted in the API and omitted from catalog exports. For APIs that need a login, the JSON API connector stores one credential as a secret in the same way.
How it works
The credential lives in the source configuration, not in the events.
- Postgres source. The
postgresconnector takes a connection URL indsn, in the formpostgresql://user:pass@host:port/dbname. Tares stores it as a secret. It is redacted in the API and omitted from catalog exports. - One event per row. The connector polls your table incrementally on a cursor column, such as an autoincrement
idorupdated_at, and stores one event per new or changed row. The timeline holds those rows as events. - You choose the columns. The
columnsfield is a comma-separated list. Empty means every column, and the cursor, key and time columns are always included. List only the columns an investigation needs. - JSON API source. The
http_pollconnector calls an endpoint on a schedule.credentialholds the whole credential value and is stored as a secret. The API never returns it and Tares never exports it.credential_headernames the header it travels in,Authorizationby default. - Other headers. Headers that are not secret go in
headers, which is plain configuration. Keep secrets out of it and put them incredential.
The payload is stored as received, so labels you declare later can still reach every field of a row or reply. That is a reason to trim columns up front. For the wider picture of how Tares investigates incidents, see AI SRE.
Set it up
- Make sure the Postgres is reachable from the Tares host. The driver ships with Tares, so you install nothing extra.
- Open Sources → Add source → Postgres and enter the Connection URL (
dsn). Press Discover. Tares lists the tables it can see. Pick the table that holds the data you want in the timeline. - Accept the proposed cursor and entity key, then choose which columns to pull. Narrow the list instead of keeping the default of all columns.
- Save. You can also
POST /api/sourceswith the same object as this example:
- name: orders
connector: postgres
poll: 10s
config:
dsn: postgresql://user:pass@host:5432/appdb
table: orders
cursor_column: updated_at
columns: id,tenant_id,status,amount,updated_at # empty = every column
labels:
- { name: tenant_id, field: tenant_id, primary: true }
- { name: status, field: status }- For an API that needs a login, add a source under Sources → Add source → HTTP API (poll). Enter the header name and value in the credentials field of the form. The value is stored as a secret. In YAML it looks like this:
config:
url: https://api.example.com/v1/readings
credential: abc123 # an API key
credential_header: X-API-Key- Verify. On the Postgres source, Recent events shows rows on the next poll, and Fields reports each column’s coverage. On the JSON API source, Recent events shows each poll’s events, and Fields lists every field seen.
The Postgres connector page lists every field, including limit for rows per poll and time_column for the event time. The HTTP API connector page covers payload_key, id_field and the rate-limit behavior.
What you get
- The Postgres connection URL sits in one place, the source configuration, stored as a secret. The API shows it redacted and catalog exports leave it out.
- The HTTP API credential is stored as a secret as well. The API never returns it and Tares never exports it.
- Your table data reaches the timeline as events, one per new or changed row, limited to the columns you picked.
- Discovery builds the source from the connection URL. It lists tables and proposes the cursor and labels, so you do not hand-write the setup.
- A polled API that rate-limits you backs off on a 429 or any 5xx, and any other 4xx shows as a config error on the source’s health. A bad credential therefore shows up on the source, not as silent gaps.
When something else fits
- If the system can call Tares instead of Tares calling it, the webhook connector takes JSON posted to an ingest URL, so there is no table to poll.
- If you want only a slice of a table, narrow
columnson the source rather than pulling everything. If the data is not in Postgres but behind an API, the HTTP API connector is the better fit.