test(auth): gate local and OIDC authentication release

This commit is contained in:
2026-08-18 06:02:25 +02:00
parent 7cf7d9db6b
commit 8a3fa5031d
23 changed files with 1711 additions and 168 deletions
+101 -46
View File
@@ -120,7 +120,7 @@ func TestRestoreStoppedInstallationRunsCheckpointRestoreAndVerification(t *testi
var events []string
var checkpointRequest CreateRequest
deps := restoreTestDependencies(t, runner)
deps.checkpointLocked = func(_ context.Context, _ config.Installation, request CreateRequest) (Result, error) {
deps.checkpoint = func(_ context.Context, _ *lifecycle.Transaction, _ config.Installation, request CreateRequest) (Result, error) {
events = append(events, "checkpoint")
checkpointRequest = request
return Result{Path: "/tmp/checkpoint.zip"}, nil
@@ -133,9 +133,9 @@ func TestRestoreStoppedInstallationRunsCheckpointRestoreAndVerification(t *testi
events = append(events, "cleanup-checkpoint")
return nil
}
deps.acquireLock = func(config.Installation) (restoreLock, error) {
deps.acquireTransaction = func(target config.Installation) (*lifecycle.Transaction, error) {
events = append(events, "lock")
return fakeRestoreLock{release: func() { events = append(events, "unlock") }}, nil
return lifecycle.AcquireTransaction(target)
}
deps.restoreFile = func(_ context.Context, _ config.Installation, entry ArchiveEntryMetadata, _ io.Reader) error {
events = append(events, "file:"+entry.Path)
@@ -159,9 +159,12 @@ func TestRestoreStoppedInstallationRunsCheckpointRestoreAndVerification(t *testi
if !checkpointRequest.IncludeSecrets || !checkpointRequest.Confirm {
t.Fatalf("checkpoint request = %#v, want private confirmed secret-aware checkpoint", checkpointRequest)
}
if got, want := events, []string{"lock", "checkpoint", "prepare-recovery", "file:configuration/operator.env", "health", "doctor", "pi", "workspace", "cleanup-checkpoint", "unlock"}; !equalStrings(got, want) {
if got, want := events, []string{"lock", "checkpoint", "prepare-recovery", "file:configuration/operator.env", "health", "doctor", "pi", "workspace", "cleanup-checkpoint"}; !equalStrings(got, want) {
t.Fatalf("restore events = %v, want %v", got, want)
}
if err := lifecycleLockFreeAfterTerminalRestore(installation); err != nil {
t.Fatal(err)
}
}
func TestRestoreClosesTargetArchiveBeforeReleasingLifecycleLock(t *testing.T) {
@@ -179,22 +182,85 @@ func TestRestoreClosesTargetArchiveBeforeReleasingLifecycleLock(t *testing.T) {
target = result
return result, err
}
unlockBeforeTargetClose := false
deps.acquireLock = func(config.Installation) (restoreLock, error) {
return fakeRestoreLock{release: func() {
if target.archive != nil && target.archive.file != nil {
if _, err := target.archive.file.Stat(); err == nil {
unlockBeforeTargetClose = true
}
}
}}, nil
}
deps.acquireTransaction = lifecycle.AcquireTransaction
if _, err := restoreWithDependencies(context.Background(), installation, RestoreRequest{Archive: archive, Confirm: true}, deps); err != nil {
t.Fatal(err)
}
if unlockBeforeTargetClose {
t.Fatal("restore released its lifecycle lock before closing the target archive")
if target.archive != nil && target.archive.file != nil {
if _, err := target.archive.file.Stat(); err == nil {
t.Fatal("restore did not close the target archive")
}
}
if err := lifecycleLockFreeAfterTerminalRestore(installation); err != nil {
t.Fatal(err)
}
}
func TestRestoreAcquiresLifecycleLockBeforeTargetDependentPreflight(t *testing.T) {
installation := preflightTestInstallation(t)
archive := filepath.Join(t.TempDir(), "restore.zip")
writePreflightArchive(t, archive, preflightArchiveSpec{
entries: []preflightArchiveEntry{{path: "configuration/operator.env", body: []byte("safe")}},
})
runner := newBackupRunner(installation, false)
deps := restoreTestDependencies(t, runner)
deps.acquireTransaction = lifecycle.AcquireTransaction
deps.preflight = func(ctx context.Context, target config.Installation, request PreflightRequest) (PreflightResult, error) {
probe, err := lifecycle.Acquire(target)
if err == nil {
_ = probe.Release()
return PreflightResult{}, errors.New("target-dependent preflight ran before lifecycle lock acquisition")
}
if !errors.Is(err, lifecycle.ErrLocked) {
return PreflightResult{}, fmt.Errorf("probe lifecycle lock: %w", err)
}
return Preflight(ctx, target, request, permissivePreflightDependencies())
}
if _, err := restoreWithDependencies(context.Background(), installation, RestoreRequest{Archive: archive, Confirm: true}, deps); err != nil {
t.Fatalf("Restore() error = %v, want preflight protected by lifecycle lock", err)
}
if err := lifecycleLockFreeAfterTerminalRestore(installation); err != nil {
t.Fatal(err)
}
}
func TestRestoreStagesArchiveAfterCheckpointAndRejectsMutation(t *testing.T) {
installation := preflightTestInstallation(t)
archive := filepath.Join(t.TempDir(), "restore.zip")
writePreflightArchive(t, archive, preflightArchiveSpec{
entries: []preflightArchiveEntry{{path: "configuration/operator.env", body: []byte("before")}},
})
runner := newBackupRunner(installation, false)
deps := restoreTestDependencies(t, runner)
deps.acquireTransaction = lifecycle.AcquireTransaction
deps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
writePreflightArchive(t, archive, preflightArchiveSpec{
entries: []preflightArchiveEntry{{path: "configuration/operator.env", body: []byte("after!")}},
})
return Result{Path: filepath.Join(t.TempDir(), "checkpoint.zip")}, nil
}
var restored [][]byte
deps.restoreFile = func(_ context.Context, _ config.Installation, _ ArchiveEntryMetadata, stream io.Reader) error {
body, err := io.ReadAll(stream)
if err != nil {
return err
}
restored = append(restored, body)
return nil
}
if _, err := restoreWithDependencies(context.Background(), installation, RestoreRequest{Archive: archive, Confirm: true}, deps); err == nil || !strings.Contains(err.Error(), "changed") {
t.Fatalf("Restore() error = %v, want archive mutation refusal", err)
}
if len(restored) != 0 {
t.Fatalf("restore applied mutated archive payloads: %q", restored)
}
if err := lifecycleLockFreeAfterTerminalRestore(installation); err != nil {
t.Fatal(err)
}
}
@@ -297,8 +363,8 @@ func TestRestoreLifecycleLockExcludesCompetingTransactionsUntilTerminalCleanup(t
caller, cancel := context.WithCancel(context.Background())
defer cancel()
deps := restoreTestDependencies(t, runner)
deps.acquireLock = func(target config.Installation) (restoreLock, error) { return lifecycle.Acquire(target) }
deps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
deps.acquireTransaction = lifecycle.AcquireTransaction
deps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
gate("checkpoint")
return Result{Path: filepath.Join(t.TempDir(), "checkpoint.zip")}, nil
}
@@ -369,8 +435,8 @@ func competingRestoreAndBackupEntry(installation config.Installation, archive st
checkpointCalled := false
restoreDeps := restoreTestDependencies(t, newBackupRunner(installation, false))
restoreDeps.acquireLock = func(target config.Installation) (restoreLock, error) { return lifecycle.Acquire(target) }
restoreDeps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
restoreDeps.acquireTransaction = lifecycle.AcquireTransaction
restoreDeps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
checkpointCalled = true
return Result{Path: filepath.Join(t.TempDir(), "competing-checkpoint.zip")}, nil
}
@@ -408,8 +474,8 @@ func TestRestoreCannotApplyAStaleCheckpointOverAnInterleavedRestore(t *testing.T
continueCheckpoint := make(chan struct{})
firstRunner := newBackupRunner(installation, true)
firstDeps := restoreTestDependencies(t, firstRunner)
firstDeps.acquireLock = func(target config.Installation) (restoreLock, error) { return lifecycle.Acquire(target) }
firstDeps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
firstDeps.acquireTransaction = lifecycle.AcquireTransaction
firstDeps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
checkpointState = targetState
close(checkpointEntered)
<-continueCheckpoint
@@ -439,8 +505,8 @@ func TestRestoreCannotApplyAStaleCheckpointOverAnInterleavedRestore(t *testing.T
interleavedCheckpoint := false
interleavedMutation := false
interleavedDeps := restoreTestDependencies(t, newBackupRunner(installation, false))
interleavedDeps.acquireLock = func(target config.Installation) (restoreLock, error) { return lifecycle.Acquire(target) }
interleavedDeps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
interleavedDeps.acquireTransaction = lifecycle.AcquireTransaction
interleavedDeps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
interleavedCheckpoint = true
return Result{Path: filepath.Join(t.TempDir(), "interleaved-checkpoint.zip")}, nil
}
@@ -542,7 +608,7 @@ func TestRestorePreflightFailureDoesNotMutateTarget(t *testing.T) {
return PreflightResult{}, preflightErr
}
checkpointCalls := 0
deps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
deps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
checkpointCalls++
return Result{}, nil
}
@@ -567,7 +633,7 @@ func TestRestoreCheckpointFailureDoesNotMutateTarget(t *testing.T) {
runner := newBackupRunner(installation, true)
deps := restoreTestDependencies(t, runner)
checkpointErr := errors.New("checkpoint unavailable")
deps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
deps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
return Result{}, checkpointErr
}
restoredFiles := 0
@@ -591,7 +657,7 @@ func TestRestoreFileFailureRollsBackSecretAwareCheckpointBeforeCleanup(t *testin
runner := newBackupRunner(installation, true)
deps := restoreTestDependencies(t, runner)
fileErr := errors.New("cannot restore operator configuration")
deps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
deps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
return Result{Path: "/tmp/recovery.zip"}, nil
}
var events []string
@@ -1095,7 +1161,7 @@ func TestRestoreReleasesBarrierOnlyAfterVerifiedRecoveryFromLostResponse(t *test
}
runner := &commandFailureRunner{fakeBackupRunner: backing, failures: []*commandFailure{&failure}}
deps := restoreTestDependencies(t, runner)
deps.checkpointLocked = func(context.Context, config.Installation, CreateRequest) (Result, error) {
deps.checkpoint = func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
return Result{Path: "/tmp/recovery.zip"}, nil
}
deps.prepareRecovery = func(context.Context, config.Installation, string) (PreflightResult, error) {
@@ -1322,10 +1388,6 @@ func (runner *lifecycleGateRunner) SessionInventoryScope() string {
return runner.fakeBackupRunner.SessionInventoryScope()
}
type fakeRestoreLock struct {
release func()
}
type authenticationStateResetRunner struct {
args []string
result compose.Result
@@ -1431,13 +1493,6 @@ func TestVerifyRestoreWorkspaceRejectsInvalidOperatorResults(t *testing.T) {
}
}
func (lock fakeRestoreLock) Release() error {
if lock.release != nil {
lock.release()
}
return nil
}
func equalStrings(got, want []string) bool {
if len(got) != len(want) {
return false
@@ -1456,18 +1511,18 @@ func restoreTestDependencies(t *testing.T, runner archiveRunner) restoreDependen
preflight: func(ctx context.Context, installation config.Installation, request PreflightRequest) (PreflightResult, error) {
return Preflight(ctx, installation, request, permissivePreflightDependencies())
},
checkpointLocked: func(context.Context, config.Installation, CreateRequest) (Result, error) {
checkpoint: func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error) {
return Result{Path: "/tmp/default-checkpoint.zip"}, nil
},
prepareRecovery: func(context.Context, config.Installation, string) (PreflightResult, error) {
return PreflightResult{}, nil
},
recover: func(context.Context, config.Installation, PreflightResult, bool) error { return nil },
cleanupCheckpoint: func(string) error { return nil },
acquireLock: func(config.Installation) (restoreLock, error) { return fakeRestoreLock{}, nil },
runner: runner,
sleep: func(time.Duration) {},
restoreFile: func(context.Context, config.Installation, ArchiveEntryMetadata, io.Reader) error { return nil },
recover: func(context.Context, config.Installation, PreflightResult, bool) error { return nil },
cleanupCheckpoint: func(string) error { return nil },
acquireTransaction: lifecycle.AcquireTransaction,
runner: runner,
sleep: func(time.Duration) {},
restoreFile: func(context.Context, config.Installation, ArchiveEntryMetadata, io.Reader) error { return nil },
restoreVolume: func(context.Context, config.Installation, VolumeMetadata, io.Reader) error {
return nil
},