carlos / platform Public

release/console: pin production's pointers grant-free, refuse a scoped key in the console

Two places the change could still bite something live.

laddercheck_test.go pins the channel NAMES production runs on. It now also
pins their SHAPE: promoted with the root key, each of the four live
channels lands a wrapper with exactly payload and sig, and verifies
against the root pubkey. Scoped signing is opt-in from the first byte —
make the grant field non-optional, attach an empty one, or route root
signing through grant handling, and this fails on the key list before any
signature check, for the channels that stop deploys when they break.

And the console: it signs every promotion as if it held the root key, so
a scoped seed in its CARLOS_RELEASE_KEY would produce grantless pointers
that nothing in the fleet accepts, discovered later on a box that stopped
adopting. It now refuses to start with one and says why. Scoped keys are
for automation; the console promotes with the deployment's own authority
or runs read-only with none.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 81c4f1f7bc869297dec76a5fe044f363a4ed32bf parent 93449d5
2 files changed, +77 −1
  • cmd/console/main.go +9 −0
  • internal/release/laddercheck_test.go +68 −1
diff --git a/cmd/console/main.go b/cmd/console/main.go
index 3f771b83c..e70848e28 100644
--- a/cmd/console/main.go
+++ b/cmd/console/main.go
@@ -198,6 +198,15 @@ func serve() {
// the promote/rollback surface is disabled. Never logged, never rendered.
var key ed25519.PrivateKey
if os.Getenv("CARLOS_RELEASE_KEY") != "" {
+ // A SCOPED key here would be a silent trap: the console signs every
+ // promotion as if it held the root key, so a scoped seed would produce
+ // pointers with no grant attached, which nothing in the fleet accepts
+ // — and the operator would learn about it from a box that stopped
+ // adopting. Scoped keys are for automation; the console promotes with
+ // the deployment's own authority or not at all.
+ if release.IsScopedSeed(os.Getenv("CARLOS_RELEASE_KEY")) {
+ log.Fatal("console: CARLOS_RELEASE_KEY holds a scoped key — the console promotes with the deployment's ROOT key or runs read-only with none; a scoped key would sign pointers the fleet refuses")
+ }
key, err = release.ParseSigningKey(os.Getenv("CARLOS_RELEASE_KEY"), "CARLOS_RELEASE_KEY")
if err != nil {
log.Fatal(err)
diff --git a/internal/release/laddercheck_test.go b/internal/release/laddercheck_test.go
index 6b778e15c..182ce0da0 100644
--- a/internal/release/laddercheck_test.go
+++ b/internal/release/laddercheck_test.go
@@ -1,16 +1,21 @@
package release_test
import (
+ "context"
+ "encoding/json"
"testing"
"github.com/carlosframework/platform/internal/release"
)
+// liveChannels is what production is running on RIGHT NOW.
+var liveChannels = []string{"canary/platform-adoption", "canary/keymail", "canary/console", "canary/rehearsal"}
+
// The two channel names production is running on RIGHT NOW must never be
// refused by the ladder — go.tito.io follows canary/platform-adoption and
// keymail.dev follows canary/keymail. If these fail, production cannot deploy.
func TestLiveProductionChannelsAreNeverRefused(t *testing.T) {
- for _, ch := range []string{"canary/platform-adoption", "canary/keymail", "canary/console", "canary/rehearsal"} {
+ for _, ch := range liveChannels {
rung, ok := release.Rung(ch)
if !ok || rung != release.RungCanary {
t.Fatalf("Rung(%q) = %q,%v — want canary,true", ch, rung, ok)
@@ -36,6 +41,68 @@ func TestEdgeToStableIsRefusedAndNamesTheBypass(t *testing.T) {
}
}
+// The other half of "production can still deploy": every live channel is
+// promoted with the ROOT key, and what lands is a wrapper with NO grant in
+// it, verifying against the root public key. Scoped signing is opt-in from
+// the first byte — nothing about the fleet's existing pointers changes until
+// someone deliberately mints a scoped key.
+//
+// Mutation-mindedness: make the grant field non-optional, attach an empty
+// grant to root-signed pointers, or route root signing through grant
+// handling, and this fails on the key list — before any signature check, and
+// for every channel production actually runs on.
+func TestLiveProductionChannelsStayRootSignedWithNoGrant(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ seedB64, pubB64, err := release.GenerateKey()
+ if err != nil {
+ t.Fatalf("GenerateKey: %v", err)
+ }
+ priv, err := release.ParseSigningKey(seedB64, "test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ pub, err := release.ParsePublicKey(pubB64, "test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ m := release.Manifest{
+ App: "kass", Version: "9f3c1ad", Kind: "binary",
+ Artifacts: map[string]release.Artifact{"linux-arm64": {}},
+ }
+ if err := release.Ship(ctx, s, release.OpsAccount, m, map[string][]byte{"linux-arm64": []byte("build")}); err != nil {
+ t.Fatalf("Ship: %v", err)
+ }
+
+ for _, ch := range liveChannels {
+ if err := release.Promote(ctx, s, priv, release.OpsAccount, "kass", ch, "9f3c1ad"); err != nil {
+ t.Fatalf("Promote %s: %v", ch, err)
+ }
+ raw, _, err := s.Get(ctx, release.ChannelKey(release.OpsAccount, "kass", ch))
+ if err != nil {
+ t.Fatalf("read %s: %v", ch, err)
+ }
+ var keys map[string]json.RawMessage
+ if err := json.Unmarshal(raw, &keys); err != nil {
+ t.Fatalf("%s: %v", ch, err)
+ }
+ if len(keys) != 2 || keys["payload"] == nil || keys["sig"] == nil {
+ t.Errorf("%s wrapper has keys %v, want exactly payload and sig", ch, keysOf(keys))
+ }
+ if _, err := release.Channel(ctx, s, pub, release.OpsAccount, "kass", ch); err != nil {
+ t.Errorf("%s does not verify against the root pubkey: %v", ch, err)
+ }
+ }
+}
+
+func keysOf(m map[string]json.RawMessage) []string {
+ out := make([]string, 0, len(m))
+ for k := range m {
+ out = append(out, k)
+ }
+ return out
+}
+
func contains(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {