| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "crypto/ed25519" |
| 5 | "crypto/sha256" |
| 6 | "encoding/hex" |
| 7 | "encoding/json" |
| 8 | "github.com/carlosframework/platform/internal/hostactors" |
| 9 | "github.com/carlosframework/platform/internal/hostclaim" |
| 10 | "github.com/carlosframework/platform/internal/hostevidence" |
| 11 | "github.com/carlosframework/platform/internal/release" |
| 12 | "strings" |
| 13 | "testing" |
| 14 | "time" |
| 15 | ) |
| 16 | |
| 17 | func TestPlan13RecoveryHomeReceipt(t *testing.T) { |
| 18 | t.Parallel() |
| 19 | for _, bad := range []string{"", "session", "stamp", "enrollment", "signature", "operation", "region", "digest", "unbound", "roster-name"} { |
| 20 | t.Run(bad, func(t *testing.T) { |
| 21 | f, o, h := plan12Handover(t, hostclaim.KindRestoring, "stop") |
| 22 | o.Claim.Op.Intent["to"] = "@home" |
| 23 | o.Claim.Op.Intent["home_region"] = "uk" |
| 24 | if bad == "unbound" { |
| 25 | delete(o.Claim.Op.Intent, "home_region") |
| 26 | } |
| 27 | key := ed25519.NewKeyFromSeed([]byte(strings.Repeat("b", 32))) |
| 28 | en := strings.Repeat("a", 32) |
| 29 | d := hostactors.Directory{Store: f.claims, PublicKey: f.signer.Public().(ed25519.PublicKey), Bucket: "bucket"} |
| 30 | roster := hostactors.Roster{HomeRegion: "uk", V: 1, Revision: 1, DeploymentBucket: "bucket", Boxes: map[string]hostactors.Box{"box-2": {Enrollment: en, BoxID: "box-2", Label: "box-2", Namespace: "platform", Region: "uk", Pool: "default", State: "active", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-2", InstanceID: "i-target"}, EvidenceSHA256: strings.Repeat("d", 64), CensusPublicKey: key.Public().(ed25519.PublicKey)}}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}} |
| 31 | if bad == "roster-name" { |
| 32 | b := roster.Boxes["box-2"] |
| 33 | b.Region = "ie" |
| 34 | roster.Boxes["box-2"] = b |
| 35 | } |
| 36 | wire, _ := release.SignRootEnvelope(release.RootSigner(f.signer), roster) |
| 37 | if _, err := f.claims.Put(t.Context(), hostactors.Key, wire, ""); err != nil { |
| 38 | t.Fatal(err) |
| 39 | } |
| 40 | r := StopReceipt{V: 1, ID: "stop", Kind: "stopped", Host: o.Host, Incarnation: o.Claim.Holder.Incarnation, InstallationStamp: 2, WriterSession: 2, OperationID: "m1", Phase: "releasing", ObligationID: "writer-session-2", Box: "box-2", Region: "uk", Enrollment: en, EvidenceAt: time.Now(), RequestLimitNS: int64(2 * time.Minute), ClaimGen: 1, FenceID: "fence", InventorySHA256: strings.Repeat("e", 64), LocalJournalSHA256: strings.Repeat("f", 64), ProducerDrainDigest: strings.Repeat("a", 64), CgroupIdentities: []string{"root-role"}} |
| 41 | switch bad { |
| 42 | case "session": |
| 43 | r.WriterSession++ |
| 44 | case "stamp": |
| 45 | r.InstallationStamp++ |
| 46 | case "enrollment": |
| 47 | r.Enrollment = strings.Repeat("e", 32) |
| 48 | case "operation": |
| 49 | r.OperationID = "other" |
| 50 | case "region": |
| 51 | r.Region = "ie" |
| 52 | } |
| 53 | body, _ := json.Marshal(r) |
| 54 | receiptKey := stopEvidenceKey(o.Host, "m1", "box-2", "stop") |
| 55 | sum := sha256.Sum256(body) |
| 56 | signer := key |
| 57 | if bad == "signature" { |
| 58 | signer = f.signer |
| 59 | } |
| 60 | if err := hostevidence.Publish(t.Context(), f.claims, signer, receiptKey, body); err != nil { |
| 61 | t.Fatal(err) |
| 62 | } |
| 63 | h.Directory = d |
| 64 | o.Claim.Op.Journal["target_stop_receipt"] = receiptKey |
| 65 | o.Claim.Op.Journal["target_stop_sha256"] = hex.EncodeToString(sum[:]) |
| 66 | o.Claim.Op.Journal[hostclaim.JournalTargetStamp] = "2" |
| 67 | // Remove the unsigned report path: the receipt alone must suffice. |
| 68 | delete(o.Claim.Op.Journal, hostclaim.JournalEvidenceTarget) |
| 69 | if bad == "digest" { |
| 70 | o.Claim.Op.Journal["target_stop_sha256"] = strings.Repeat("0", 64) |
| 71 | } |
| 72 | err := h.recoveryEvidence(t.Context(), o) |
| 73 | stored, _, readErr := f.claims.Get(t.Context(), receiptKey) |
| 74 | if readErr != nil || string(stored) != string(body) { |
| 75 | t.Fatal("receipt bytes changed") |
| 76 | } |
| 77 | if (err == nil) != (bad == "") { |
| 78 | t.Fatal(bad, err) |
| 79 | } |
| 80 | }) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | func TestPlan13HomeRecoveryFenceBinding(t *testing.T) { |
| 85 | t.Parallel() |
| 86 | for _, home := range []string{"uk", "", "ie"} { |
| 87 | _, o, h := plan12Handover(t, hostclaim.KindRestoring, "fence") |
| 88 | o.Claim.Op.Intent["to"] = "@home" |
| 89 | o.Claim.Op.Intent["home_region"] = home |
| 90 | h.FenceAuthority = plan12FenceAuthority{home: "uk"} |
| 91 | err := h.recoveryEvidence(t.Context(), o) |
| 92 | if (err == nil) != (home == "uk") { |
| 93 | t.Fatal(home, err) |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |