oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "encoding/base64"
5 "errors"
6 "fmt"
7 "os"
8 "strings"
9
10 "amadan.net/oficina/sheets/internal/sheets/seal"
11)
12
13// dekBytes is the DEK's required length. seal.New enforces it too; this
14// check exists so the refusal names SHEETS_DEK and the length an
15// operator has to produce, rather than surfacing a framework message
16// about a byte slice.
17const dekBytes = 32
18
19// LoadSealer reads the data encryption key from platform config and
20// builds the app's one Sealer.
21//
22// The DEK arrives as platform config (`carlos secrets`), never stored in
23// the database — spec §2.1. A missing one is a hard refusal rather than
24// a generated key: an instance that invents a DEK at boot seals this
25// run's cells with a key the next run will not have, which reads as data
26// loss and cannot be undone. Every other boot refusal in this app has
27// the same shape, and for the same reason: continuing would produce an
28// instance that answers /healthz perfectly well and then fails on the
29// first cell anybody types.
30//
31// No error here ever includes the value read from the environment. It is
32// a live key, and `carlos logs` is platform-held storage we cannot grep
33// or redact (AGENTS.md / Mechanical rules).
34func LoadSealer() (*seal.Sealer, error) {
35 raw := strings.TrimSpace(os.Getenv("SHEETS_DEK"))
36 if raw == "" {
37 return nil, errors.New(
38 "sealing is not configured: SHEETS_DEK not set. " +
39 "Generate one with `head -c 32 /dev/urandom | base64` and set it with " +
40 "`carlos secrets set --app sheets SHEETS_DEK=...`")
41 }
42 dek, err := base64.StdEncoding.DecodeString(raw)
43 if err != nil {
44 // The decode error is deliberately dropped rather than wrapped:
45 // base64.CorruptInputError renders as an offset, which is a
46 // (small) fact about the key's bytes. The variable name is the
47 // only thing an operator needs here.
48 return nil, errors.New("sealing is not configured: SHEETS_DEK is not valid base64")
49 }
50 if len(dek) != dekBytes {
51 return nil, fmt.Errorf(
52 "sealing is not configured: SHEETS_DEK decodes to %d bytes, want %d",
53 len(dek), dekBytes)
54 }
55 s, err := seal.New(dek)
56 if err != nil {
57 return nil, fmt.Errorf("sealing is not configured: %w", err)
58 }
59 return s, nil
60}
61