Commit 59c9fd0
authored
v1.1.21 - Key Hashing & Storage Hardening (#334)
* feat(migrations): add versioned schema migration runner
* perf(requestlog): aggregate log stats in SQL and index created_at
Replace the paged List()-and-aggregate-in-Go path behind GET /admin/logs/stats
with a single SQL aggregation, so counts are exact instead of capped at 5,000
scanned rows.
- Add SQLWriter.Stats: one UNION ALL statement groups matching rows by stage,
provider, and model in a single round trip. COALESCE(NULLIF(col,''),'unknown')
folds NULL and '' into one group, matching the prior Go behavior for the
nullable provider/model columns. Totals derive from the NOT NULL stage rows.
- Add the idx_request_logs_created_at index, serving List's ordering/range,
Delete's range, and Stats' since filter.
- Add Stats to the requestlog.Reader interface.
- Drop the now-exact response's truncated/scan_limit/available_entries fields
and the scan-cap constant.
* feat(admin): store API keys hashed at rest
Persist sha256(key) plus a display form and look keys up by hash. The
full secret is returned only from create and rotate.
Existing databases are migrated in place through the new migration
runner. The migration rebuilds the table rather than dropping the
plaintext column: SQLite refuses to drop a UNIQUE column and retains
freed pages until VACUUM, and a Postgres DROP COLUMN leaves the values
in the heap. On SQLite the migration also vacuums and truncates the
write-ahead log, so the secrets do not survive in the database file.
Bootstrap credentials now fail closed. They are accepted only against a
key store confirmed empty via IsEmpty; previously a store that could not
be read reported zero keys and re-opened them during an outage.
The stored display form keeps both ends of the secret. This also fixes
the dashboard key table, which truncated an already-truncated value and
rendered every key as "fgw_...".
* fix(admin): restrict store files before migrating and serialize migrations
Restrict SQLite database files to owner-only access immediately after the
file exists, rather than after the schema is initialized. SQLite honors
the process umask when creating a file, so the key store was previously
world-readable while the migration read and rewrote every stored secret.
The config and request-log stores shared the ordering.
Hold a Postgres advisory lock for the duration of the migration run.
Several gateway instances sharing one database could otherwise each see
the same pending step, and every instance but the first would fail to
start against an already-migrated table.
Make the key rebuild's column additions re-runnable, and reject an empty
bearer value before it is hashed and looked up.
* fix(admin): create store files restricted and check the WAL checkpoint result
Create the SQLite database file with owner-only permissions before opening
it, rather than relaxing then narrowing it. Chmod-ing after creation left
a window in which another process could open the file and keep reading it
through the descriptor. Creating it first also gives SQLite's rollback
journal and write-ahead log the same restricted mode, since SQLite copies
the database file's permissions onto them.
Read the result of PRAGMA wal_checkpoint(TRUNCATE) instead of discarding
it. The pragma reports a busy database in a row rather than an error and
merges nothing, so the key migration could record itself complete with the
plaintext still in the write-ahead log.
Scope the Postgres column probe to the relation to_regclass resolves.
Filtering information_schema by name alone matches a same-named table in
any visible schema.
Reject an unsupported dialect instead of falling through to the SQLite
path, and refuse to run against a database a newer build has migrated.
Keep absent request-log dimensions encoding as {} rather than null.
* fix(admin): pin plugin storage paths and build the log index concurrently
Take each plugin's storage options (dsn, backend) from the running config
when a config is submitted over the admin API, and reject a submission
that changes one. A request-supplied request-logger dsn otherwise reaches
NewSQLiteWriter, which creates a database file — and restricts its
permissions — at any path the process can write. Storage location is
process configuration, not something an authenticated request may
redirect. Rollback re-resolves the same way.
Build the request_logs created_at index with CREATE INDEX CONCURRENTLY on
Postgres so an existing table's writers are not blocked for the length of
the build during a rolling restart, and drop and rebuild an index left
invalid by an interrupted concurrent build. SQLite stays inline.
Fix the integration key-list assertion for the display-form key, and add
Postgres migration coverage: in-place hashing with a rebuilt (not
altered) table, idempotence across restarts, concurrent startups
serializing on the advisory lock, and fresh-vs-migrated schema parity.
Document why hashKey uses SHA-256: the keys are full-entropy CSPRNG
tokens, so a password KDF adds per-request cost without defending
anything.
* docs: note admin storage-path guard and concurrent index in changelog
* fix(requestlog): don't drop an index another instance may be building
The invalid-index cleanup checked the index by bare name and dropped it
whenever it was marked invalid. A CREATE INDEX CONCURRENTLY that is still
running is visible as an invalid catalog row, so a second instance
starting up could drop an index the first was actively building, and the
bare-name probe could match a same-named index in another schema.
Resolve the index through to_regclass so the probe honors search_path,
and stop auto-dropping: an interrupted build is logged with a REINDEX
hint for an operator rather than healed by a cross-instance drop that
cannot tell an abandoned index from a live build. This also removes the
DROP INDEX string built by concatenation.
Also fix the Postgres migration test teardown to run on
context.Background(), since t.Context() is already canceled by the time
t.Cleanup runs; trim the hashKey godoc to the house style and keep the
rationale as an inline comment; assert the NoTx failure wraps its error
by identity; and add a rollback regression test proving a poisoned
history entry cannot redirect plugin storage.
* docs(requestlog): keep index comments neutral and accurate
Drop an internal marker from a code comment and correct the build-failure
log message: a failed concurrent index build leaves an invalid index that
a later start reports and points at REINDEX, rather than silently
rebuilding, so the message no longer implies automatic recovery.
* refactor(logger): record through the shared request-log store
The request-logger plugin opened its own SQLite/Postgres store from its
plugin config, while the gateway separately built a request-log store
from REQUEST_LOG_STORE_* for the admin log views. The two were never
connected, so unless an operator pointed both at the same path the admin
API read a different database than the plugin wrote — and a request body
submitted to POST /admin/config could set the plugin dsn, creating a file
at an arbitrary path on reload.
The gateway now holds the request-log store and injects it into logging
plugins as they load, mirroring SetObservability. The plugin records
through the shared store and never opens one from config, so no
request-supplied value reaches the filesystem. Its backend/dsn options
are obsolete and ignored with a warning; persistence is configured once
via REQUEST_LOG_STORE_BACKEND / REQUEST_LOG_STORE_DSN.
This deletes the resolveStorageOptions guard and its rollback
re-resolve — with no storage settings in the reloadable config, there is
nothing to pin. The plugin's Close is now a no-op: the store is owned by
the gateway and closed on shutdown, and closing it from the plugin would
break the admin log reader that shares it.1 parent 87fe4bd commit 59c9fd0
32 files changed
Lines changed: 2915 additions & 355 deletions
File tree
- internal
- admin
- bootstrap
- migrations
- plugins/logger
- requestlog
- sqlitefile
- test/integration
- http
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
8 | 40 | | |
9 | 41 | | |
10 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
184 | | - | |
185 | | - | |
186 | | - | |
| 184 | + | |
187 | 185 | | |
188 | 186 | | |
189 | 187 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
141 | | - | |
142 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
143 | 145 | | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | 146 | | |
149 | 147 | | |
150 | 148 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
168 | 185 | | |
169 | 186 | | |
170 | 187 | | |
| |||
282 | 299 | | |
283 | 300 | | |
284 | 301 | | |
285 | | - | |
| 302 | + | |
286 | 303 | | |
287 | 304 | | |
288 | 305 | | |
| |||
327 | 344 | | |
328 | 345 | | |
329 | 346 | | |
330 | | - | |
| 347 | + | |
331 | 348 | | |
332 | 349 | | |
333 | 350 | | |
| |||
342 | 359 | | |
343 | 360 | | |
344 | 361 | | |
345 | | - | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
346 | 367 | | |
347 | 368 | | |
348 | 369 | | |
| |||
354 | 375 | | |
355 | 376 | | |
356 | 377 | | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
357 | 387 | | |
358 | 388 | | |
359 | 389 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
2889 | 2890 | | |
2890 | 2891 | | |
2891 | 2892 | | |
| 2893 | + | |
| 2894 | + | |
| 2895 | + | |
| 2896 | + | |
| 2897 | + | |
| 2898 | + | |
| 2899 | + | |
| 2900 | + | |
| 2901 | + | |
| 2902 | + | |
| 2903 | + | |
| 2904 | + | |
| 2905 | + | |
| 2906 | + | |
| 2907 | + | |
| 2908 | + | |
| 2909 | + | |
| 2910 | + | |
| 2911 | + | |
| 2912 | + | |
| 2913 | + | |
| 2914 | + | |
| 2915 | + | |
| 2916 | + | |
| 2917 | + | |
| 2918 | + | |
| 2919 | + | |
| 2920 | + | |
| 2921 | + | |
| 2922 | + | |
| 2923 | + | |
| 2924 | + | |
| 2925 | + | |
| 2926 | + | |
| 2927 | + | |
| 2928 | + | |
| 2929 | + | |
| 2930 | + | |
| 2931 | + | |
| 2932 | + | |
| 2933 | + | |
| 2934 | + | |
| 2935 | + | |
| 2936 | + | |
| 2937 | + | |
| 2938 | + | |
| 2939 | + | |
| 2940 | + | |
| 2941 | + | |
| 2942 | + | |
| 2943 | + | |
| 2944 | + | |
| 2945 | + | |
| 2946 | + | |
| 2947 | + | |
| 2948 | + | |
| 2949 | + | |
| 2950 | + | |
| 2951 | + | |
| 2952 | + | |
| 2953 | + | |
| 2954 | + | |
| 2955 | + | |
| 2956 | + | |
| 2957 | + | |
| 2958 | + | |
| 2959 | + | |
| 2960 | + | |
| 2961 | + | |
| 2962 | + | |
| 2963 | + | |
| 2964 | + | |
| 2965 | + | |
| 2966 | + | |
| 2967 | + | |
| 2968 | + | |
| 2969 | + | |
| 2970 | + | |
| 2971 | + | |
| 2972 | + | |
| 2973 | + | |
| 2974 | + | |
| 2975 | + | |
| 2976 | + | |
2892 | 2977 | | |
2893 | 2978 | | |
2894 | 2979 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | 81 | | |
85 | | - | |
| 82 | + | |
86 | 83 | | |
87 | 84 | | |
88 | 85 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
124 | | - | |
125 | | - | |
126 | | - | |
| 124 | + | |
127 | 125 | | |
128 | 126 | | |
129 | 127 | | |
130 | 128 | | |
131 | 129 | | |
132 | 130 | | |
133 | | - | |
| 131 | + | |
134 | 132 | | |
135 | 133 | | |
136 | 134 | | |
137 | 135 | | |
138 | 136 | | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
176 | | - | |
177 | | - | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | | - | |
182 | | - | |
183 | | - | |
184 | | - | |
185 | | - | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | | - | |
193 | | - | |
194 | | - | |
195 | 137 | | |
196 | 138 | | |
197 | 139 | | |
198 | | - | |
199 | | - | |
200 | | - | |
201 | | - | |
202 | | - | |
203 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
204 | 143 | | |
205 | | - | |
206 | | - | |
207 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
208 | 147 | | |
209 | 148 | | |
210 | | - | |
211 | | - | |
212 | | - | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
213 | 152 | | |
214 | 153 | | |
215 | 154 | | |
216 | 155 | | |
217 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
218 | 166 | | |
219 | 167 | | |
220 | 168 | | |
| |||
0 commit comments