MySQL · 1444 bytes Raw Blame History
1 -- SPDX-License-Identifier: AGPL-3.0-or-later
2 --
3 -- S41c runner JWT replay protection.
4 --
5 -- The runner heartbeat endpoint mints short-lived, per-job JWTs after a
6 -- registration-token-authenticated runner claims a workflow_jobs row. Job
7 -- endpoints require that JWT and consume its jti exactly once. INSERT ...
8 -- ON CONFLICT DO NOTHING against this table is the replay gate: one affected
9 -- row means "first use"; zero rows means "replay" and the API returns 401.
10 --
11 -- We keep the workflow references here for auditability and cleanup. jti is
12 -- the hot lookup path and is enforced by the PRIMARY KEY.
13
14 -- +goose Up
15
16 CREATE TABLE runner_jwt_used (
17 jti text PRIMARY KEY,
18 runner_id bigint NOT NULL REFERENCES workflow_runners(id) ON DELETE CASCADE,
19 job_id bigint NOT NULL REFERENCES workflow_jobs(id) ON DELETE CASCADE,
20 run_id bigint NOT NULL REFERENCES workflow_runs(id) ON DELETE CASCADE,
21 repo_id bigint NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
22 expires_at timestamptz NOT NULL,
23 used_at timestamptz NOT NULL DEFAULT now(),
24
25 CONSTRAINT runner_jwt_used_jti_length CHECK (char_length(jti) BETWEEN 16 AND 128)
26 );
27
28 CREATE INDEX runner_jwt_used_expires_idx
29 ON runner_jwt_used (expires_at);
30 CREATE INDEX runner_jwt_used_runner_used_idx
31 ON runner_jwt_used (runner_id, used_at DESC);
32
33
34 -- +goose Down
35 DROP TABLE IF EXISTS runner_jwt_used;
36