Files
netbird/shared/management/client/agentnetwork_test.go
mlsmaycon 9169a36658 [management, client] Make the agent-network setup RPC provable at runtime
An Unimplemented answer to GetAgentNetworkSetup can only come from a
server binary compiled without the regenerated management proto — the
combined and management servers share the one registration path in
boot.go. Make that failure mode self-diagnosing:

- Log "ManagementService registered on gRPC server (agent-network
  setup RPC available)" at boot, so server logs prove which build is
  running.
- Have the CLI name the management URL it dialed in every error, and
  map Unimplemented to an actionable message including the binary
  check (grep -ac GetAgentNetworkSetup <server binary>).
- Pin the wire path with a round-trip test: a real gRPC server built
  from this tree routes the RPC through the NaCl envelope end to end.

Verified live: a combined server built from this branch answers an
unregistered probe with PermissionDenied "peer is not registered",
never Unimplemented.

Linear: NET-1399
2026-08-04 02:06:48 +00:00

90 lines
3.0 KiB
Go

package client
import (
"context"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"github.com/netbirdio/netbird/encryption"
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
)
// agentNetworkSetupServer is a minimal ManagementService that answers
// GetServerKey and GetAgentNetworkSetup with the real NaCl-box envelope,
// so the test pins the full wire path: gRPC method routing, request
// encryption, and response decryption.
type agentNetworkSetupServer struct {
mgmtProto.UnimplementedManagementServiceServer
key wgtypes.Key
}
func (s *agentNetworkSetupServer) GetServerKey(_ context.Context, _ *mgmtProto.Empty) (*mgmtProto.ServerKeyResponse, error) {
return &mgmtProto.ServerKeyResponse{Key: s.key.PublicKey().String()}, nil
}
func (s *agentNetworkSetupServer) GetAgentNetworkSetup(_ context.Context, msg *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
peerKey, err := wgtypes.ParseKey(msg.WgPubKey)
if err != nil {
return nil, err
}
req := &mgmtProto.AgentNetworkSetupRequest{}
if err := encryption.DecryptMessage(peerKey, s.key, msg.Body, req); err != nil {
return nil, err
}
resp := &mgmtProto.AgentNetworkSetupResponse{
Configured: true,
Endpoint: "https://violet.eu.proxy.example.com",
Providers: []*mgmtProto.AgentNetworkProviderInfo{
{
Name: "Anthropic prod",
CatalogId: "anthropic_api",
ApiFlavor: "anthropic",
Models: []string{"claude-sonnet-4-5"},
},
},
}
body, err := encryption.EncryptMessage(peerKey, s.key, resp)
if err != nil {
return nil, err
}
return &mgmtProto.EncryptedMessage{WgPubKey: s.key.PublicKey().String(), Body: body}, nil
}
// TestGetAgentNetworkSetup_RoundTrip proves the RPC is routable on any
// server built from this tree and that the encrypt→invoke→decrypt path
// the CLI uses round-trips. A server answering this call with
// codes.Unimplemented is by definition running a binary compiled without
// the regenerated management proto.
func TestGetAgentNetworkSetup_RoundTrip(t *testing.T) {
serverKey, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
srv := grpc.NewServer()
mgmtProto.RegisterManagementServiceServer(srv, &agentNetworkSetupServer{key: serverKey})
go func() { _ = srv.Serve(lis) }()
t.Cleanup(srv.Stop)
clientKey, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
client, err := NewClient(context.Background(), lis.Addr().String(), clientKey, false)
require.NoError(t, err)
t.Cleanup(func() { _ = client.Close() })
setup, err := client.GetAgentNetworkSetup(context.Background())
require.NoError(t, err)
assert.True(t, setup.Configured)
assert.Equal(t, "https://violet.eu.proxy.example.com", setup.Endpoint)
require.Len(t, setup.Providers, 1)
assert.Equal(t, "anthropic_api", setup.Providers[0].CatalogId)
assert.Equal(t, []string{"claude-sonnet-4-5"}, setup.Providers[0].Models)
}