diff --git a/client/cmd/agentnetwork.go b/client/cmd/agentnetwork.go index cbd3d5610..7c9d136a6 100644 --- a/client/cmd/agentnetwork.go +++ b/client/cmd/agentnetwork.go @@ -1,22 +1,15 @@ package cmd import ( - "context" - "errors" "fmt" - "io/fs" "strings" - "time" "github.com/spf13/cobra" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" - "github.com/netbirdio/netbird/client/internal/profilemanager" - mgm "github.com/netbirdio/netbird/shared/management/client" - mgmProto "github.com/netbirdio/netbird/shared/management/proto" + "github.com/netbirdio/netbird/client/proto" ) // agentNetworkAuthToken is the placeholder credential exported for @@ -26,8 +19,9 @@ import ( const agentNetworkAuthToken = "netbird" var ( - agentNetworkModelFlag string - agentNetworkJSONFlag bool + agentNetworkProviderFlag string + agentNetworkModelFlag string + agentNetworkJSONFlag bool ) var agentNetworkCmd = &cobra.Command{ @@ -48,88 +42,48 @@ var agentNetworkLsCmd = &cobra.Command{ var agentNetworkEnvCmd = &cobra.Command{ Use: "env", Short: "Print shell export lines that point AI tools at the Agent Network", - Long: `Print POSIX shell export lines (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, and -ANTHROPIC_MODEL when unambiguous) that configure Anthropic-compatible AI tools, such as -Claude Code, to use the Agent Network proxy. Apply them to the current shell with: + Long: `Print POSIX shell export lines that configure AI tools to use the Agent Network proxy. +The variables depend on the provider's API shape — Anthropic API, AWS Bedrock, Google Vertex AI, +and OpenAI-compatible providers each get the environment their tools expect (for Claude Code, +following its LLM-gateway configuration). Apply them to the current shell with: eval "$(netbird agent-network env)" -When several models are allowed, none is exported — pass --model to pin one.`, - Example: " eval \"$(netbird agent-network env)\"\n eval \"$(netbird agent-network env --model claude-sonnet-4-5)\"", +When several providers are authorized, pass --provider to pick one; when several models are +allowed, pass --model to pin one — nothing is ever guessed.`, + Example: " eval \"$(netbird agent-network env)\"\n eval \"$(netbird agent-network env --provider 'Bedrock prod' --model anthropic.claude-sonnet-4-5)\"", RunE: agentNetworkEnv, } func init() { agentNetworkLsCmd.PersistentFlags().BoolVar(&agentNetworkJSONFlag, "json", false, "output the setup as JSON") - agentNetworkEnvCmd.PersistentFlags().StringVar(&agentNetworkModelFlag, "model", "", "model to export as ANTHROPIC_MODEL (required when several models are allowed)") + agentNetworkEnvCmd.PersistentFlags().StringVar(&agentNetworkProviderFlag, "provider", "", "provider to configure, by name or catalog id (required when several are authorized)") + agentNetworkEnvCmd.PersistentFlags().StringVar(&agentNetworkModelFlag, "model", "", "model to export (required when several models are allowed)") } -// fetchAgentNetworkSetup dials the management server directly with the -// active profile's WireGuard key — the same credential and path every -// other peer RPC uses — and asks for the caller-scoped setup. No daemon -// involvement: the request is read-only and needs no tunnel state. -func fetchAgentNetworkSetup(ctx context.Context) (*mgmProto.AgentNetworkSetupResponse, error) { - pm := profilemanager.NewProfileManager() - activeProf, err := pm.GetActiveProfile() +// fetchAgentNetworkSetup asks the daemon for the caller-scoped Agent +// Network setup. The daemon relays the request to management over its +// existing peer connection, so no elevated permissions are needed. +func fetchAgentNetworkSetup(cmd *cobra.Command) (*proto.GetAgentNetworkSetupResponse, error) { + conn, err := getClient(cmd) if err != nil { - return nil, fmt.Errorf("get active profile: %v", err) - } - if activeProf == nil { - return nil, fmt.Errorf("active profile not found, please run 'netbird up' first") + return nil, err } + defer conn.Close() - configFilePath, err := activeProf.FilePath() + client := proto.NewDaemonServiceClient(conn) + setup, err := client.GetAgentNetworkSetup(cmd.Context(), &proto.GetAgentNetworkSetupRequest{}) if err != nil { - return nil, fmt.Errorf("get active profile file path: %v", err) - } - config, err := profilemanager.ReadConfig(configFilePath) - if err != nil { - // The default profile config (and its WireGuard key) is owned by - // root; dialing management directly therefore needs the same - // elevation the daemon has. Point at sudo instead of surfacing a - // bare permission error. - if errors.Is(err, fs.ErrPermission) { - return nil, fmt.Errorf("reading profile %s requires elevated permissions — re-run with sudo", configFilePath) + if s, ok := status.FromError(err); ok && s.Code() == codes.Unimplemented { + return nil, fmt.Errorf("the running daemon does not support agent-network commands — update the NetBird daemon and restart the service") } - return nil, fmt.Errorf("read config file %s: %v (run 'netbird up' first)", configFilePath, err) - } - - privateKey, err := wgtypes.ParseKey(config.PrivateKey) - if err != nil { - return nil, fmt.Errorf("parse profile private key: %v", err) - } - - mgmCtx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() - - tlsEnabled := config.ManagementURL.Scheme == "https" - mgmClient, err := mgm.NewClient(mgmCtx, config.ManagementURL.Host, privateKey, tlsEnabled) - if err != nil { - return nil, fmt.Errorf("connect to management service %s: %v", config.ManagementURL.String(), err) - } - defer func() { - _ = mgmClient.Close() - }() - - setup, err := mgmClient.GetAgentNetworkSetup(mgmCtx) - if err != nil { - 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 (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 from %s: %v", config.ManagementURL.String(), err) + return nil, fmt.Errorf("get agent network setup: %v", status.Convert(err).Message()) } return setup, nil } func agentNetworkLs(cmd *cobra.Command, _ []string) error { - setup, err := fetchAgentNetworkSetup(cmd.Context()) + setup, err := fetchAgentNetworkSetup(cmd) if err != nil { return err } @@ -165,7 +119,7 @@ func agentNetworkLs(cmd *cobra.Command, _ []string) error { } } cmd.Println() - cmd.Println("To configure Anthropic-compatible tools in the current shell: eval \"$(netbird agent-network env)\"") + cmd.Println("To configure an AI tool in the current shell: eval \"$(netbird agent-network env)\"") return nil } @@ -179,7 +133,7 @@ func printModels(cmd *cobra.Command, models []string) { } } -func providerFlavorLabel(p *mgmProto.AgentNetworkProviderInfo) string { +func providerFlavorLabel(p *proto.AgentNetworkProvider) string { if p.ApiFlavor == "" { return p.CatalogId } @@ -187,7 +141,7 @@ func providerFlavorLabel(p *mgmProto.AgentNetworkProviderInfo) string { } func agentNetworkEnv(cmd *cobra.Command, _ []string) error { - setup, err := fetchAgentNetworkSetup(cmd.Context()) + setup, err := fetchAgentNetworkSetup(cmd) if err != nil { return err } @@ -199,76 +153,168 @@ func agentNetworkEnv(cmd *cobra.Command, _ []string) error { return nil } - cmd.Printf("export ANTHROPIC_BASE_URL=%s\n", shellQuote(setup.Endpoint)) - cmd.Printf("export ANTHROPIC_AUTH_TOKEN=%s\n", shellQuote(agentNetworkAuthToken)) - - model, note, err := resolveAgentNetworkModel(setup, agentNetworkModelFlag) + lines, err := buildAgentNetworkEnv(setup, agentNetworkProviderFlag, agentNetworkModelFlag) if err != nil { return err } - if model != "" { - cmd.Printf("export ANTHROPIC_MODEL=%s\n", shellQuote(model)) - } - for _, line := range note { - cmd.Printf("# %s\n", sanitizeOutput(line)) + for _, line := range lines { + cmd.Println(line) } return nil } -// resolveAgentNetworkModel picks the model to export from the -// Anthropic-flavor providers' effective model sets. A model is never -// guessed: --model wins (validated against the allowed set), a single -// allowed model is used, and anything ambiguous is returned as comment -// lines instead of an export. -func resolveAgentNetworkModel(setup *mgmProto.AgentNetworkSetupResponse, flagModel string) (string, []string, error) { - allowAny := false - var models []string - seen := make(map[string]struct{}) - for _, p := range setup.Providers { - if p.ApiFlavor != "anthropic" { - continue +// buildAgentNetworkEnv renders the export lines for one selected +// provider. Nothing is guessed: an ambiguous provider or model choice +// comes back as comment lines instead of exports, and an invalid +// --provider/--model is an error. +func buildAgentNetworkEnv(setup *proto.GetAgentNetworkSetupResponse, providerFlag, modelFlag string) ([]string, error) { + provider, choiceLines, err := selectAgentNetworkProvider(setup.Providers, providerFlag) + if err != nil { + return nil, err + } + if provider == nil { + return choiceLines, nil + } + + model, modelNotes, err := resolveAgentNetworkModel(provider, modelFlag) + if err != nil { + return nil, err + } + + var lines []string + switch { + case provider.CatalogId == "bedrock_api": + // Claude Code's Bedrock-format gateway configuration: the proxy + // routes native Bedrock paths and injects the AWS credentials, so + // client-side signing is skipped. + lines = append(lines, + exportLine("CLAUDE_CODE_USE_BEDROCK", "1"), + exportLine("ANTHROPIC_BEDROCK_BASE_URL", setup.Endpoint), + exportLine("CLAUDE_CODE_SKIP_BEDROCK_AUTH", "1"), + ) + if model != "" { + lines = append(lines, exportLine("ANTHROPIC_MODEL", model)) } - if p.AllModelsAllowed { - allowAny = true + case provider.CatalogId == "vertex_ai_api": + // Claude Code's Vertex-format gateway configuration. Vertex + // requests carry the GCP project and region in the URL path, which + // the proxy forwards to the upstream — those two values belong to + // the operator's GCP setup and must come from the administrator. + lines = append(lines, + exportLine("CLAUDE_CODE_USE_VERTEX", "1"), + exportLine("ANTHROPIC_VERTEX_BASE_URL", setup.Endpoint), + exportLine("CLAUDE_CODE_SKIP_VERTEX_AUTH", "1"), + ) + if model != "" { + lines = append(lines, exportLine("ANTHROPIC_MODEL", model)) } - for _, m := range p.Models { - key := strings.ToLower(strings.TrimSpace(m)) - if key == "" { - continue + lines = append(lines, + comment("Vertex requests carry your operator's GCP project and region in the URL."), + comment("Ask your administrator for the values, then export:"), + comment(" export ANTHROPIC_VERTEX_PROJECT_ID="), + comment(" export CLOUD_ML_REGION="), + ) + case provider.ApiFlavor == "anthropic": + lines = append(lines, + exportLine("ANTHROPIC_BASE_URL", setup.Endpoint), + exportLine("ANTHROPIC_AUTH_TOKEN", agentNetworkAuthToken), + ) + if model != "" { + lines = append(lines, exportLine("ANTHROPIC_MODEL", model)) + } + case provider.ApiFlavor == "openai": + lines = append(lines, + exportLine("OPENAI_BASE_URL", setup.Endpoint), + exportLine("OPENAI_API_KEY", agentNetworkAuthToken), + ) + if model != "" { + lines = append(lines, comment(fmt.Sprintf("Configure your tool to use model %s.", model))) + } + default: + lines = append(lines, + comment(fmt.Sprintf("Provider %s (%s) is dispatched by URL path; no standard environment", provider.Name, provider.CatalogId)), + comment("variables apply. Point your tool at the endpoint below (auth token: netbird):"), + comment(" "+setup.Endpoint), + ) + } + + for _, note := range modelNotes { + lines = append(lines, comment(note)) + } + return lines, nil +} + +// selectAgentNetworkProvider picks the provider to configure. An +// explicit --provider matches the operator label or catalog id +// (case-insensitive); with no flag a single authorized provider is +// used, and several come back as comment lines asking for the flag. +func selectAgentNetworkProvider(providers []*proto.AgentNetworkProvider, providerFlag string) (*proto.AgentNetworkProvider, []string, error) { + if providerFlag != "" { + wanted := strings.ToLower(strings.TrimSpace(providerFlag)) + for _, p := range providers { + if strings.ToLower(strings.TrimSpace(p.Name)) == wanted || strings.ToLower(p.CatalogId) == wanted { + return p, nil, nil } - if _, dup := seen[key]; dup { - continue + } + names := make([]string, 0, len(providers)) + for _, p := range providers { + names = append(names, fmt.Sprintf("%s (%s)", p.Name, p.CatalogId)) + } + return nil, nil, fmt.Errorf("provider %q is not authorized for this peer — available: %s", providerFlag, strings.Join(names, ", ")) + } + + if len(providers) == 1 { + return providers[0], nil, nil + } + + lines := []string{comment("Multiple providers are authorized — none configured. Re-run with --provider to pick one:")} + for _, p := range providers { + lines = append(lines, comment(fmt.Sprintf(" netbird agent-network env --provider %q (%s)", p.Name, providerFlavorLabel(p)))) + } + return nil, lines, nil +} + +// resolveAgentNetworkModel picks the model for the selected provider. A +// model is never guessed: --model wins (validated against the allowed +// set), a single allowed model is used, and anything ambiguous is +// returned as note lines instead. +func resolveAgentNetworkModel(provider *proto.AgentNetworkProvider, modelFlag string) (string, []string, error) { + if modelFlag != "" { + if provider.AllModelsAllowed { + return modelFlag, nil, nil + } + wanted := strings.ToLower(strings.TrimSpace(modelFlag)) + for _, m := range provider.Models { + if strings.ToLower(strings.TrimSpace(m)) == wanted { + return modelFlag, nil, nil } - seen[key] = struct{}{} - models = append(models, strings.TrimSpace(m)) } + return "", nil, fmt.Errorf("model %q is not allowed on provider %s — run 'netbird agent-network ls' to see the allowed models", modelFlag, provider.Name) } - if flagModel != "" { - if allowAny { - return flagModel, nil, nil - } - if _, ok := seen[strings.ToLower(strings.TrimSpace(flagModel))]; !ok { - return "", nil, fmt.Errorf("model %q is not in the allowed model list — run 'netbird agent-network ls' to see it", flagModel) - } - return flagModel, nil, nil + if len(provider.Models) == 1 && !provider.AllModelsAllowed { + return provider.Models[0], nil, nil + } + if len(provider.Models) == 0 && provider.AllModelsAllowed { + return "", []string{"Any model is allowed; pass --model to pin one."}, nil } - if len(models) == 0 && !allowAny { - return "", []string{"No Anthropic-flavor provider is authorized for this peer; ANTHROPIC_MODEL not exported."}, nil + notes := []string{"Multiple models are allowed — none exported. Re-run with --model to pin one:"} + for _, m := range provider.Models { + notes = append(notes, " "+m) } - if len(models) == 1 && !allowAny { - return models[0], nil, nil + if provider.AllModelsAllowed { + notes = append(notes, " (any other model the provider serves)") } + return "", notes, nil +} - note := []string{"Multiple models are allowed — none exported. Re-run with --model to pin one:"} - for _, m := range models { - note = append(note, " "+m) - } - if allowAny { - note = append(note, " (any other model the provider serves)") - } - return "", note, nil +func exportLine(name, value string) string { + return fmt.Sprintf("export %s=%s", name, shellQuote(value)) +} + +func comment(text string) string { + return "# " + sanitizeOutput(text) } // shellQuote single-quotes a value for safe use in an eval'd export diff --git a/client/cmd/agentnetwork_test.go b/client/cmd/agentnetwork_test.go new file mode 100644 index 000000000..391074129 --- /dev/null +++ b/client/cmd/agentnetwork_test.go @@ -0,0 +1,165 @@ +package cmd + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/client/proto" +) + +func anTestSetup(providers ...*proto.AgentNetworkProvider) *proto.GetAgentNetworkSetupResponse { + return &proto.GetAgentNetworkSetupResponse{ + Configured: true, + Endpoint: "https://calm-otter.proxy.example.com", + Providers: providers, + } +} + +func TestBuildAgentNetworkEnv_AnthropicSingleModel(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Anthropic prod", CatalogId: "anthropic_api", ApiFlavor: "anthropic", + Models: []string{"claude-sonnet-4-5"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + assert.Equal(t, []string{ + "export ANTHROPIC_BASE_URL='https://calm-otter.proxy.example.com'", + "export ANTHROPIC_AUTH_TOKEN='netbird'", + "export ANTHROPIC_MODEL='claude-sonnet-4-5'", + }, lines) +} + +func TestBuildAgentNetworkEnv_MultipleModelsBecomeComments(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Anthropic prod", CatalogId: "anthropic_api", ApiFlavor: "anthropic", + Models: []string{"claude-sonnet-4-5", "claude-haiku-4-5"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + assert.Contains(t, lines, "export ANTHROPIC_BASE_URL='https://calm-otter.proxy.example.com'") + assert.NotContains(t, strings.Join(lines, "\n"), "ANTHROPIC_MODEL=", "no model is ever guessed") + assert.Contains(t, strings.Join(lines, "\n"), "# Multiple models are allowed") +} + +func TestBuildAgentNetworkEnv_ModelFlagValidated(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Anthropic prod", CatalogId: "anthropic_api", ApiFlavor: "anthropic", + Models: []string{"claude-sonnet-4-5", "claude-haiku-4-5"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "Claude-Haiku-4-5") + require.NoError(t, err, "model match is case-insensitive") + assert.Contains(t, lines, "export ANTHROPIC_MODEL='Claude-Haiku-4-5'") + + _, err = buildAgentNetworkEnv(setup, "", "gpt-4o") + require.Error(t, err, "a model outside the allowlist is rejected") +} + +func TestBuildAgentNetworkEnv_BedrockFlavor(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Bedrock prod", CatalogId: "bedrock_api", ApiFlavor: "", + Models: []string{"anthropic.claude-sonnet-4-5"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + assert.Equal(t, []string{ + "export CLAUDE_CODE_USE_BEDROCK='1'", + "export ANTHROPIC_BEDROCK_BASE_URL='https://calm-otter.proxy.example.com'", + "export CLAUDE_CODE_SKIP_BEDROCK_AUTH='1'", + "export ANTHROPIC_MODEL='anthropic.claude-sonnet-4-5'", + }, lines) +} + +func TestBuildAgentNetworkEnv_VertexFlavorNotesProjectAndRegion(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Vertex prod", CatalogId: "vertex_ai_api", ApiFlavor: "", + Models: []string{"claude-sonnet-4-5"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + joined := strings.Join(lines, "\n") + assert.Contains(t, lines, "export CLAUDE_CODE_USE_VERTEX='1'") + assert.Contains(t, lines, "export ANTHROPIC_VERTEX_BASE_URL='https://calm-otter.proxy.example.com'") + assert.Contains(t, lines, "export CLAUDE_CODE_SKIP_VERTEX_AUTH='1'") + assert.Contains(t, lines, "export ANTHROPIC_MODEL='claude-sonnet-4-5'") + assert.Contains(t, joined, "ANTHROPIC_VERTEX_PROJECT_ID", "project id must be called out as admin-supplied") + assert.Contains(t, joined, "CLOUD_ML_REGION", "region must be called out as admin-supplied") +} + +func TestBuildAgentNetworkEnv_OpenAIFlavor(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "OpenAI prod", CatalogId: "openai_api", ApiFlavor: "openai", + Models: []string{"gpt-5.4"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + assert.Contains(t, lines, "export OPENAI_BASE_URL='https://calm-otter.proxy.example.com'") + assert.Contains(t, lines, "export OPENAI_API_KEY='netbird'") + assert.NotContains(t, strings.Join(lines, "\n"), "ANTHROPIC_", "openai flavor must not emit anthropic variables") +} + +func TestBuildAgentNetworkEnv_MultipleProvidersRequireFlag(t *testing.T) { + anthropic := &proto.AgentNetworkProvider{Name: "Anthropic prod", CatalogId: "anthropic_api", ApiFlavor: "anthropic", Models: []string{"claude-sonnet-4-5"}} + bedrock := &proto.AgentNetworkProvider{Name: "Bedrock prod", CatalogId: "bedrock_api", Models: []string{"anthropic.claude-sonnet-4-5"}} + setup := anTestSetup(anthropic, bedrock) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + joined := strings.Join(lines, "\n") + assert.NotContains(t, joined, "export ", "ambiguous provider choice must export nothing") + assert.Contains(t, joined, "--provider") + assert.Contains(t, joined, "Bedrock prod") + + // Selection by operator label, case-insensitive. + lines, err = buildAgentNetworkEnv(setup, "bedrock prod", "") + require.NoError(t, err) + assert.Contains(t, lines, "export CLAUDE_CODE_USE_BEDROCK='1'") + + // Selection by catalog id. + lines, err = buildAgentNetworkEnv(setup, "anthropic_api", "") + require.NoError(t, err) + assert.Contains(t, lines, "export ANTHROPIC_AUTH_TOKEN='netbird'") + + // Unknown provider is an error naming the available ones. + _, err = buildAgentNetworkEnv(setup, "vertex", "") + require.Error(t, err) + assert.Contains(t, err.Error(), "Anthropic prod") +} + +func TestBuildAgentNetworkEnv_AllModelsAllowed(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Anthropic prod", CatalogId: "anthropic_api", ApiFlavor: "anthropic", + AllModelsAllowed: true, Models: []string{"claude-sonnet-4-5"}, + }) + + // A courtesy-listed single model is still ambiguous when everything is allowed. + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + assert.NotContains(t, strings.Join(lines, "\n"), "ANTHROPIC_MODEL=") + + // --model passes without allowlist validation. + lines, err = buildAgentNetworkEnv(setup, "", "claude-opus-4-8") + require.NoError(t, err) + assert.Contains(t, lines, "export ANTHROPIC_MODEL='claude-opus-4-8'") +} + +func TestBuildAgentNetworkEnv_UnknownFlavorFallsBackToComments(t *testing.T) { + setup := anTestSetup(&proto.AgentNetworkProvider{ + Name: "Kimi", CatalogId: "kimi_api", ApiFlavor: "", + Models: []string{"kimi-k3"}, + }) + + lines, err := buildAgentNetworkEnv(setup, "", "") + require.NoError(t, err) + joined := strings.Join(lines, "\n") + assert.NotContains(t, joined, "export ", "unknown API shape must not guess variables") + assert.Contains(t, joined, "https://calm-otter.proxy.example.com") +} diff --git a/client/internal/engine.go b/client/internal/engine.go index f4f47992f..5d3550c39 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -2200,6 +2200,19 @@ func (e *Engine) GetExposeManager() *expose.Manager { return e.exposeManager } +// GetAgentNetworkSetup asks the management server for the Agent Network +// connection info this peer's groups authorize, over the engine's +// existing management connection. +func (e *Engine) GetAgentNetworkSetup(ctx context.Context) (*mgmProto.AgentNetworkSetupResponse, error) { + e.syncMsgMux.Lock() + mgmClient := e.mgmClient + e.syncMsgMux.Unlock() + if mgmClient == nil { + return nil, errors.New("management client not available") + } + return mgmClient.GetAgentNetworkSetup(ctx) +} + // IsBlockInbound returns whether inbound connections are blocked. func (e *Engine) IsBlockInbound() bool { return e.config.BlockInbound diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index d4deeb8ec..858c09cdb 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.6 -// protoc v6.33.1 +// protoc v7.34.1 // source: daemon.proto package proto @@ -6941,6 +6941,190 @@ func (*StopBundleCaptureResponse) Descriptor() ([]byte, []int) { return file_daemon_proto_rawDescGZIP(), []int{106} } +type GetAgentNetworkSetupRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentNetworkSetupRequest) Reset() { + *x = GetAgentNetworkSetupRequest{} + mi := &file_daemon_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentNetworkSetupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentNetworkSetupRequest) ProtoMessage() {} + +func (x *GetAgentNetworkSetupRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[107] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentNetworkSetupRequest.ProtoReflect.Descriptor instead. +func (*GetAgentNetworkSetupRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{107} +} + +type GetAgentNetworkSetupResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // configured is false when the account has no Agent Network set up or + // this peer's groups authorize none of it. + Configured bool `protobuf:"varint,1,opt,name=configured,proto3" json:"configured,omitempty"` + // endpoint is the account's proxy base URL, reachable over the NetBird + // tunnel only. Empty when configured is false. + Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Providers []*AgentNetworkProvider `protobuf:"bytes,3,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentNetworkSetupResponse) Reset() { + *x = GetAgentNetworkSetupResponse{} + mi := &file_daemon_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentNetworkSetupResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentNetworkSetupResponse) ProtoMessage() {} + +func (x *GetAgentNetworkSetupResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[108] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentNetworkSetupResponse.ProtoReflect.Descriptor instead. +func (*GetAgentNetworkSetupResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{108} +} + +func (x *GetAgentNetworkSetupResponse) GetConfigured() bool { + if x != nil { + return x.Configured + } + return false +} + +func (x *GetAgentNetworkSetupResponse) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *GetAgentNetworkSetupResponse) GetProviders() []*AgentNetworkProvider { + if x != nil { + return x.Providers + } + return nil +} + +type AgentNetworkProvider struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name is the operator-assigned provider label, e.g. "Bedrock prod". + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // catalog_id names the provider type, e.g. "anthropic_api", "bedrock_api". + CatalogId string `protobuf:"bytes,2,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty"` + // api_flavor is the request-body shape the provider speaks ("anthropic", + // "openai"); empty when the proxy dispatches it by URL path instead. + ApiFlavor string `protobuf:"bytes,3,opt,name=api_flavor,json=apiFlavor,proto3" json:"api_flavor,omitempty"` + // all_models_allowed is true when no model allowlist restricts this + // provider for the caller. + AllModelsAllowed bool `protobuf:"varint,4,opt,name=all_models_allowed,json=allModelsAllowed,proto3" json:"all_models_allowed,omitempty"` + // models is the effective model allowlist (or the declared/catalog models + // when all_models_allowed is true). + Models []string `protobuf:"bytes,5,rep,name=models,proto3" json:"models,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentNetworkProvider) Reset() { + *x = AgentNetworkProvider{} + mi := &file_daemon_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentNetworkProvider) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentNetworkProvider) ProtoMessage() {} + +func (x *AgentNetworkProvider) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[109] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentNetworkProvider.ProtoReflect.Descriptor instead. +func (*AgentNetworkProvider) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{109} +} + +func (x *AgentNetworkProvider) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AgentNetworkProvider) GetCatalogId() string { + if x != nil { + return x.CatalogId + } + return "" +} + +func (x *AgentNetworkProvider) GetApiFlavor() string { + if x != nil { + return x.ApiFlavor + } + return "" +} + +func (x *AgentNetworkProvider) GetAllModelsAllowed() bool { + if x != nil { + return x.AllModelsAllowed + } + return false +} + +func (x *AgentNetworkProvider) GetModels() []string { + if x != nil { + return x.Models + } + return nil +} + type PortInfo_Range struct { state protoimpl.MessageState `protogen:"open.v1"` Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` @@ -6951,7 +7135,7 @@ type PortInfo_Range struct { func (x *PortInfo_Range) Reset() { *x = PortInfo_Range{} - mi := &file_daemon_proto_msgTypes[108] + mi := &file_daemon_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6963,7 +7147,7 @@ func (x *PortInfo_Range) String() string { func (*PortInfo_Range) ProtoMessage() {} func (x *PortInfo_Range) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[108] + mi := &file_daemon_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7577,7 +7761,22 @@ const file_daemon_proto_rawDesc = "" + "\atimeout\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\atimeout\"\x1c\n" + "\x1aStartBundleCaptureResponse\"\x1a\n" + "\x18StopBundleCaptureRequest\"\x1b\n" + - "\x19StopBundleCaptureResponse*b\n" + + "\x19StopBundleCaptureResponse\"\x1d\n" + + "\x1bGetAgentNetworkSetupRequest\"\x96\x01\n" + + "\x1cGetAgentNetworkSetupResponse\x12\x1e\n" + + "\n" + + "configured\x18\x01 \x01(\bR\n" + + "configured\x12\x1a\n" + + "\bendpoint\x18\x02 \x01(\tR\bendpoint\x12:\n" + + "\tproviders\x18\x03 \x03(\v2\x1c.daemon.AgentNetworkProviderR\tproviders\"\xae\x01\n" + + "\x14AgentNetworkProvider\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" + + "\n" + + "catalog_id\x18\x02 \x01(\tR\tcatalogId\x12\x1d\n" + + "\n" + + "api_flavor\x18\x03 \x01(\tR\tapiFlavor\x12,\n" + + "\x12all_models_allowed\x18\x04 \x01(\bR\x10allModelsAllowed\x12\x16\n" + + "\x06models\x18\x05 \x03(\tR\x06models*b\n" + "\bLogLevel\x12\v\n" + "\aUNKNOWN\x10\x00\x12\t\n" + "\x05PANIC\x10\x01\x12\t\n" + @@ -7595,7 +7794,7 @@ const file_daemon_proto_rawDesc = "" + "\n" + "EXPOSE_UDP\x10\x03\x12\x0e\n" + "\n" + - "EXPOSE_TLS\x10\x042\xa3\x1c\n" + + "EXPOSE_TLS\x10\x042\x88\x1d\n" + "\rDaemonService\x126\n" + "\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" + "\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" + @@ -7645,7 +7844,8 @@ const file_daemon_proto_rawDesc = "" + "\x0eStopCPUProfile\x12\x1d.daemon.StopCPUProfileRequest\x1a\x1e.daemon.StopCPUProfileResponse\"\x00\x12W\n" + "\x12GetInstallerResult\x12\x1e.daemon.InstallerResultRequest\x1a\x1f.daemon.InstallerResultResponse\"\x00\x12M\n" + "\rExposeService\x12\x1c.daemon.ExposeServiceRequest\x1a\x1a.daemon.ExposeServiceEvent\"\x000\x01\x12K\n" + - "\fWailsUIReady\x12\x1b.daemon.WailsUIReadyRequest\x1a\x1c.daemon.WailsUIReadyResponse\"\x00B\bZ\x06/protob\x06proto3" + "\fWailsUIReady\x12\x1b.daemon.WailsUIReadyRequest\x1a\x1c.daemon.WailsUIReadyResponse\"\x00\x12c\n" + + "\x14GetAgentNetworkSetup\x12#.daemon.GetAgentNetworkSetupRequest\x1a$.daemon.GetAgentNetworkSetupResponse\"\x00B\bZ\x06/protob\x06proto3" var ( file_daemon_proto_rawDescOnce sync.Once @@ -7660,7 +7860,7 @@ func file_daemon_proto_rawDescGZIP() []byte { } var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 110) +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 113) var file_daemon_proto_goTypes = []any{ (LogLevel)(0), // 0: daemon.LogLevel (ExposeProtocol)(0), // 1: daemon.ExposeProtocol @@ -7773,19 +7973,22 @@ var file_daemon_proto_goTypes = []any{ (*StartBundleCaptureResponse)(nil), // 108: daemon.StartBundleCaptureResponse (*StopBundleCaptureRequest)(nil), // 109: daemon.StopBundleCaptureRequest (*StopBundleCaptureResponse)(nil), // 110: daemon.StopBundleCaptureResponse - nil, // 111: daemon.Network.ResolvedIPsEntry - (*PortInfo_Range)(nil), // 112: daemon.PortInfo.Range - nil, // 113: daemon.SystemEvent.MetadataEntry - (*durationpb.Duration)(nil), // 114: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 115: google.protobuf.Timestamp + (*GetAgentNetworkSetupRequest)(nil), // 111: daemon.GetAgentNetworkSetupRequest + (*GetAgentNetworkSetupResponse)(nil), // 112: daemon.GetAgentNetworkSetupResponse + (*AgentNetworkProvider)(nil), // 113: daemon.AgentNetworkProvider + nil, // 114: daemon.Network.ResolvedIPsEntry + (*PortInfo_Range)(nil), // 115: daemon.PortInfo.Range + nil, // 116: daemon.SystemEvent.MetadataEntry + (*durationpb.Duration)(nil), // 117: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 118: google.protobuf.Timestamp } var file_daemon_proto_depIdxs = []int32{ - 114, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration + 117, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration 25, // 1: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus - 115, // 2: daemon.StatusResponse.sessionExpiresAt:type_name -> google.protobuf.Timestamp - 115, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp - 115, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp - 114, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration + 118, // 2: daemon.StatusResponse.sessionExpiresAt:type_name -> google.protobuf.Timestamp + 118, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp + 118, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp + 117, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration 23, // 6: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo 20, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState 19, // 8: daemon.FullStatus.signalState:type_name -> daemon.SignalState @@ -7796,8 +7999,8 @@ var file_daemon_proto_depIdxs = []int32{ 57, // 13: daemon.FullStatus.events:type_name -> daemon.SystemEvent 24, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState 31, // 15: daemon.ListNetworksResponse.routes:type_name -> daemon.Network - 111, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry - 112, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range + 114, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry + 115, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range 32, // 18: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo 32, // 19: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo 33, // 20: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule @@ -7808,114 +8011,117 @@ var file_daemon_proto_depIdxs = []int32{ 54, // 25: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage 2, // 26: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity 3, // 27: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category - 115, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp - 113, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry + 118, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp + 116, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry 57, // 30: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent - 114, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration + 117, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration 72, // 32: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile - 115, // 33: daemon.WaitExtendAuthSessionResponse.sessionExpiresAt:type_name -> google.protobuf.Timestamp + 118, // 33: daemon.WaitExtendAuthSessionResponse.sessionExpiresAt:type_name -> google.protobuf.Timestamp 1, // 34: daemon.ExposeServiceRequest.protocol:type_name -> daemon.ExposeProtocol 104, // 35: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady - 114, // 36: daemon.StartCaptureRequest.duration:type_name -> google.protobuf.Duration - 114, // 37: daemon.StartBundleCaptureRequest.timeout:type_name -> google.protobuf.Duration - 30, // 38: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList - 5, // 39: daemon.DaemonService.Login:input_type -> daemon.LoginRequest - 7, // 40: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest - 9, // 41: daemon.DaemonService.Up:input_type -> daemon.UpRequest - 11, // 42: daemon.DaemonService.Status:input_type -> daemon.StatusRequest - 11, // 43: daemon.DaemonService.SubscribeStatus:input_type -> daemon.StatusRequest - 13, // 44: daemon.DaemonService.Down:input_type -> daemon.DownRequest - 15, // 45: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest - 26, // 46: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest - 28, // 47: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest - 28, // 48: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest - 4, // 49: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest - 35, // 50: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest - 37, // 51: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest - 39, // 52: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest - 44, // 53: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest - 46, // 54: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest - 48, // 55: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest - 50, // 56: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest - 53, // 57: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest - 105, // 58: daemon.DaemonService.StartCapture:input_type -> daemon.StartCaptureRequest - 107, // 59: daemon.DaemonService.StartBundleCapture:input_type -> daemon.StartBundleCaptureRequest - 109, // 60: daemon.DaemonService.StopBundleCapture:input_type -> daemon.StopBundleCaptureRequest - 56, // 61: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest - 58, // 62: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest - 41, // 63: daemon.DaemonService.RegisterUILog:input_type -> daemon.RegisterUILogRequest - 60, // 64: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest - 62, // 65: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest - 64, // 66: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest - 66, // 67: daemon.DaemonService.RenameProfile:input_type -> daemon.RenameProfileRequest - 68, // 68: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest - 70, // 69: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest - 73, // 70: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest - 75, // 71: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest - 79, // 72: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest - 82, // 73: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest - 84, // 74: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest - 86, // 75: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest - 88, // 76: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest - 90, // 77: daemon.DaemonService.RequestExtendAuthSession:input_type -> daemon.RequestExtendAuthSessionRequest - 92, // 78: daemon.DaemonService.WaitExtendAuthSession:input_type -> daemon.WaitExtendAuthSessionRequest - 94, // 79: daemon.DaemonService.DismissSessionWarning:input_type -> daemon.DismissSessionWarningRequest - 96, // 80: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest - 98, // 81: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest - 100, // 82: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest - 102, // 83: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest - 77, // 84: daemon.DaemonService.WailsUIReady:input_type -> daemon.WailsUIReadyRequest - 6, // 85: daemon.DaemonService.Login:output_type -> daemon.LoginResponse - 8, // 86: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse - 10, // 87: daemon.DaemonService.Up:output_type -> daemon.UpResponse - 12, // 88: daemon.DaemonService.Status:output_type -> daemon.StatusResponse - 12, // 89: daemon.DaemonService.SubscribeStatus:output_type -> daemon.StatusResponse - 14, // 90: daemon.DaemonService.Down:output_type -> daemon.DownResponse - 16, // 91: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse - 27, // 92: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse - 29, // 93: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse - 29, // 94: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse - 34, // 95: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse - 36, // 96: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse - 38, // 97: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse - 40, // 98: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse - 45, // 99: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse - 47, // 100: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse - 49, // 101: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse - 51, // 102: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse - 55, // 103: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse - 106, // 104: daemon.DaemonService.StartCapture:output_type -> daemon.CapturePacket - 108, // 105: daemon.DaemonService.StartBundleCapture:output_type -> daemon.StartBundleCaptureResponse - 110, // 106: daemon.DaemonService.StopBundleCapture:output_type -> daemon.StopBundleCaptureResponse - 57, // 107: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent - 59, // 108: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse - 42, // 109: daemon.DaemonService.RegisterUILog:output_type -> daemon.RegisterUILogResponse - 61, // 110: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse - 63, // 111: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse - 65, // 112: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse - 67, // 113: daemon.DaemonService.RenameProfile:output_type -> daemon.RenameProfileResponse - 69, // 114: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse - 71, // 115: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse - 74, // 116: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse - 76, // 117: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse - 80, // 118: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse - 83, // 119: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse - 85, // 120: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse - 87, // 121: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse - 89, // 122: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse - 91, // 123: daemon.DaemonService.RequestExtendAuthSession:output_type -> daemon.RequestExtendAuthSessionResponse - 93, // 124: daemon.DaemonService.WaitExtendAuthSession:output_type -> daemon.WaitExtendAuthSessionResponse - 95, // 125: daemon.DaemonService.DismissSessionWarning:output_type -> daemon.DismissSessionWarningResponse - 97, // 126: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse - 99, // 127: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse - 101, // 128: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse - 103, // 129: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent - 78, // 130: daemon.DaemonService.WailsUIReady:output_type -> daemon.WailsUIReadyResponse - 85, // [85:131] is the sub-list for method output_type - 39, // [39:85] is the sub-list for method input_type - 39, // [39:39] is the sub-list for extension type_name - 39, // [39:39] is the sub-list for extension extendee - 0, // [0:39] is the sub-list for field type_name + 117, // 36: daemon.StartCaptureRequest.duration:type_name -> google.protobuf.Duration + 117, // 37: daemon.StartBundleCaptureRequest.timeout:type_name -> google.protobuf.Duration + 113, // 38: daemon.GetAgentNetworkSetupResponse.providers:type_name -> daemon.AgentNetworkProvider + 30, // 39: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList + 5, // 40: daemon.DaemonService.Login:input_type -> daemon.LoginRequest + 7, // 41: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest + 9, // 42: daemon.DaemonService.Up:input_type -> daemon.UpRequest + 11, // 43: daemon.DaemonService.Status:input_type -> daemon.StatusRequest + 11, // 44: daemon.DaemonService.SubscribeStatus:input_type -> daemon.StatusRequest + 13, // 45: daemon.DaemonService.Down:input_type -> daemon.DownRequest + 15, // 46: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest + 26, // 47: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest + 28, // 48: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest + 28, // 49: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest + 4, // 50: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest + 35, // 51: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest + 37, // 52: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest + 39, // 53: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest + 44, // 54: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest + 46, // 55: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest + 48, // 56: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest + 50, // 57: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest + 53, // 58: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest + 105, // 59: daemon.DaemonService.StartCapture:input_type -> daemon.StartCaptureRequest + 107, // 60: daemon.DaemonService.StartBundleCapture:input_type -> daemon.StartBundleCaptureRequest + 109, // 61: daemon.DaemonService.StopBundleCapture:input_type -> daemon.StopBundleCaptureRequest + 56, // 62: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest + 58, // 63: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest + 41, // 64: daemon.DaemonService.RegisterUILog:input_type -> daemon.RegisterUILogRequest + 60, // 65: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest + 62, // 66: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest + 64, // 67: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest + 66, // 68: daemon.DaemonService.RenameProfile:input_type -> daemon.RenameProfileRequest + 68, // 69: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest + 70, // 70: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest + 73, // 71: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest + 75, // 72: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest + 79, // 73: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest + 82, // 74: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest + 84, // 75: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest + 86, // 76: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest + 88, // 77: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest + 90, // 78: daemon.DaemonService.RequestExtendAuthSession:input_type -> daemon.RequestExtendAuthSessionRequest + 92, // 79: daemon.DaemonService.WaitExtendAuthSession:input_type -> daemon.WaitExtendAuthSessionRequest + 94, // 80: daemon.DaemonService.DismissSessionWarning:input_type -> daemon.DismissSessionWarningRequest + 96, // 81: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest + 98, // 82: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest + 100, // 83: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest + 102, // 84: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest + 77, // 85: daemon.DaemonService.WailsUIReady:input_type -> daemon.WailsUIReadyRequest + 111, // 86: daemon.DaemonService.GetAgentNetworkSetup:input_type -> daemon.GetAgentNetworkSetupRequest + 6, // 87: daemon.DaemonService.Login:output_type -> daemon.LoginResponse + 8, // 88: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse + 10, // 89: daemon.DaemonService.Up:output_type -> daemon.UpResponse + 12, // 90: daemon.DaemonService.Status:output_type -> daemon.StatusResponse + 12, // 91: daemon.DaemonService.SubscribeStatus:output_type -> daemon.StatusResponse + 14, // 92: daemon.DaemonService.Down:output_type -> daemon.DownResponse + 16, // 93: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse + 27, // 94: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse + 29, // 95: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse + 29, // 96: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse + 34, // 97: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse + 36, // 98: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse + 38, // 99: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse + 40, // 100: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse + 45, // 101: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse + 47, // 102: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse + 49, // 103: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse + 51, // 104: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse + 55, // 105: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse + 106, // 106: daemon.DaemonService.StartCapture:output_type -> daemon.CapturePacket + 108, // 107: daemon.DaemonService.StartBundleCapture:output_type -> daemon.StartBundleCaptureResponse + 110, // 108: daemon.DaemonService.StopBundleCapture:output_type -> daemon.StopBundleCaptureResponse + 57, // 109: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent + 59, // 110: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse + 42, // 111: daemon.DaemonService.RegisterUILog:output_type -> daemon.RegisterUILogResponse + 61, // 112: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse + 63, // 113: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse + 65, // 114: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse + 67, // 115: daemon.DaemonService.RenameProfile:output_type -> daemon.RenameProfileResponse + 69, // 116: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse + 71, // 117: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse + 74, // 118: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse + 76, // 119: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse + 80, // 120: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse + 83, // 121: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse + 85, // 122: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse + 87, // 123: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse + 89, // 124: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse + 91, // 125: daemon.DaemonService.RequestExtendAuthSession:output_type -> daemon.RequestExtendAuthSessionResponse + 93, // 126: daemon.DaemonService.WaitExtendAuthSession:output_type -> daemon.WaitExtendAuthSessionResponse + 95, // 127: daemon.DaemonService.DismissSessionWarning:output_type -> daemon.DismissSessionWarningResponse + 97, // 128: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse + 99, // 129: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse + 101, // 130: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse + 103, // 131: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent + 78, // 132: daemon.DaemonService.WailsUIReady:output_type -> daemon.WailsUIReadyResponse + 112, // 133: daemon.DaemonService.GetAgentNetworkSetup:output_type -> daemon.GetAgentNetworkSetupResponse + 87, // [87:134] is the sub-list for method output_type + 40, // [40:87] is the sub-list for method input_type + 40, // [40:40] is the sub-list for extension type_name + 40, // [40:40] is the sub-list for extension extendee + 0, // [0:40] is the sub-list for field type_name } func init() { file_daemon_proto_init() } @@ -7947,7 +8153,7 @@ func file_daemon_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)), NumEnums: 4, - NumMessages: 110, + NumMessages: 113, NumExtensions: 0, NumServices: 1, }, diff --git a/client/proto/daemon.pb.gw.go b/client/proto/daemon.pb.gw.go index b64dfeea1..759733cdd 100644 --- a/client/proto/daemon.pb.gw.go +++ b/client/proto/daemon.pb.gw.go @@ -1123,6 +1123,30 @@ func local_request_DaemonService_WailsUIReady_0(ctx context.Context, marshaler r return msg, metadata, err } +func request_DaemonService_GetAgentNetworkSetup_0(ctx context.Context, marshaler runtime.Marshaler, client DaemonServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetAgentNetworkSetupRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetAgentNetworkSetup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_DaemonService_GetAgentNetworkSetup_0(ctx context.Context, marshaler runtime.Marshaler, server DaemonServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetAgentNetworkSetupRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetAgentNetworkSetup(ctx, &protoReq) + return msg, metadata, err +} + // RegisterDaemonServiceHandlerServer registers the http handlers for service DaemonService to "mux". // UnaryRPC :call DaemonServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1997,6 +2021,26 @@ func RegisterDaemonServiceHandlerServer(ctx context.Context, mux *runtime.ServeM } forward_DaemonService_WailsUIReady_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_DaemonService_GetAgentNetworkSetup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/daemon.DaemonService/GetAgentNetworkSetup", runtime.WithHTTPPathPattern("/daemon.DaemonService/GetAgentNetworkSetup")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_DaemonService_GetAgentNetworkSetup_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_DaemonService_GetAgentNetworkSetup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -2819,6 +2863,23 @@ func RegisterDaemonServiceHandlerClient(ctx context.Context, mux *runtime.ServeM } forward_DaemonService_WailsUIReady_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_DaemonService_GetAgentNetworkSetup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/daemon.DaemonService/GetAgentNetworkSetup", runtime.WithHTTPPathPattern("/daemon.DaemonService/GetAgentNetworkSetup")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_DaemonService_GetAgentNetworkSetup_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_DaemonService_GetAgentNetworkSetup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -2869,6 +2930,7 @@ var ( pattern_DaemonService_GetInstallerResult_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"daemon.DaemonService", "GetInstallerResult"}, "")) pattern_DaemonService_ExposeService_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"daemon.DaemonService", "ExposeService"}, "")) pattern_DaemonService_WailsUIReady_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"daemon.DaemonService", "WailsUIReady"}, "")) + pattern_DaemonService_GetAgentNetworkSetup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"daemon.DaemonService", "GetAgentNetworkSetup"}, "")) ) var ( @@ -2918,4 +2980,5 @@ var ( forward_DaemonService_GetInstallerResult_0 = runtime.ForwardResponseMessage forward_DaemonService_ExposeService_0 = runtime.ForwardResponseStream forward_DaemonService_WailsUIReady_0 = runtime.ForwardResponseMessage + forward_DaemonService_GetAgentNetworkSetup_0 = runtime.ForwardResponseMessage ) diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index 3c31156ec..1dc6b5f60 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -156,6 +156,12 @@ service DaemonService { // only cares whether the daemon implements it: an Unimplemented response // means the daemon predates this UI and is too old to drive it. rpc WailsUIReady(WailsUIReadyRequest) returns (WailsUIReadyResponse) {} + + // GetAgentNetworkSetup returns the Agent Network connection info this + // peer's groups authorize: proxy endpoint plus effective providers and + // models. The daemon relays the request to management over its existing + // peer connection, so unprivileged CLI callers get a caller-scoped answer. + rpc GetAgentNetworkSetup(GetAgentNetworkSetupRequest) returns (GetAgentNetworkSetupResponse) {} } @@ -1050,3 +1056,31 @@ message StartBundleCaptureRequest { message StartBundleCaptureResponse {} message StopBundleCaptureRequest {} message StopBundleCaptureResponse {} + +message GetAgentNetworkSetupRequest {} + +message GetAgentNetworkSetupResponse { + // configured is false when the account has no Agent Network set up or + // this peer's groups authorize none of it. + bool configured = 1; + // endpoint is the account's proxy base URL, reachable over the NetBird + // tunnel only. Empty when configured is false. + string endpoint = 2; + repeated AgentNetworkProvider providers = 3; +} + +message AgentNetworkProvider { + // name is the operator-assigned provider label, e.g. "Bedrock prod". + string name = 1; + // catalog_id names the provider type, e.g. "anthropic_api", "bedrock_api". + string catalog_id = 2; + // api_flavor is the request-body shape the provider speaks ("anthropic", + // "openai"); empty when the proxy dispatches it by URL path instead. + string api_flavor = 3; + // all_models_allowed is true when no model allowlist restricts this + // provider for the caller. + bool all_models_allowed = 4; + // models is the effective model allowlist (or the declared/catalog models + // when all_models_allowed is true). + repeated string models = 5; +} diff --git a/client/proto/daemon_grpc.pb.go b/client/proto/daemon_grpc.pb.go index 2d01d474d..a7cf77853 100644 --- a/client/proto/daemon_grpc.pb.go +++ b/client/proto/daemon_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.1 -// - protoc v6.33.1 +// - protoc v7.34.1 // source: daemon.proto package proto @@ -65,6 +65,7 @@ const ( DaemonService_GetInstallerResult_FullMethodName = "/daemon.DaemonService/GetInstallerResult" DaemonService_ExposeService_FullMethodName = "/daemon.DaemonService/ExposeService" DaemonService_WailsUIReady_FullMethodName = "/daemon.DaemonService/WailsUIReady" + DaemonService_GetAgentNetworkSetup_FullMethodName = "/daemon.DaemonService/GetAgentNetworkSetup" ) // DaemonServiceClient is the client API for DaemonService service. @@ -171,6 +172,11 @@ type DaemonServiceClient interface { // only cares whether the daemon implements it: an Unimplemented response // means the daemon predates this UI and is too old to drive it. WailsUIReady(ctx context.Context, in *WailsUIReadyRequest, opts ...grpc.CallOption) (*WailsUIReadyResponse, error) + // GetAgentNetworkSetup returns the Agent Network connection info this + // peer's groups authorize: proxy endpoint plus effective providers and + // models. The daemon relays the request to management over its existing + // peer connection, so unprivileged CLI callers get a caller-scoped answer. + GetAgentNetworkSetup(ctx context.Context, in *GetAgentNetworkSetupRequest, opts ...grpc.CallOption) (*GetAgentNetworkSetupResponse, error) } type daemonServiceClient struct { @@ -677,6 +683,16 @@ func (c *daemonServiceClient) WailsUIReady(ctx context.Context, in *WailsUIReady return out, nil } +func (c *daemonServiceClient) GetAgentNetworkSetup(ctx context.Context, in *GetAgentNetworkSetupRequest, opts ...grpc.CallOption) (*GetAgentNetworkSetupResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAgentNetworkSetupResponse) + err := c.cc.Invoke(ctx, DaemonService_GetAgentNetworkSetup_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // DaemonServiceServer is the server API for DaemonService service. // All implementations must embed UnimplementedDaemonServiceServer // for forward compatibility. @@ -781,6 +797,11 @@ type DaemonServiceServer interface { // only cares whether the daemon implements it: an Unimplemented response // means the daemon predates this UI and is too old to drive it. WailsUIReady(context.Context, *WailsUIReadyRequest) (*WailsUIReadyResponse, error) + // GetAgentNetworkSetup returns the Agent Network connection info this + // peer's groups authorize: proxy endpoint plus effective providers and + // models. The daemon relays the request to management over its existing + // peer connection, so unprivileged CLI callers get a caller-scoped answer. + GetAgentNetworkSetup(context.Context, *GetAgentNetworkSetupRequest) (*GetAgentNetworkSetupResponse, error) mustEmbedUnimplementedDaemonServiceServer() } @@ -929,6 +950,9 @@ func (UnimplementedDaemonServiceServer) ExposeService(*ExposeServiceRequest, grp func (UnimplementedDaemonServiceServer) WailsUIReady(context.Context, *WailsUIReadyRequest) (*WailsUIReadyResponse, error) { return nil, status.Error(codes.Unimplemented, "method WailsUIReady not implemented") } +func (UnimplementedDaemonServiceServer) GetAgentNetworkSetup(context.Context, *GetAgentNetworkSetupRequest) (*GetAgentNetworkSetupResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetAgentNetworkSetup not implemented") +} func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {} func (UnimplementedDaemonServiceServer) testEmbeddedByValue() {} @@ -1750,6 +1774,24 @@ func _DaemonService_WailsUIReady_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _DaemonService_GetAgentNetworkSetup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgentNetworkSetupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).GetAgentNetworkSetup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DaemonService_GetAgentNetworkSetup_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).GetAgentNetworkSetup(ctx, req.(*GetAgentNetworkSetupRequest)) + } + return interceptor(ctx, in, info, handler) +} + // DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1925,6 +1967,10 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{ MethodName: "WailsUIReady", Handler: _DaemonService_WailsUIReady_Handler, }, + { + MethodName: "GetAgentNetworkSetup", + Handler: _DaemonService_GetAgentNetworkSetup_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/client/server/agentnetwork.go b/client/server/agentnetwork.go new file mode 100644 index 000000000..07cc1157b --- /dev/null +++ b/client/server/agentnetwork.go @@ -0,0 +1,59 @@ +package server + +import ( + "context" + "time" + + "google.golang.org/grpc/codes" + gstatus "google.golang.org/grpc/status" + + "github.com/netbirdio/netbird/client/proto" + mgmProto "github.com/netbirdio/netbird/shared/management/proto" +) + +// GetAgentNetworkSetup relays the peer's Agent Network setup request to the +// management server over the engine's existing connection. Running through +// the daemon keeps the WireGuard key inside the daemon — unprivileged CLI +// callers get a caller-scoped answer without reading the profile config. +func (s *Server) GetAgentNetworkSetup(ctx context.Context, _ *proto.GetAgentNetworkSetupRequest) (*proto.GetAgentNetworkSetupResponse, error) { + s.mutex.Lock() + clientRunning := s.clientRunning + connectClient := s.connectClient + s.mutex.Unlock() + + if !clientRunning || connectClient == nil { + return nil, gstatus.Errorf(codes.FailedPrecondition, "client is not running, run 'netbird up' first") + } + engine := connectClient.Engine() + if engine == nil { + return nil, gstatus.Errorf(codes.FailedPrecondition, "engine not initialized") + } + + setupCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + setup, err := engine.GetAgentNetworkSetup(setupCtx) + if err != nil { + return nil, gstatus.Errorf(codes.Internal, "get agent network setup: %v", err) + } + + return toDaemonAgentNetworkSetup(setup), nil +} + +func toDaemonAgentNetworkSetup(setup *mgmProto.AgentNetworkSetupResponse) *proto.GetAgentNetworkSetupResponse { + resp := &proto.GetAgentNetworkSetupResponse{ + Configured: setup.Configured, + Endpoint: setup.Endpoint, + Providers: make([]*proto.AgentNetworkProvider, 0, len(setup.Providers)), + } + for _, p := range setup.Providers { + resp.Providers = append(resp.Providers, &proto.AgentNetworkProvider{ + Name: p.Name, + CatalogId: p.CatalogId, + ApiFlavor: p.ApiFlavor, + AllModelsAllowed: p.AllModelsAllowed, + Models: p.Models, + }) + } + return resp +}