feat(secrets): tokens og kodeord forlader ikke længere huset med en kopi
Gmails refresh-token lå i klartekst i databasen. Det er ikke filen på serveren, der er problemet, men kopierne af den: sikkerhedskopier kan hentes ned gennem fladen, så hver eneste kopi, der er taget indtil nu, bar et levende token. Derfor ligger nøglen uden for databasen — i miljøet, eller i en fil ved siden af dataene med tilstand 0600. VAPID-parret er præcedens for en hemmelighed, instansen selv laver og gemmer i basen, men det mønster dur ikke her: en nøgle i samme fil som det, den låser, er ingen lås. Backup er VACUUM INTO af selve databasen, så filen følger ikke med. Kun de navngivne felter forsegles — refresh_token, access_token, password, client_secret. Resten af klatten er det, man læser, når en postkasse driller: hvilken mappe, hvilken etiket, hvilken adresse. At kryptere det med ville ikke købe noget og ville koste muligheden for at kigge. Ingen migration, og ingen der skal forbinde forfra: en værdi uden mærket læses som sig selv, og næste skrivning lægger den forseglet på plads. Kolonnen konverterer sig selv efterhånden som den bruges, i stedet for i én omgang, der skal have ret om hver eneste række på én gang. Der er en prøve for netop det. Fladen siger det på siden, hvor man henter kopien — for den dag nøglen betyder noget, er den dag, man gendanner et andet sted og undrer sig over, at postkassen er tavs. Runen er på v4; en ny variabel uden et versionsspring er usynlig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
aabbbac583
commit
a38a79e9b6
@@ -7,6 +7,7 @@ import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/kristianwind/verdande/internal/secret"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -23,8 +24,20 @@ var migrationFS embed.FS
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
path string
|
||||
|
||||
// box seals the values that must not travel with a backup. Nil in the tests
|
||||
// that do not care, where a secret is stored as itself — see Seal.
|
||||
box *secret.Box
|
||||
}
|
||||
|
||||
// UseSecrets gives the store the key it seals with. Called once at startup, before
|
||||
// anything reads or writes settings.
|
||||
//
|
||||
// Separate from Open because the key comes from the environment or from a file
|
||||
// beside the data, and a database that cannot be opened without it would make a
|
||||
// misplaced key look like a corrupt disk.
|
||||
func (db *DB) UseSecrets(box *secret.Box) { db.box = box }
|
||||
|
||||
// dsn is the connection string.
|
||||
//
|
||||
// SQLite is a single file, but it is being driven by an HTTP server with concurrent
|
||||
|
||||
Reference in New Issue
Block a user