Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/pilotctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2814,7 +2814,8 @@ func cmdRotateKey(args []string) {
_ = args
d := connectDriver()
defer d.Close()
resp, err := d.RotateKey()
adminToken := getAdminToken()
resp, err := d.RotateKey(adminToken)
if err != nil {
fatalCode("connection_failed", "rotate-key: %v", err)
}
Expand Down
32 changes: 30 additions & 2 deletions pkg/daemon/ipc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package daemon

import (
"context"
"crypto/subtle"
"encoding/binary"
"encoding/json"
"errors"
Expand Down Expand Up @@ -645,7 +646,7 @@ func (s *IPCServer) dispatch(conn *ipcConn, cmd byte, reqID uint64, payload []by
case CmdManaged:
s.handleManaged(conn, reqID, payload)
case CmdRotateKey:
s.handleRotateKey(conn, reqID)
s.handleRotateKey(conn, reqID, payload)
default:
s.sendError(conn, reqID, fmt.Sprintf("unknown command: 0x%02X", cmd))
}
Expand Down Expand Up @@ -1155,7 +1156,34 @@ func (s *IPCServer) handleSetTags(conn *ipcConn, reqID uint64, payload []byte) {
}
}

func (s *IPCServer) handleRotateKey(conn *ipcConn, reqID uint64) {
// handleRotateKey services CmdRotateKey — admin-token-gated key rotation.
// Wire payload: [tokenLen(2)][token...]
//
// On success, replies with CmdRotateKeyOK carrying the new public key.
// On failure, replies with CmdError — "rotate_key denied: daemon has no
// admin token configured" or "rotate_key denied: invalid admin token".
func (s *IPCServer) handleRotateKey(conn *ipcConn, reqID uint64, payload []byte) {
// Admin token required: [tokenLen(2)][token...]
if len(payload) < 2 {
s.sendError(conn, reqID, "rotate_key: missing admin token")
return
}
tokenLen := binary.BigEndian.Uint16(payload[0:2])
if len(payload) < 2+int(tokenLen) {
s.sendError(conn, reqID, "rotate_key: truncated admin token")
return
}
token := string(payload[2 : 2+tokenLen])

if s.daemon.config.AdminToken == "" {
s.sendError(conn, reqID, "rotate_key denied: daemon has no admin token configured")
return
}
if subtle.ConstantTimeCompare([]byte(token), []byte(s.daemon.config.AdminToken)) != 1 {
s.sendError(conn, reqID, "rotate_key denied: invalid admin token")
return
}

result, err := s.daemon.RotateKey()
if err != nil {
s.sendError(conn, reqID, err.Error())
Expand Down
10 changes: 8 additions & 2 deletions pkg/daemon/zz_coverage_pkg_daemon_round2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package daemon

import (
"encoding/base64"
"encoding/binary"
"os"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -805,13 +806,18 @@ func TestHandleRotateKeyHappyPath(t *testing.T) {
t.Cleanup(func() { reg.Close() })
t.Cleanup(func() { rc.Close() })

d := New(Config{})
d := New(Config{AdminToken: "test-token"})
d.regConn = rc
registerSelfOnRegistry(t, d)

s := d.ipc
ic, client := newIPCTestConn(t)
reply := runHandler(t, client, func() { s.handleRotateKey(ic, 0) })
// Wire payload: [tokenLen(2)][token...]
token := "test-token"
payload := make([]byte, 2+len(token))
binary.BigEndian.PutUint16(payload[0:2], uint16(len(token)))
copy(payload[2:], token)
reply := runHandler(t, client, func() { s.handleRotateKey(ic, 0, payload) })

if len(reply) < 1 {
t.Fatalf("rotate-key reply too short: %d", len(reply))
Expand Down
9 changes: 7 additions & 2 deletions pkg/daemon/zz_coverage_pkg_daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1388,11 +1388,16 @@ func TestHandleManagedPolicyGetNoEngine(t *testing.T) {

func TestHandleRotateKeyNoIdentitySendsError(t *testing.T) {
t.Parallel()
d := New(Config{}) // no identity
d := New(Config{AdminToken: "test"}) // no identity, but admin token configured
s := d.ipc
ic, client := newIPCTestConn(t)
// send valid admin token; RotateKey should still fail because identity is nil
token := "test"
payload := make([]byte, 2+len(token))
binary.BigEndian.PutUint16(payload[0:2], uint16(len(token)))
copy(payload[2:], token)
reply := runHandler(t, client, func() {
s.handleRotateKey(ic, 0)
s.handleRotateKey(ic, 0, payload)
})
if reply[0] != CmdError {
t.Fatalf("expected error reply; got 0x%02X", reply[0])
Expand Down
12 changes: 10 additions & 2 deletions pkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,16 @@ func (d *Driver) SetWebhook(url string) (map[string]interface{}, error) {
// RotateKey asks the daemon to rotate its Ed25519 identity at the registry.
// The daemon generates a new keypair, signs proof of the current key, calls
// registry.RotateKey, then atomically swaps and persists the new identity.
func (d *Driver) RotateKey() (map[string]interface{}, error) {
return d.jsonRPC([]byte{cmdRotateKey}, cmdRotateKeyOK, "rotate_key")
// RotateKey sends a key-rotation request to the daemon. The adminToken
// must match the daemon's configured AdminToken (empty = denied). The
// wire format is [cmd(1)][tokenLen(2)][token...].
func (d *Driver) RotateKey(adminToken string) (map[string]interface{}, error) {
tokenLen := len(adminToken)
msg := make([]byte, 3+tokenLen)
msg[0] = cmdRotateKey
binary.BigEndian.PutUint16(msg[1:3], uint16(tokenLen))
copy(msg[3:], adminToken)
return d.jsonRPC(msg, cmdRotateKeyOK, "rotate_key")
}

// Disconnect closes a connection by ID. Used by administrative tools.
Expand Down
2 changes: 1 addition & 1 deletion pkg/driver/zz_driver_simple_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestDriverRotateKey(t *testing.T) {
}
defer drv.Close()

result, err := drv.RotateKey()
result, err := drv.RotateKey("")
if err != nil {
t.Fatalf("RotateKey: %v", err)
}
Expand Down
Loading