From 05dc31ceda7806c2687b56f3e53c924b35a6f5d4 Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Fri, 29 May 2026 14:00:41 -0700 Subject: [PATCH] feat(driver): add adminToken parameter to PolicySet (PILOT-233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the change that landed in TeoSlayer/pilotprotocol#172 — the managed.policy.set IPC is a network-admin operation; without an adminToken parameter, the gate that PR #172 added to the daemon side has no caller-side support. Wire format: [cmd][sub][action=0x01][netID(2)][tokenLen(2)][token...][policyJSON...] Threat model alignment (per PILOT-347): - Only the network administrators hold the admin token. - managed.policy.set is correctly admin-gated. - User-owned IPC ops (rotate_key, handshake approve/reject/revoke, set_webhook) belong on a SO_PEERCRED check, not admin-token — see TeoSlayer/pilotprotocol#176 for that pattern. Empty adminToken is fine for solo-mode daemons (no AdminToken configured); managed-mode daemons reject empty. Tests updated to pass empty token. go test ./driver/... PASS. --- driver/driver.go | 16 +++++++++++++--- driver/zz_driver_test.go | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/driver/driver.go b/driver/driver.go index e1d3a3a..e83385f 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -455,13 +455,23 @@ func (d *Driver) PolicyGet(networkID uint16) (map[string]interface{}, error) { } // PolicySet sends a policy document to the daemon for immediate application. -func (d *Driver) PolicySet(networkID uint16, policyJSON []byte) (map[string]interface{}, error) { - msg := make([]byte, 5+len(policyJSON)) +// adminToken authenticates the caller as a network administrator — +// pilot-daemon's managed.policy.set IPC handler validates this against +// its configured AdminToken before applying the policy. Empty token is +// accepted by the daemon iff it has no AdminToken configured (default +// solo-mode daemons); managed-mode daemons reject empty tokens. +// (PILOT-233) +func (d *Driver) PolicySet(networkID uint16, policyJSON []byte, adminToken string) (map[string]interface{}, error) { + // Wire: [cmd][sub][action=0x01][netID(2)][tokenLen(2)][token...][policyJSON...] + tokenLen := len(adminToken) + msg := make([]byte, 7+tokenLen+len(policyJSON)) msg[0] = cmdManaged msg[1] = subManagedPolicy msg[2] = 0x01 // set binary.BigEndian.PutUint16(msg[3:5], networkID) - copy(msg[5:], policyJSON) + binary.BigEndian.PutUint16(msg[5:7], uint16(tokenLen)) + copy(msg[7:7+tokenLen], []byte(adminToken)) + copy(msg[7+tokenLen:], policyJSON) return d.jsonRPC(msg, cmdManagedOK, "policy set") } diff --git a/driver/zz_driver_test.go b/driver/zz_driver_test.go index e6e6dd4..85ae821 100644 --- a/driver/zz_driver_test.go +++ b/driver/zz_driver_test.go @@ -704,7 +704,7 @@ func TestManagedFamilyRoundTrips(t *testing.T) { if _, err := drv.PolicyGet(5); err != nil { t.Fatal(err) } - if _, err := drv.PolicySet(5, []byte(`{"version":1}`)); err != nil { + if _, err := drv.PolicySet(5, []byte(`{"version":1}`), ""); err != nil { t.Fatal(err) } if _, err := drv.MemberTagsGet(5, 99); err != nil {