oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1package operator
2
3import (
4 "bytes"
5 "context"
6 "database/sql"
7 "encoding/base64"
8 "errors"
9 "os"
10 "testing"
11
12 "amadan.net/oficina/home/internal/home/directorydata"
13 "amadan.net/oficina/home/roster"
14 family "amadan.net/rastrillo/rastrillo/crypto"
15 "github.com/go-json-experiment/json"
16)
17
18type directoryFixture struct {
19 engineFixture
20 directory *Directory
21 ownerKey *family.Keypair
22 attempt byte
23 bearer string
24}
25
26func newDirectoryFixture(t *testing.T) *directoryFixture {
27 t.Helper()
28 f := newEngineFixture(t)
29 p := f.engine.verifier.pins
30 p.Actions = []string{"directory.initialize", "directory.seed-team", "directory.provision-consumer", "directory.verify-consumer", "directory.activate-consumer"}
31 v, e := NewVerifier(p)
32 if e != nil {
33 t.Fatal(e)
34 }
35 f.engine.verifier = v
36 owner, e := family.Generate()
37 if e != nil {
38 t.Fatal(e)
39 }
40 signer, e := family.Generate()
41 if e != nil {
42 t.Fatal(e)
43 }
44 pageKey, e := family.Generate()
45 if e != nil {
46 t.Fatal(e)
47 }
48 ov, e := NewOwnerVerifier(base64.RawURLEncoding.EncodeToString(owner.SignPub()), "https://id.oficina.works", p.Origin)
49 if e != nil {
50 t.Fatal(e)
51 }
52 d, e := NewDirectory(f.engine, ov, signer, []roster.Key{{ID: "page1", Public: base64.RawURLEncoding.EncodeToString(pageKey.SignPub())}}, directorydata.Config{Apps: []string{"docs", "sheets"}, ValidateRoute: func(r directorydata.Route) bool { return r.Origin == "https://"+r.Label+"."+r.App+".oficina.works" }})
53 if e != nil {
54 t.Fatal(e)
55 }
56 return &directoryFixture{engineFixture: f, directory: d, ownerKey: owner, attempt: 20}
57}
58func (f *directoryFixture) input(t *testing.T, action string, build func(InputBase) any) []byte {
59 t.Helper()
60 a := f.auth
61 var sequence int64
62 var head string
63 e := f.db.QueryRow("SELECT sequence,head FROM directory_operator_state WHERE singleton=1").Scan(&sequence, &head)
64 if e != nil && !errors.Is(e, sql.ErrNoRows) {
65 t.Fatal(e)
66 }
67 a.Sequence = sequence + 1
68 a.Previous = head
69 a.Initial = a.Sequence == 1
70 a.JournalSequence = int64(f.attempt)
71 a.Action = action
72 a.OperationID = token(f.attempt)
73 f.attempt++
74 a.Issued = f.now.Unix()
75 a.Expires = a.Issued + 300
76 raw, e := SignRequest(f.key, a, build(InputBase{1, action, a.OperationID}))
77 if e != nil {
78 t.Fatal(e)
79 }
80 return raw
81}
82func (f *directoryFixture) initialize(t *testing.T) {
83 t.Helper()
84 raw := f.input(t, "directory.initialize", func(b InputBase) any { return InitializeInput{b, nil, token(10), "operator-attests-never-provisioned"} })
85 if _, e := f.directory.Execute(context.Background(), raw); e != nil {
86 t.Fatal(e)
87 }
88}
89func (f *directoryFixture) receipt(t *testing.T, id int64) []byte {
90 t.Helper()
91 sqid, e := TeamSQID(id)
92 if e != nil {
93 t.Fatal(e)
94 }
95 p := OwnerReceipt{1, "owner-seed", token(byte(id) + 100), f.directory.owner.issuer, f.directory.owner.directory, id, sqid, "pr_aaaaaaaaaaaaaaaaaaaaaaaaaa", "owner", f.now.Unix(), f.now.Unix() + 300}
96 raw, e := SignOwner(f.ownerKey, p)
97 if e != nil {
98 t.Fatal(e)
99 }
100 return raw
101}
102func (f *directoryFixture) seed(t *testing.T, id int64, label string) ([]byte, error) {
103 t.Helper()
104 sqid, _ := TeamSQID(id)
105 proof := f.receipt(t, id)
106 raw := f.input(t, "directory.seed-team", func(b InputBase) any {
107 return SeedInput{b, token(10), id, sqid, label, "PRIVATE-TEAM-NAME-CANARY", proof}
108 })
109 out, e := f.directory.Execute(context.Background(), raw)
110 if e != nil {
111 return raw, e
112 }
113 var result Result
114 if json.Unmarshal(out, &result) != nil || result.Epoch != 2 || result.TeamID != id {
115 t.Fatal("wrong seed result")
116 }
117 return raw, nil
118}
119func TestDirectoryInitializeOwnerAndPendingConsumerAreAtomic(t *testing.T) {
120 f := newDirectoryFixture(t)
121 f.initialize(t)
122 seedRaw, e := f.seed(t, 1, "alpha")
123 if e != nil {
124 t.Fatal(e)
125 }
126 if _, e = f.directory.Execute(context.Background(), seedRaw); e != nil {
127 t.Fatal("seed replay", e)
128 }
129 if countRows(t, f.db, "teams") != 1 || countRows(t, f.db, "memberships") != 1 || countRows(t, f.db, "directory_operator_owner_receipts") != 1 {
130 t.Fatal("duplicate owner/team")
131 }
132 raw := f.input(t, "directory.provision-consumer", func(b InputBase) any {
133 return ProvisionInput{b, token(10), "Uk", "docs", "alpha-docs", "https://alpha.docs.oficina.works", 0}
134 })
135 output, e := f.directory.Execute(context.Background(), raw)
136 if e != nil {
137 t.Fatal(e)
138 }
139 var result Result
140 if json.Unmarshal(output, &result) != nil || !CanonicalToken(result.Bearer) || len(result.Record) == 0 {
141 t.Fatal("missing private provision result")
142 }
143 repeated, e := f.directory.Execute(context.Background(), raw)
144 if e != nil || !bytes.Equal(output, repeated) {
145 t.Fatal("token changed on replay", e)
146 }
147 binding := roster.Bindings{Issuer: f.directory.owner.issuer, Directory: f.directory.owner.directory, Consumer: "alpha-docs", App: "docs", Team: "Uk", Audience: "https://alpha.docs.oficina.works"}
148 if _, e = roster.VerifyProvisioning(result.Record, []roster.Key{{ID: keyID(f.directory.signer.SignPub()), Public: base64.RawURLEncoding.EncodeToString(f.directory.signer.SignPub())}}, binding); e != nil {
149 t.Fatal(e)
150 }
151 var state string
152 var pointerSequence int64
153 var pd, rd []byte
154 if e = f.db.QueryRow("SELECT i.state,p.sequence,p.record_digest,r.record_digest FROM team_instances i JOIN directory_operator_consumer_records p ON p.consumer_id=i.consumer_id JOIN roster_operator_receipts r ON r.operation_id=p.operation_id").Scan(&state, &pointerSequence, &pd, &rd); e != nil || state != "pending" || pointerSequence != 1 || !bytes.Equal(pd, rd) {
155 t.Fatal("wrong current pointer", e)
156 }
157 src, e := directorydata.NewRosterSource(f.db, f.engine.sealer, f.directory.policy, f.directory.owner.directory)
158 if e != nil {
159 t.Fatal(e)
160 }
161 _, status, e := src.Read(context.Background(), directorydata.RosterRequest{Token: result.Bearer, Team: "Uk", Fetch: token(80)}, *f.now)
162 if status != 503 || e != directorydata.ErrUnavailable {
163 t.Fatal("pending standing leaked", status, e)
164 }
165 for _, suffix := range []string{"", "-wal"} {
166 disk, e := os.ReadFile(f.path + suffix)
167 if e != nil && !errors.Is(e, os.ErrNotExist) {
168 t.Fatal(e)
169 }
170 for _, secret := range []string{"PRIVATE-TEAM-NAME-CANARY", result.Bearer} {
171 if bytes.Contains(disk, []byte(secret)) {
172 t.Fatal("unsealed private field")
173 }
174 }
175 }
176}
177func TestDirectoryRefusesChangedGenerationProposalLabelAndOwnerProof(t *testing.T) {
178 f := newDirectoryFixture(t)
179 f.initialize(t)
180 if _, e := f.seed(t, 1, "alpha"); e != nil {
181 t.Fatal(e)
182 }
183 if _, e := f.seed(t, 1, "beta"); e != ErrRefused {
184 t.Fatal("old owner receipt reused", e)
185 }
186 if _, e := f.seed(t, 2, "alpha"); e != ErrRefused {
187 t.Fatal("suite label collision", e)
188 }
189 if _, e := f.seed(t, 3, "gamma"); e != ErrRefused {
190 t.Fatal("raced integer proposal", e)
191 }
192 if _, e := f.seed(t, 2, "alpha.beta"); e != ErrRefused {
193 t.Fatal("invalid label", e)
194 }
195 raw := f.input(t, "directory.provision-consumer", func(b InputBase) any {
196 return ProvisionInput{b, token(11), "Uk", "docs", "alpha-docs", "https://alpha.docs.oficina.works", 0}
197 })
198 if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused {
199 t.Fatal("generation mismatch", e)
200 }
201 raw = f.input(t, "directory.provision-consumer", func(b InputBase) any {
202 return ProvisionInput{b, token(10), "Uk", "docs", "alpha-docs", "https://other.docs.oficina.works", 0}
203 })
204 if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused {
205 t.Fatal("host mismatch", e)
206 }
207 if countRows(t, f.db, "teams") != 1 || countRows(t, f.db, "team_instances") != 0 {
208 t.Fatal("failed seed/provision mutated")
209 }
210}
211func TestDirectoryWillNotInitializeFromEmptyTablesWithoutSignedBaseline(t *testing.T) {
212 f := newDirectoryFixture(t)
213 raw := f.input(t, "directory.initialize", func(b InputBase) any { return InitializeInput{b, nil, token(10), ""} })
214 if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused {
215 t.Fatal("missing baseline", e)
216 }
217 if countRows(t, f.db, "roster_generation") != 0 || countRows(t, f.db, "directory_operator_state") != 0 {
218 t.Fatal("invented initial authority")
219 }
220 f.initialize(t)
221 raw = f.input(t, "directory.initialize", func(b InputBase) any { return InitializeInput{b, nil, token(11), "operator-attests-never-provisioned"} })
222 if _, e := f.directory.Execute(context.Background(), raw); e != ErrRefused {
223 t.Fatal("generation reset", e)
224 }
225}
226func TestEngineOperatorKeyPinCannotSilentlyRotate(t *testing.T) {
227 f := newEngineFixture(t)
228 if _, e := f.engine.Execute(context.Background(), f.raw, insertOne); e != nil {
229 t.Fatal(e)
230 }
231 old, _ := f.engine.verifier.Verify(f.raw)
232 k, e := family.Generate()
233 if e != nil {
234 t.Fatal(e)
235 }
236 p := f.engine.verifier.pins
237 p.PublicKey = base64.RawURLEncoding.EncodeToString(k.SignPub())
238 v, e := NewVerifier(p)
239 if e != nil {
240 t.Fatal(e)
241 }
242 f.engine.verifier = v
243 a := f.auth
244 a.Sequence = 2
245 a.Initial = false
246 a.Previous = old.Head()
247 a.OperationID = token(55)
248 a.JournalSequence = 2
249 raw, e := SignRequest(k, a, struct{}{})
250 if e != nil {
251 t.Fatal(e)
252 }
253 if _, e = f.engine.Execute(context.Background(), raw, func(context.Context, *sql.Conn, Request) ([]byte, error) {
254 t.Fatal("rotated key reached mutation")
255 return nil, nil
256 }); e != ErrRefused {
257 t.Fatal("key changed", e)
258 }
259}
260