package hostfloor import ( "bytes" "crypto/ed25519" "crypto/rand" "encoding/json" "testing" "github.com/carlosframework/platform/internal/release" ) // The armed policy signed offline (claims run sign-policy) carries exactly // the floor bytes and authorization that Publish later writes for the same // floor. A box armed with it reports that floor's digest, and guard-exec // compares its local policy with the published pair, so any difference // would refuse every guarded launch once the floor is published. func TestArmedPolicyIsThePublishedAuthorization(t *testing.T) { pub, key, _ := ed25519.GenerateKey(rand.Reader) sg := release.RootSigner(key) floor := Floor{Min: 1, MinRelease: "v0.28.2"} p, err := ArmedPolicy(sg, "bucket", floor) if err != nil { t.Fatal(err) } if p.Schema != 1 || p.DeploymentBucket != "bucket" || p.Mode != "armed" { t.Fatalf("policy header %+v", p) } verified, err := p.Verify(pub, "bucket") if err != nil || verified.Floor != floor { t.Fatal("armed policy does not verify", verified, err) } if _, err = p.Verify(pub, "other"); err == nil { t.Fatal("armed policy verified for another deployment") } // The policy survives the file round trip guard-arm makes. wire, err := json.Marshal(p) if err != nil { t.Fatal(err) } var read Policy if err = json.Unmarshal(wire, &read); err != nil { t.Fatal(err) } if _, err = read.Verify(pub, "bucket"); err != nil || !bytes.Equal(read.Floor, p.Floor) || !bytes.Equal(read.Authorization, p.Authorization) { t.Fatal("policy file round trip changed the signed bytes", err) } s := release.NewDirStore(t.TempDir()) if err = Publish(t.Context(), s, sg, "bucket", floor); err != nil { t.Fatal(err) } raw, _, err := s.Get(t.Context(), Key) if err != nil { t.Fatal(err) } auth, _, err := s.Get(t.Context(), AuthKey) if err != nil { t.Fatal(err) } if !bytes.Equal(raw, p.Floor) || !bytes.Equal(auth, p.Authorization) { t.Fatalf("published pair differs from the armed policy:\nfloor %s\npolicy %s\nauth %s\npolicy %s", raw, p.Floor, auth, p.Authorization) } published, err := Read(t.Context(), s, pub, "bucket") if err != nil || published.Digest != verified.Digest { t.Fatal("published digest differs", published, verified, err) } // Signing again is the same bytes: ed25519 is deterministic and the // floor marshals one way. again, err := ArmedPolicy(sg, "bucket", floor) if err != nil || !bytes.Equal(again.Floor, p.Floor) || !bytes.Equal(again.Authorization, p.Authorization) { t.Fatal("second signature differs", err) } } func TestArmedPolicyRefusesAnInvalidFloor(t *testing.T) { _, key, _ := ed25519.GenerateKey(rand.Reader) sg := release.RootSigner(key) for name, tc := range map[string]struct { bucket string floor Floor }{ "no bucket": {"", Floor{Min: 1, MinRelease: "v1.0.0"}}, "protocol zero": {"bucket", Floor{Min: 0, MinRelease: "v1.0.0"}}, "no release": {"bucket", Floor{Min: 1}}, "not a tag": {"bucket", Floor{Min: 1, MinRelease: "1.0.0"}}, "build, not tag": {"bucket", Floor{Min: 1, MinRelease: "26065378"}}, } { t.Run(name, func(t *testing.T) { if _, err := ArmedPolicy(sg, tc.bucket, tc.floor); err == nil { t.Fatal("signed an invalid floor") } if _, _, err := Authorize(sg, tc.bucket, tc.floor); err == nil { t.Fatal("authorized an invalid floor") } }) } }