package release import ( "context" "encoding/json" "os" "os/exec" "testing" "github.com/carlosframework/platform/internal/rootstate" ) type generationCertificateCrash struct { rootstate.Store phase string } func (s generationCertificateCrash) Write(k string, b []byte) error { if err := s.Store.Write(k, b); err != nil { return err } var c GenerationCompleteness if k == generationCompletenessKey && json.Unmarshal(b, &c) == nil { if s.phase == "invalidate" && !c.Complete { os.Exit(74) } if s.phase == "certified" && c.Complete && c.Revision > 1 { os.Exit(74) } } return nil } func TestPlan14GenerationCompletenessProcessCrash(t *testing.T) { if dir := os.Getenv("PLAN14_GENERATION_CERT_CHILD"); dir != "" { phase := os.Getenv("PLAN14_GENERATION_CERT_PHASE") disk := &healthyRetryStore{dir: dir} g := &RootGenerations{Store: generationCertificateCrash{disk, phase}} records, err := g.Reservations(t.Context()) if err != nil { t.Fatal(err) } _, err = g.Certify(t.Context(), records, func(context.Context, []GenerationReservation) (GenerationEvidence, error) { if phase == "verify" { os.Exit(74) } return generationEvidence(), nil }) t.Fatal("crash not reached", err) } for _, phase := range []string{"invalidate", "verify", "certified"} { t.Run(phase, func(t *testing.T) { g, s, b := generationFixture(t) r, err := g.Reserve(b) if err != nil { t.Fatal(err) } records, err := g.Reservations(t.Context()) if err != nil { t.Fatal(err) } verify := func(context.Context, []GenerationReservation) (GenerationEvidence, error) { return generationEvidence(), nil } if _, err = g.Certify(t.Context(), records, verify); err != nil { t.Fatal(err) } cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationCompletenessProcessCrash$", "-test.count=1") cmd.Dir = t.TempDir() cmd.Env = append(os.Environ(), "PLAN14_GENERATION_CERT_CHILD="+s.dir, "PLAN14_GENERATION_CERT_PHASE="+phase) out, err := cmd.CombinedOutput() if code, ok := err.(*exec.ExitError); !ok || code.ExitCode() != 74 { t.Fatalf("wrong crash %v %s", err, out) } reopened := &RootGenerations{Store: s} c, err := reopened.Completeness() if err != nil || c.Complete != (phase == "certified") { t.Fatalf("crash state %+v %v", c, err) } if got, err := reopened.Read(b); err != nil || got != r { t.Fatal("crash lost pin", err) } // Even the positive record cannot replace fresh kernel reconciliation. if _, err := reopened.Certify(t.Context(), records, func(context.Context, []GenerationReservation) (GenerationEvidence, error) { return GenerationEvidence{}, os.ErrPermission }); err == nil { t.Fatal("crashed certificate supplied authority") } if _, err := reopened.Certify(t.Context(), records, verify); err != nil { t.Fatal(err) } }) } }