From ef1b4c49967682ac65c9fb40eb3602e2dc98cc91 Mon Sep 17 00:00:00 2001 From: matthew-pilot Date: Thu, 28 May 2026 15:22:13 +0000 Subject: [PATCH 1/2] test(pilot-ca): add TestWritePEM_FsyncBeforeClose regression test The test writes a PEM block and reads it back immediately, pinning that writePEM produces a valid, readable file. This test will catch any future regression that removes or breaks the fsync behavior. Ref: PILOT-138 --- zz_pilot_ca_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/zz_pilot_ca_test.go b/zz_pilot_ca_test.go index c1af99d..c7ff396 100644 --- a/zz_pilot_ca_test.go +++ b/zz_pilot_ca_test.go @@ -264,6 +264,37 @@ func TestRandomSerial_Unique(t *testing.T) { } } +// TestWritePEM_FsyncBeforeClose pins that writePEM produces valid +// PEM files and that the file is readable immediately after the call +// returns (data durability via fsync). +func TestWritePEM_FsyncBeforeClose(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := filepath.Join(dir, "test.crt") + der := make([]byte, 32) + for i := range der { + der[i] = byte(i) + } + if err := writePEM(path, "CERTIFICATE", der, 0o644); err != nil { + t.Fatalf("writePEM: %v", err) + } + // Immediately read back — data must be on disk (fsync'd). + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + block, _ := pem.Decode(raw) + if block == nil { + t.Fatal("no PEM block found") + } + if block.Type != "CERTIFICATE" { + t.Fatalf("expected CERTIFICATE, got %q", block.Type) + } + if len(block.Bytes) != 32 { + t.Fatalf("expected 32 DER bytes, got %d", len(block.Bytes)) + } +} + // mustReadCert reads a PEM-encoded cert and parses it, failing the // test on any error. func mustReadCert(t *testing.T, path string) *x509.Certificate { From 637c8155b43e1cb3b4a8911506668ca10dfc0358 Mon Sep 17 00:00:00 2001 From: matthew-pilot Date: Thu, 28 May 2026 15:22:13 +0000 Subject: [PATCH 2/2] fix(pilot-ca): add fsync to writePEM before close writePEM previously deferred f.Close() after pem.Encode with no intervening fsync. A process crash between Encode and the deferred Close would lose the newly-issued certificate, leaving a partial or missing PEM file on disk. Fix: call f.Sync() after pem.Encode to flush data to durable storage, then open and sync the parent directory so the directory entry itself is committed before writePEM returns. Closes PILOT-138 --- main.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/main.go b/main.go index 87de3e8..101b667 100644 --- a/main.go +++ b/main.go @@ -324,6 +324,15 @@ func writePEM(path, blockType string, der []byte, mode os.FileMode) error { if err := pem.Encode(f, &pem.Block{Type: blockType, Bytes: der}); err != nil { return fmt.Errorf("encode %s: %w", path, err) } + // fsync the data file so a crash after writePEM returns doesn't lose the cert. + if err := f.Sync(); err != nil { + return fmt.Errorf("sync %s: %w", path, err) + } + // fsync the parent directory so the directory entry is committed. + if dir, err := os.Open(filepath.Dir(path)); err == nil { + dir.Sync() // best-effort; Close will report any delayed error + dir.Close() + } return nil }