Skip to content

Commit d89e69e

Browse files
fix(daemon): gate managed.policy.set IPC on admin-token validation (PILOT-233) (#172)
The managed.policy.set IPC handler accepted arbitrary policy JSON from any same-UID process with no admin-token check. A malicious process could inject a policy that disables network gates by either setting "allow_everything" or exploiting fail-open semantics on evaluation error. This adds an admin-token gate to the daemon-side handler: - Wire format extended from [netID(2)][policyJSON...] to [netID(2)][tokenLen(2)][token...][policyJSON...] - Daemon validates the token via constant-time comparison against its configured AdminToken (if set). - Driver.PolicySet signature updated to accept adminToken. - When no admin token is configured, the gate is a no-op. Closes PILOT-233
1 parent e05aa1f commit d89e69e

6 files changed

Lines changed: 39 additions & 10 deletions

File tree

cmd/pilotctl/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6393,7 +6393,7 @@ func cmdPolicySet(args []string) {
63936393
d := connectDriver()
63946394
defer d.Close()
63956395

6396-
resp, err := d.PolicySet(netID, policyJSON)
6396+
resp, err := d.PolicySet(netID, policyJSON, adminToken)
63976397
if err != nil {
63986398
fmt.Fprintf(os.Stderr, "warning: policy saved to registry but daemon apply failed: %v\n", err)
63996399
return

pkg/daemon/ipc.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package daemon
44

55
import (
66
"context"
7+
"crypto/subtle"
78
"encoding/binary"
89
"encoding/json"
910
"errors"
@@ -1669,8 +1670,30 @@ func (s *IPCServer) handleManaged(conn *ipcConn, reqID uint64, payload []byte) {
16691670
}
16701671
data, _ := json.Marshal(resp)
16711672
s.ipcWriteManagedOK(conn, reqID, data)
1672-
case 0x01: // set — reload policy from registry
1673-
policyJSON := rest[3:]
1673+
case 0x01: // set — apply policy from registry (admin-token gated)
1674+
// Wire: [netID(2)][tokenLen(2)][token...][policyJSON...]
1675+
if len(rest) < 5 {
1676+
s.sendError(conn, reqID, "managed policy set: missing token length")
1677+
return
1678+
}
1679+
tokenLen := binary.BigEndian.Uint16(rest[3:5])
1680+
if len(rest) < 5+int(tokenLen) {
1681+
s.sendError(conn, reqID, "managed policy set: truncated token")
1682+
return
1683+
}
1684+
token := string(rest[5 : 5+tokenLen])
1685+
policyJSON := rest[5+tokenLen:]
1686+
1687+
// Validate admin token if the daemon has one configured.
1688+
// Without this gate any same-UID process can inject a
1689+
// policy that disables network gates.
1690+
if s.daemon.AdminToken() != "" {
1691+
if subtle.ConstantTimeCompare([]byte(token), []byte(s.daemon.AdminToken())) != 1 {
1692+
s.sendError(conn, reqID, "managed policy set: invalid admin token")
1693+
return
1694+
}
1695+
}
1696+
16741697
if len(policyJSON) == 0 {
16751698
s.sendError(conn, reqID, "managed policy set: missing policy JSON")
16761699
return

pkg/driver/driver.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,13 +455,19 @@ func (d *Driver) PolicyGet(networkID uint16) (map[string]interface{}, error) {
455455
}
456456

457457
// PolicySet sends a policy document to the daemon for immediate application.
458-
func (d *Driver) PolicySet(networkID uint16, policyJSON []byte) (map[string]interface{}, error) {
459-
msg := make([]byte, 5+len(policyJSON))
458+
// adminToken is validated by the daemon against its configured AdminToken.
459+
// Pass "" when the daemon has no admin token configured.
460+
func (d *Driver) PolicySet(networkID uint16, policyJSON []byte, adminToken string) (map[string]interface{}, error) {
461+
// Wire: [cmd][sub][action=0x01][netID(2)][tokenLen(2)][token...][policyJSON...]
462+
tokenLen := len(adminToken)
463+
msg := make([]byte, 7+tokenLen+len(policyJSON))
460464
msg[0] = cmdManaged
461465
msg[1] = subManagedPolicy
462466
msg[2] = 0x01 // set
463467
binary.BigEndian.PutUint16(msg[3:5], networkID)
464-
copy(msg[5:], policyJSON)
468+
binary.BigEndian.PutUint16(msg[5:7], uint16(tokenLen))
469+
copy(msg[7:7+tokenLen], []byte(adminToken))
470+
copy(msg[7+tokenLen:], policyJSON)
465471
return d.jsonRPC(msg, cmdManagedOK, "policy set")
466472
}
467473

pkg/driver/zz_driver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ func TestManagedFamilyRoundTrips(t *testing.T) {
704704
if _, err := drv.PolicyGet(5); err != nil {
705705
t.Fatal(err)
706706
}
707-
if _, err := drv.PolicySet(5, []byte(`{"version":1}`)); err != nil {
707+
if _, err := drv.PolicySet(5, []byte(`{"version":1}`), ""); err != nil {
708708
t.Fatal(err)
709709
}
710710
if _, err := drv.MemberTagsGet(5, 99); err != nil {

tests/zz_data_exchange_policy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestDataExchangePolicy(t *testing.T) {
104104

105105
// Deploy policy to each daemon via driver IPC (starts policy runner, bootstraps peers+tags)
106106
for name, d := range map[string]*DaemonInfo{"svc1": svc1, "svc2": svc2, "reg1": reg1, "reg2": reg2} {
107-
if _, err := d.Driver.PolicySet(netID, policyJSON); err != nil {
107+
if _, err := d.Driver.PolicySet(netID, policyJSON, ""); err != nil {
108108
t.Fatalf("policy set %s: %v", name, err)
109109
}
110110
}

tests/zz_ipc_ops_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ func TestPolicySetGetViaIPC(t *testing.T) {
395395
}
396396

397397
// Apply policy to daemon via IPC
398-
setResult, err := di.Driver.PolicySet(netID, policyJSON)
398+
setResult, err := di.Driver.PolicySet(netID, policyJSON, "")
399399
if err != nil {
400400
t.Fatalf("PolicySet: %v", err)
401401
}
@@ -458,7 +458,7 @@ func TestManagedOpsViaIPCWithPolicyRunner(t *testing.T) {
458458

459459
// Set policy with cycle/prune rules
460460
policyJSON := []byte(`{"version":1,"config":{"cycle":"1h","max_peers":50},"rules":[{"name":"cycle","on":"cycle","match":"true","actions":[{"type":"prune","params":{"count":2,"by":"age"}}]}]}`)
461-
_, err = di.Driver.PolicySet(netID, policyJSON)
461+
_, err = di.Driver.PolicySet(netID, policyJSON, "")
462462
if err != nil {
463463
t.Fatalf("PolicySet: %v", err)
464464
}

0 commit comments

Comments
 (0)