fix(release): harden handoff auth stores and installer rehearsal harness

This commit is contained in:
rcourtman
2026-03-26 13:32:39 +00:00
parent f34ec16138
commit cf2915ca54
7 changed files with 76 additions and 21 deletions
+5
View File
@@ -206,6 +206,11 @@ func InitSessionStore(dataPath string) {
}
}
func InitPersistentAuthStores(dataPath string) {
InitSessionStore(dataPath)
InitCSRFStore(dataPath)
}
// GetSessionStore returns the global session store instance
func GetSessionStore() *SessionStore {
sessionStoreMu.Lock()
+1
View File
@@ -20,6 +20,7 @@ import (
// Self-guards: returns 404 if the handoff key file does not exist in dataPath,
// meaning this is not a cloud-managed tenant.
func HandleCloudHandoff(dataPath string) http.HandlerFunc {
InitPersistentAuthStores(dataPath)
replay := &jtiReplayStore{configDir: dataPath}
return func(w http.ResponseWriter, r *http.Request) {
+1
View File
@@ -281,6 +281,7 @@ func normalizeHandoffEmail(email string) string {
// this returns a success payload instead of redirecting.
func HandleHandoffExchange(configDir string) http.HandlerFunc {
configDir = filepath.Clean(configDir)
InitPersistentAuthStores(configDir)
keyPath := filepath.Join(configDir, "secrets", "handoff.key")
replay := &jtiReplayStore{configDir: configDir}
+8
View File
@@ -14,6 +14,8 @@ import (
)
func TestHandleCloudHandoffRejectsReplay(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
dataPath := t.TempDir()
key := []byte("0123456789abcdef0123456789abcdef")
if err := os.WriteFile(filepath.Join(dataPath, cloudauth.HandoffKeyFile), key, 0o600); err != nil {
@@ -54,6 +56,8 @@ func TestHandleCloudHandoffRejectsReplay(t *testing.T) {
}
func TestHandleCloudHandoffSetsTenantOrgCookie(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
dataPath := t.TempDir()
key := []byte("0123456789abcdef0123456789abcdef")
if err := os.WriteFile(filepath.Join(dataPath, cloudauth.HandoffKeyFile), key, 0o600); err != nil {
@@ -91,6 +95,8 @@ func TestHandleCloudHandoffSetsTenantOrgCookie(t *testing.T) {
}
func TestHandleCloudHandoffRejectsInvalidTenantID(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
dataPath := t.TempDir()
key := []byte("0123456789abcdef0123456789abcdef")
if err := os.WriteFile(filepath.Join(dataPath, cloudauth.HandoffKeyFile), key, 0o600); err != nil {
@@ -117,6 +123,8 @@ func TestHandleCloudHandoffRejectsInvalidTenantID(t *testing.T) {
}
func TestHandleCloudHandoffLowercasesSessionEmailIdentity(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
dataPath := t.TempDir()
key := []byte("0123456789abcdef0123456789abcdef")
if err := os.WriteFile(filepath.Join(dataPath, cloudauth.HandoffKeyFile), key, 0o600); err != nil {
+9 -3
View File
@@ -30,6 +30,7 @@ type CSRFTokenStore struct {
dataPath string
saveTicker *time.Ticker
stopChan chan bool
workerDone chan struct{}
stopOnce sync.Once
}
@@ -112,9 +113,10 @@ func InitCSRFStore(dataPath string) {
oldStore := csrfStore
csrfStore = &CSRFTokenStore{
tokens: make(map[string]*CSRFToken),
dataPath: newDataPath,
stopChan: make(chan bool),
tokens: make(map[string]*CSRFToken),
dataPath: newDataPath,
stopChan: make(chan bool),
workerDone: make(chan struct{}),
}
csrfStoreDataPath = newDataPath
@@ -154,6 +156,9 @@ func (c *CSRFTokenStore) Shutdown() {
default:
}
})
if c.workerDone != nil {
<-c.workerDone
}
}
func resetCSRFStoreForTests() {
@@ -169,6 +174,7 @@ func resetCSRFStoreForTests() {
// backgroundWorker handles periodic saves and cleanup
func (c *CSRFTokenStore) backgroundWorker() {
defer close(c.workerDone)
for {
select {
case <-c.saveTicker.C:
+10 -4
View File
@@ -20,6 +20,7 @@ type SessionStore struct {
dataPath string
saveTicker *time.Ticker
stopChan chan bool
workerDone chan struct{}
stopOnce sync.Once
crypto *crypto.CryptoManager
}
@@ -150,10 +151,11 @@ func NewSessionStore(dataPath string) *SessionStore {
}
store := &SessionStore{
sessions: make(map[string]*SessionData),
dataPath: dataPath,
stopChan: make(chan bool),
crypto: cm,
sessions: make(map[string]*SessionData),
dataPath: dataPath,
stopChan: make(chan bool),
workerDone: make(chan struct{}),
crypto: cm,
}
// Load existing sessions from disk
@@ -179,10 +181,14 @@ func (s *SessionStore) Shutdown() {
default:
}
})
if s.workerDone != nil {
<-s.workerDone
}
}
// backgroundWorker handles periodic saves and cleanup
func (s *SessionStore) backgroundWorker() {
defer close(s.workerDone)
for {
select {
case <-s.saveTicker.C:
+42 -14
View File
@@ -776,6 +776,32 @@ func extractRootInstallShellFunction(t *testing.T, name string) string {
return string(match)
}
func extractSetupAutoUpdatesShellFunctions(t *testing.T) string {
t.Helper()
return extractRootInstallShellFunction(t, "selected_update_channel") + "\n" +
extractRootInstallShellFunction(t, "repo_web_url") + "\n" +
extractRootInstallShellFunction(t, "configure_auto_update_script_repo") + "\n" +
extractRootInstallShellFunction(t, "setup_auto_updates")
}
func prepareAutoUpdatePaths(t *testing.T, tmpDir string) (string, string, string) {
t.Helper()
autoUpdateDest := filepath.Join(tmpDir, "bin", "pulse-auto-update.sh")
servicePath := filepath.Join(tmpDir, "systemd", "pulse-update.service")
timerPath := filepath.Join(tmpDir, "systemd", "pulse-update.timer")
if err := os.MkdirAll(filepath.Dir(autoUpdateDest), 0755); err != nil {
t.Fatalf("mkdir auto-update dest dir: %v", err)
}
if err := os.MkdirAll(filepath.Dir(servicePath), 0755); err != nil {
t.Fatalf("mkdir systemd dir: %v", err)
}
return autoUpdateDest, servicePath, timerPath
}
func extractAutoUpdateFunction(t *testing.T, name string) string {
t.Helper()
@@ -1620,16 +1646,13 @@ func TestSelectedUpdateChannelTreatsPrereleaseVersionAsRC(t *testing.T) {
func TestSetupAutoUpdatesCreatesSystemJSONWithSelectedChannel(t *testing.T) {
tmpDir := t.TempDir()
configDir := filepath.Join(tmpDir, "config")
systemdDir := filepath.Join(tmpDir, "systemd")
installDir := filepath.Join(tmpDir, "install")
autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh")
autoUpdateDest, servicePath, timerPath := prepareAutoUpdatePaths(t, tmpDir)
if err := os.MkdirAll(configDir, 0755); err != nil {
t.Fatalf("mkdir config dir: %v", err)
}
if err := os.MkdirAll(systemdDir, 0755); err != nil {
t.Fatalf("mkdir systemd dir: %v", err)
}
if err := os.MkdirAll(filepath.Dir(autoUpdateSrc), 0755); err != nil {
t.Fatalf("mkdir auto-update src dir: %v", err)
}
@@ -1640,6 +1663,9 @@ func TestSetupAutoUpdatesCreatesSystemJSONWithSelectedChannel(t *testing.T) {
script := `
CONFIG_DIR="` + configDir + `"
INSTALL_DIR="` + installDir + `"
PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `"
PULSE_UPDATE_SERVICE_PATH="` + servicePath + `"
PULSE_UPDATE_TIMER_PATH="` + timerPath + `"
FORCE_CHANNEL="rc"
UPDATE_CHANNEL=""
GITHUB_REPO="rcourtman/Pulse"
@@ -1653,8 +1679,7 @@ func TestSetupAutoUpdatesCreatesSystemJSONWithSelectedChannel(t *testing.T) {
chown() { :; }
cat() { command cat "$@"; }
mkdir() { command mkdir "$@"; }
` + extractRootInstallShellFunction(t, "selected_update_channel") + `
` + extractRootInstallShellFunction(t, "setup_auto_updates") + `
` + extractSetupAutoUpdatesShellFunctions(t) + `
setup_auto_updates
`
@@ -1728,10 +1753,7 @@ func TestSetupAutoUpdatesConfiguresInstalledAutoUpdateRepo(t *testing.T) {
rm() { command rm "$@"; }
awk() { command awk "$@"; }
mktemp() { command mktemp "$@"; }
` + extractRootInstallShellFunction(t, "selected_update_channel") + `
` + extractRootInstallShellFunction(t, "repo_web_url") + `
` + extractRootInstallShellFunction(t, "configure_auto_update_script_repo") + `
` + extractRootInstallShellFunction(t, "setup_auto_updates") + `
` + extractSetupAutoUpdatesShellFunctions(t) + `
setup_auto_updates
`
@@ -1779,6 +1801,7 @@ func TestSetupAutoUpdatesTreatsPrereleaseVersionAsRCChannel(t *testing.T) {
configDir := filepath.Join(tmpDir, "config")
installDir := filepath.Join(tmpDir, "install")
autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh")
autoUpdateDest, servicePath, timerPath := prepareAutoUpdatePaths(t, tmpDir)
if err := os.MkdirAll(configDir, 0755); err != nil {
t.Fatalf("mkdir config dir: %v", err)
@@ -1793,6 +1816,9 @@ func TestSetupAutoUpdatesTreatsPrereleaseVersionAsRCChannel(t *testing.T) {
script := `
CONFIG_DIR="` + configDir + `"
INSTALL_DIR="` + installDir + `"
PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `"
PULSE_UPDATE_SERVICE_PATH="` + servicePath + `"
PULSE_UPDATE_TIMER_PATH="` + timerPath + `"
FORCE_CHANNEL=""
FORCE_VERSION="v1.2.3-rc.4"
UPDATE_CHANNEL=""
@@ -1803,8 +1829,7 @@ func TestSetupAutoUpdatesTreatsPrereleaseVersionAsRCChannel(t *testing.T) {
safe_systemctl() { :; }
systemctl() { return 0; }
chown() { :; }
` + extractRootInstallShellFunction(t, "selected_update_channel") + `
` + extractRootInstallShellFunction(t, "setup_auto_updates") + `
` + extractSetupAutoUpdatesShellFunctions(t) + `
setup_auto_updates
`
@@ -1831,6 +1856,7 @@ func TestSetupAutoUpdatesPreservesRCChannelWhenUpdatingExistingConfig(t *testing
configDir := filepath.Join(tmpDir, "config")
installDir := filepath.Join(tmpDir, "install")
autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh")
autoUpdateDest, servicePath, timerPath := prepareAutoUpdatePaths(t, tmpDir)
if err := os.MkdirAll(configDir, 0755); err != nil {
t.Fatalf("mkdir config dir: %v", err)
@@ -1848,6 +1874,9 @@ func TestSetupAutoUpdatesPreservesRCChannelWhenUpdatingExistingConfig(t *testing
script := `
CONFIG_DIR="` + configDir + `"
INSTALL_DIR="` + installDir + `"
PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `"
PULSE_UPDATE_SERVICE_PATH="` + servicePath + `"
PULSE_UPDATE_TIMER_PATH="` + timerPath + `"
FORCE_CHANNEL=""
UPDATE_CHANNEL=""
GITHUB_REPO="rcourtman/Pulse"
@@ -1858,8 +1887,7 @@ func TestSetupAutoUpdatesPreservesRCChannelWhenUpdatingExistingConfig(t *testing
systemctl() { return 0; }
command -v jq >/dev/null 2>&1 || true
chown() { :; }
` + extractRootInstallShellFunction(t, "selected_update_channel") + `
` + extractRootInstallShellFunction(t, "setup_auto_updates") + `
` + extractSetupAutoUpdatesShellFunctions(t) + `
setup_auto_updates
`