-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfailopen_lost_bornmarker_test.go
More file actions
226 lines (209 loc) · 10.2 KB
/
Copy pathfailopen_lost_bornmarker_test.go
File metadata and controls
226 lines (209 loc) · 10.2 KB
1
2
3
4
5
6
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package main
import (
"context"
"go/ast"
"go/parser"
"go/token"
"os"
"reflect"
"testing"
"github.com/pingular/pingularity/internal/config"
"github.com/pingular/pingularity/internal/settings"
"github.com/pingular/pingularity/internal/store"
)
// A birth marker records something only the daemon that created the database
// can know, so the daemon that watched it happen may still complete a stamp a
// store fault lost (see internal/settings) - but only until it exits. A write
// that fails at birth and is not completed before the process restarts leaves
// the install markerless FOREVER: from that boot on the store is established,
// no later controller witnessed anything, and an established store must never be
// stamped (a marker added later claims a birth that cannot be proven). On disk
// that install becomes byte-identical to a genuinely 0.61-or-earlier one, which
// is the shape these tests are about.
//
// This is the test that matters: that shape must still fail CLOSED. Marker
// absence advises and nothing more - warnAmbiguousContainerAccess writes
// nothing, and TestOnlyExplicitInputPersistsAccess pins that no other path in
// main may persist an access decision - so the worst a lost stamp can do is
// cost a recurring warning, never network-reachable access. If a future change
// ever lets marker absence DECIDE something, a dropped write at birth becomes
// that decision taken wrongly and permanently; this test is the tripwire.
// denyWritesTo / allowWritesTo make a REAL store refuse writes while reads keep
// working - the exact shape of the fault (settings load fine, only the stamp
// cannot land). The :memory: pool is one connection, so the per-connection
// pragma sticks across calls.
func denyWritesTo(t *testing.T, st *store.Store) {
t.Helper()
if _, err := st.DB().ExecContext(context.Background(), "PRAGMA query_only = ON"); err != nil {
t.Fatalf("query_only on: %v", err)
}
}
func allowWritesTo(t *testing.T, st *store.Store) {
t.Helper()
if _, err := st.DB().ExecContext(context.Background(), "PRAGMA query_only = OFF"); err != nil {
t.Fatalf("query_only off: %v", err)
}
}
// quietStderr swallows what fn writes to os.Stderr (settings warns there about
// the lost stamp, before any logger exists) so the failure being modelled does
// not look like test output.
func quietStderr(t *testing.T, fn func()) {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "stderr")
if err != nil {
t.Fatalf("temp stderr: %v", err)
}
defer f.Close()
orig := os.Stderr
os.Stderr = f
defer func() { os.Stderr = orig }()
fn()
}
// newLostMarkerContainerStore builds the store a container is left with when its
// birth stamp was LOST to a write error: born under a build that fails closed
// from the first boot (so it was never network-reachable), yet carrying no
// marker, and then established by an ordinary life of configuration and
// history. Every step is real - the write genuinely fails, the marker is
// genuinely absent - so this reproduces the shape rather than asserting it.
//
// What makes the loss PERMANENT is the restart. The controller that watched the
// database come into existence may still complete its own stamp (a later reload
// or settings write finishes it - see the settings package), so this fixture
// drops it while the store is still markerless: from here on, every controller
// is one that never saw the store fresh, and those must refuse forever. That is
// also why the store's later life is written directly rather than through the
// witnessing controller - the daemon that witnessed the birth is gone.
//
// cfg is the boot being modelled, and it seeds the controller exactly as main
// does (establishedContainerController -> testDefaultsFor). Seeding local-only
// here while handing an explicit network scope to reconcileAccess would invent a
// disagreement production never has.
func newLostMarkerContainerStore(t *testing.T, cfg config.Config) (*store.Store, *settings.Controller) {
t.Helper()
ctx := context.Background()
st := openStore(t)
// Birth, with the database refusing writes.
denyWritesTo(t, st)
var born *settings.Controller
var err error
quietStderr(t, func() {
born, err = settings.New(ctx, st, testDefaultsFor(cfg), settings.WithBornVersion("0.62.0-test"))
})
if err != nil {
t.Fatalf("settings.New at birth = %v; a failed stamp must not fail the load", err)
}
if born.BornMarkerErr() == nil {
t.Fatal("fixture precondition: the birth stamp must have failed and been recorded")
}
if _, marked := settingsSnapshot(t, st)[settings.KeyInstallBornVersion]; marked {
t.Fatal("fixture precondition: the store must be markerless")
}
allowWritesTo(t, st) // the hiccup passes; everything else works from here on
// The restart, on a later release: `born` is dropped here without ever
// completing its stamp, and the controller for the boot under test is built
// the one sanctioned way - over a store the ordinary life of configuration
// and history has since made ESTABLISHED.
set := establishedContainerController(t, st, cfg)
if set.BornMarkerErr() != nil {
t.Fatalf("BornMarkerErr = %v on a boot that had nothing to stamp", set.BornMarkerErr())
}
return st, set
}
// THE PROOF. An install whose birth marker was lost to a write error, running in
// a container with no explicit -access/PINGULARITY_ACCESS, must end its boot
// LOCAL-ONLY with nothing written. The marker's absence is a missing signal, not
// permission to guess: guessing open here would put an unauthenticated dashboard
// on the LAN of an install that was born private and never answered the network.
func TestLostBirthMarkerContainerBootStaysLocalOnlyAndWritesNothing(t *testing.T) {
ctx := context.Background()
st, set := newLostMarkerContainerStore(t, config.Config{}) // no explicit access input
before := settingsSnapshot(t, st)
warns := bootAccessDecision(t, config.Config{}, st, set, true) // no explicit access input
if !set.AccessLocalOnly() {
t.Fatal("a LOST birth-marker write opened the dashboard to the network; marker absence must decide nothing")
}
if storedAccessKey(t, st) {
t.Fatal("an access choice was persisted from a missing marker; only explicit operator input may write access")
}
if after := settingsSnapshot(t, st); !reflect.DeepEqual(before, after) {
t.Fatalf("the boot wrote to settings: before=%v after=%v (this path must write nothing at all)", before, after)
}
// Not just in memory: the next boot reads the same fail-closed state.
if err := set.Reload(ctx); err != nil {
t.Fatalf("reload: %v", err)
}
if !set.AccessLocalOnly() {
t.Fatal("access did not survive a reload as local-only")
}
if _, marked := settingsSnapshot(t, st)[settings.KeyInstallBornVersion]; marked {
t.Fatal("the reload stamped the marker on an established store")
}
// The cost of the lost stamp, stated: this install gets the ambiguity
// warning on every boot forever, and the operator's way out is the explicit
// flag. That is the whole damage - a support signal, not an exposure.
if len(warns) != 1 || warns[0].msg != accessAmbiguousWarnMsg {
t.Fatalf("got %d warnings (%v), want exactly the ambiguity warning: a permanently markerless install must at least be explained", len(warns), warns)
}
}
// The surfaced error must also be CONSUMED, or it is dropped again one layer up.
// Settings records WHY the stamp was lost (and warns to stderr, before any
// logger exists); main is what turns it into a log line an operator can still
// find months later, when the only remaining symptom is an ambiguity warning on
// every boot. Source-level, because run()'s boot sequence is not callable from a
// test - the same reason TestOnlyExplicitInputPersistsAccess reads main.go.
func TestBootReportsALostBirthMarker(t *testing.T) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "main.go", nil, 0)
if err != nil {
t.Fatalf("parse main.go: %v", err)
}
found := false
ast.Inspect(file, func(n ast.Node) bool {
if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == "BornMarkerErr" {
found = true
}
return !found
})
if !found {
t.Error("main.go never reads BornMarkerErr: a birth marker lost to a write error is permanent, and nothing would record why this install is treated as pre-marker for the rest of its life")
}
}
// The way out is the documented one, unchanged by how the store lost its marker:
// an explicit PINGULARITY_ACCESS=network is authoritative at THIS boot, so the
// install is one env var away from being reachable again - and once access is
// settled, the ambiguity warning stops.
//
// What it does NOT do is write an access row, and the fixture is what makes that
// visible: main seeds the controller from the same config the boot carries (def
// := defaultSettings(p.cfg)), so with no stored choice the network scope is
// already the effective value and reconcileAccess finds nothing to reconcile.
// Persisting is the Access tab's Save - which is why the docs tell operators to
// keep the variable rather than assume one run made it stick. An older version
// of this test seeded local-only defaults while passing network here, and
// "proved" a persistence production never performs.
func TestLostBirthMarkerInstallStillRecoversWithExplicitAccess(t *testing.T) {
ctx := context.Background()
cfg := config.Config{Access: "network", AccessExplicit: true}
st, set := newLostMarkerContainerStore(t, cfg)
warns := bootAccessDecision(t, cfg, st, set, true)
if set.AccessLocalOnly() {
t.Fatal("explicit PINGULARITY_ACCESS=network did not open access; that is the documented way out of the lockout")
}
if storedAccessKey(t, st) {
t.Fatal("the boot wrote an access row: with the scope already seeded from the same config there is nothing to reconcile, and claiming otherwise models a state production never reaches")
}
if err := set.Reload(ctx); err != nil {
t.Fatalf("reload: %v", err)
}
// The reload re-overlays the same defaults over a store that still has no
// access row, so the variable still governs - and still nothing is stored.
if set.AccessLocalOnly() {
t.Fatal("the explicit scope did not survive a reload of the same boot's config")
}
if storedAccessKey(t, st) {
t.Fatal("the reload persisted an access choice")
}
if len(warns) != 0 {
t.Fatalf("got %d warnings, want 0: the operator settled access, so there is no ambiguity left to report", len(warns))
}
}