mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-07 15:31:30 +02:00
[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
This commit is contained in:
@@ -104,10 +104,17 @@ func fetchAgentNetworkSetup(ctx context.Context) (*mgmProto.AgentNetworkSetupRes
|
||||
|
||||
setup, err := mgmClient.GetAgentNetworkSetup(mgmCtx)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||
return nil, fmt.Errorf("this peer is not registered with the management service — run 'netbird up' first")
|
||||
if s, ok := status.FromError(err); ok {
|
||||
switch s.Code() {
|
||||
case codes.PermissionDenied:
|
||||
return nil, fmt.Errorf("this peer is not registered with the management service at %s — run 'netbird up' first", config.ManagementURL.String())
|
||||
case codes.Unimplemented:
|
||||
return nil, fmt.Errorf("the management server at %s does not implement the agent-network setup RPC — the process answering runs a build without it.\n"+
|
||||
"Verify the running binary contains the RPC: grep -ac GetAgentNetworkSetup <path-to-server-binary> (0 = built without it),\n"+
|
||||
"and that this URL actually reaches the server you rebuilt", config.ManagementURL.String())
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("get agent network setup: %v", err)
|
||||
return nil, fmt.Errorf("get agent network setup from %s: %v", config.ManagementURL.String(), err)
|
||||
}
|
||||
return setup, nil
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ func (s *BaseServer) GRPCServer() *grpc.Server {
|
||||
serviceMgr.StartExposeReaper(context.Background())
|
||||
}
|
||||
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
||||
log.Info("ManagementService registered on gRPC server (agent-network setup RPC available)")
|
||||
|
||||
mgmtProto.RegisterProxyServiceServer(gRPCAPIHandler, s.ReverseProxyGRPCServer())
|
||||
log.Info("ProxyService registered on gRPC server")
|
||||
|
||||
89
shared/management/client/agentnetwork_test.go
Normal file
89
shared/management/client/agentnetwork_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user