mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-07 23:41:29 +02:00
merge main\
This commit is contained in:
@@ -17,7 +17,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
||||
ephemeral_manager "github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||
@@ -103,7 +103,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ia, _ := integrations.NewIntegratedValidator(ctx, peersManger, settingsManagerMock, eventStore, cacheStore)
|
||||
ia, _ := validator.NewIntegratedValidator(ctx, peersManger, settingsManagerMock, eventStore, cacheStore)
|
||||
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -143,6 +143,10 @@ type Client struct {
|
||||
|
||||
// ReverseProxyDomains NetBird reverse proxy domains APIs
|
||||
ReverseProxyDomains *ReverseProxyDomainsAPI
|
||||
|
||||
// ReverseProxyTokens account-scoped proxy access tokens used to register
|
||||
// self-hosted (bring-your-own-proxy) `netbird proxy` instances.
|
||||
ReverseProxyTokens *ReverseProxyTokensAPI
|
||||
}
|
||||
|
||||
// New initialize new Client instance using PAT token
|
||||
@@ -204,6 +208,7 @@ func (c *Client) initialize() {
|
||||
c.ReverseProxyServices = &ReverseProxyServicesAPI{c}
|
||||
c.ReverseProxyClusters = &ReverseProxyClustersAPI{c}
|
||||
c.ReverseProxyDomains = &ReverseProxyDomainsAPI{c}
|
||||
c.ReverseProxyTokens = &ReverseProxyTokensAPI{c}
|
||||
}
|
||||
|
||||
// NewRequest creates and executes new management API request
|
||||
|
||||
@@ -2,6 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||
)
|
||||
@@ -11,7 +12,10 @@ type ReverseProxyClustersAPI struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// List lists all available proxy clusters
|
||||
// List lists all available proxy clusters. Each cluster is enriched with the
|
||||
// capability flags reported by its connected proxies (supports_custom_ports,
|
||||
// supports_crowdsec, private, etc.), so callers can render UX gates without
|
||||
// a follow-up round-trip.
|
||||
func (a *ReverseProxyClustersAPI) List(ctx context.Context) ([]api.ProxyCluster, error) {
|
||||
resp, err := a.c.NewRequest(ctx, "GET", "/api/reverse-proxies/clusters", nil, nil)
|
||||
if err != nil {
|
||||
@@ -23,3 +27,18 @@ func (a *ReverseProxyClustersAPI) List(ctx context.Context) ([]api.ProxyCluster,
|
||||
ret, err := parseResponse[[]api.ProxyCluster](resp)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Delete removes every self-hosted (BYOP) proxy registration for the given
|
||||
// cluster address owned by the calling account. Shared clusters operated by
|
||||
// NetBird cannot be deleted via this endpoint; the server returns 404 / 400
|
||||
// for cluster addresses the account does not own.
|
||||
func (a *ReverseProxyClustersAPI) Delete(ctx context.Context, clusterAddress string) error {
|
||||
resp, err := a.c.NewRequest(ctx, "DELETE", "/api/reverse-proxies/clusters/"+url.PathEscape(clusterAddress), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
90
shared/management/client/rest/reverse_proxy_clusters_test.go
Normal file
90
shared/management/client/rest/reverse_proxy_clusters_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
//go:build integration
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/client/rest"
|
||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||
"github.com/netbirdio/netbird/shared/management/http/util"
|
||||
)
|
||||
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
var testCluster = api.ProxyCluster{
|
||||
Id: "cluster-1",
|
||||
Address: "proxy.netbird.local",
|
||||
Type: "shared",
|
||||
Online: true,
|
||||
ConnectedProxies: 2,
|
||||
SupportsCustomPorts: boolPtr(true),
|
||||
RequireSubdomain: boolPtr(false),
|
||||
SupportsCrowdsec: boolPtr(false),
|
||||
Private: boolPtr(true),
|
||||
}
|
||||
|
||||
func TestReverseProxyClusters_List_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/clusters", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "GET", r.Method, "List must use GET")
|
||||
retBytes, _ := json.Marshal([]api.ProxyCluster{testCluster})
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.ReverseProxyClusters.List(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ret, 1)
|
||||
assert.Equal(t, testCluster.Id, ret[0].Id)
|
||||
assert.Equal(t, testCluster.Address, ret[0].Address)
|
||||
require.NotNil(t, ret[0].Private, "private capability must round-trip through the client")
|
||||
assert.True(t, *ret[0].Private, "private capability must reflect the server value")
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyClusters_List_Err(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/clusters", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "No", Code: 500})
|
||||
w.WriteHeader(500)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.ReverseProxyClusters.List(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, ret)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyClusters_Delete_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
// PathEscape on "proxy.netbird.local" leaves it intact; the route mux
|
||||
// matches the unescaped form. Sanity-check both the method and that
|
||||
// path-escaping doesn't double-encode the dotted address.
|
||||
mux.HandleFunc("/api/reverse-proxies/clusters/proxy.netbird.local", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "DELETE", r.Method, "Delete must use DELETE")
|
||||
w.WriteHeader(200)
|
||||
})
|
||||
err := c.ReverseProxyClusters.Delete(context.Background(), "proxy.netbird.local")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyClusters_Delete_Err(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/clusters/proxy.netbird.local", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "Not found", Code: 404})
|
||||
w.WriteHeader(404)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
err := c.ReverseProxyClusters.Delete(context.Background(), "proxy.netbird.local")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -116,8 +116,8 @@ func TestReverseProxyServices_Create_200(t *testing.T) {
|
||||
Name: "test-service",
|
||||
Domain: "test.example.com",
|
||||
Enabled: true,
|
||||
Auth: api.ServiceAuthConfig{},
|
||||
Targets: []api.ServiceTarget{testServiceTarget},
|
||||
Auth: &api.ServiceAuthConfig{},
|
||||
Targets: &[]api.ServiceTarget{testServiceTarget},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testService.Id, ret.Id)
|
||||
@@ -136,8 +136,8 @@ func TestReverseProxyServices_Create_Err(t *testing.T) {
|
||||
Name: "test-service",
|
||||
Domain: "test.example.com",
|
||||
Enabled: true,
|
||||
Auth: api.ServiceAuthConfig{},
|
||||
Targets: []api.ServiceTarget{testServiceTarget},
|
||||
Auth: &api.ServiceAuthConfig{},
|
||||
Targets: &[]api.ServiceTarget{testServiceTarget},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "No", err.Error())
|
||||
@@ -154,8 +154,9 @@ func TestReverseProxyServices_Create_WithPerTargetOptions(t *testing.T) {
|
||||
var req api.ServiceRequest
|
||||
require.NoError(t, json.Unmarshal(reqBytes, &req))
|
||||
|
||||
require.Len(t, req.Targets, 1)
|
||||
target := req.Targets[0]
|
||||
require.NotNil(t, req.Targets, "targets must be set on the request")
|
||||
require.Len(t, *req.Targets, 1)
|
||||
target := (*req.Targets)[0]
|
||||
require.NotNil(t, target.Options, "options should be present")
|
||||
opts := target.Options
|
||||
require.NotNil(t, opts.SkipTlsVerify, "skip_tls_verify should be present")
|
||||
@@ -177,8 +178,8 @@ func TestReverseProxyServices_Create_WithPerTargetOptions(t *testing.T) {
|
||||
Name: "test-service",
|
||||
Domain: "test.example.com",
|
||||
Enabled: true,
|
||||
Auth: api.ServiceAuthConfig{},
|
||||
Targets: []api.ServiceTarget{
|
||||
Auth: &api.ServiceAuthConfig{},
|
||||
Targets: &[]api.ServiceTarget{
|
||||
{
|
||||
TargetId: "peer-123",
|
||||
TargetType: "peer",
|
||||
@@ -216,8 +217,8 @@ func TestReverseProxyServices_Update_200(t *testing.T) {
|
||||
Name: "updated-service",
|
||||
Domain: "test.example.com",
|
||||
Enabled: true,
|
||||
Auth: api.ServiceAuthConfig{},
|
||||
Targets: []api.ServiceTarget{testServiceTarget},
|
||||
Auth: &api.ServiceAuthConfig{},
|
||||
Targets: &[]api.ServiceTarget{testServiceTarget},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testService.Id, ret.Id)
|
||||
@@ -236,8 +237,8 @@ func TestReverseProxyServices_Update_Err(t *testing.T) {
|
||||
Name: "updated-service",
|
||||
Domain: "test.example.com",
|
||||
Enabled: true,
|
||||
Auth: api.ServiceAuthConfig{},
|
||||
Targets: []api.ServiceTarget{testServiceTarget},
|
||||
Auth: &api.ServiceAuthConfig{},
|
||||
Targets: &[]api.ServiceTarget{testServiceTarget},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "No", err.Error())
|
||||
|
||||
72
shared/management/client/rest/reverse_proxy_tokens.go
Normal file
72
shared/management/client/rest/reverse_proxy_tokens.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||
)
|
||||
|
||||
// ReverseProxyTokensAPI exposes the account-scoped proxy access tokens that
|
||||
// self-hosted (bring-your-own-proxy) deployments use to register a
|
||||
// `netbird proxy` instance with management. Tokens are bound to the
|
||||
// calling account; revoking a token disconnects every proxy that
|
||||
// authenticated with it.
|
||||
type ReverseProxyTokensAPI struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// List returns every proxy token the calling account has minted, including
|
||||
// already-revoked entries. The plain token is never returned — only the
|
||||
// metadata (id, name, created_at, last_used, revoked).
|
||||
func (a *ReverseProxyTokensAPI) List(ctx context.Context) ([]api.ProxyToken, error) {
|
||||
resp, err := a.c.NewRequest(ctx, "GET", "/api/reverse-proxies/proxy-tokens", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
ret, err := parseResponse[[]api.ProxyToken](resp)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Create mints a fresh account-scoped proxy token. The returned
|
||||
// ProxyTokenCreated.PlainToken is shown only once — callers must persist
|
||||
// it immediately. Subsequent reads will only expose the token metadata,
|
||||
// not the secret material.
|
||||
func (a *ReverseProxyTokensAPI) Create(ctx context.Context, request api.ProxyTokenRequest) (*api.ProxyTokenCreated, error) {
|
||||
requestBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := a.c.NewRequest(ctx, "POST", "/api/reverse-proxies/proxy-tokens", bytes.NewReader(requestBytes), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
ret, err := parseResponse[api.ProxyTokenCreated](resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
// Delete revokes a previously-issued proxy token by ID. Revoked tokens
|
||||
// remain in List output (with revoked=true) so operators can audit which
|
||||
// credentials existed; the plain secret can no longer authenticate any
|
||||
// new proxy registration.
|
||||
func (a *ReverseProxyTokensAPI) Delete(ctx context.Context, tokenID string) error {
|
||||
resp, err := a.c.NewRequest(ctx, "DELETE", "/api/reverse-proxies/proxy-tokens/"+url.PathEscape(tokenID), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
131
shared/management/client/rest/reverse_proxy_tokens_test.go
Normal file
131
shared/management/client/rest/reverse_proxy_tokens_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
//go:build integration
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/client/rest"
|
||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||
"github.com/netbirdio/netbird/shared/management/http/util"
|
||||
)
|
||||
|
||||
func intPtr(v int) *int { return &v }
|
||||
|
||||
var testProxyToken = api.ProxyToken{
|
||||
Id: "tok-1",
|
||||
Name: "ci-runner",
|
||||
CreatedAt: time.Date(2026, 5, 21, 9, 0, 0, 0, time.UTC),
|
||||
Revoked: false,
|
||||
}
|
||||
|
||||
var testProxyTokenCreated = api.ProxyTokenCreated{
|
||||
Id: "tok-1",
|
||||
Name: "ci-runner",
|
||||
CreatedAt: time.Date(2026, 5, 21, 9, 0, 0, 0, time.UTC),
|
||||
PlainToken: "nbproxy_abcdef0123456789",
|
||||
Revoked: false,
|
||||
}
|
||||
|
||||
func TestReverseProxyTokens_List_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/proxy-tokens", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "GET", r.Method, "List must use GET")
|
||||
retBytes, _ := json.Marshal([]api.ProxyToken{testProxyToken})
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.ReverseProxyTokens.List(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ret, 1)
|
||||
assert.Equal(t, testProxyToken.Id, ret[0].Id)
|
||||
assert.Equal(t, testProxyToken.Name, ret[0].Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyTokens_List_Err(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/proxy-tokens", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "No", Code: 500})
|
||||
w.WriteHeader(500)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.ReverseProxyTokens.List(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, ret)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyTokens_Create_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/proxy-tokens", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "POST", r.Method, "Create must use POST")
|
||||
body, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
var req api.ProxyTokenRequest
|
||||
require.NoError(t, json.Unmarshal(body, &req), "server must receive a valid ProxyTokenRequest body")
|
||||
assert.Equal(t, "ci-runner", req.Name, "name must round-trip through the client")
|
||||
require.NotNil(t, req.ExpiresIn, "expires_in must be sent when provided")
|
||||
assert.Equal(t, 3600, *req.ExpiresIn, "expires_in value must round-trip unchanged")
|
||||
|
||||
retBytes, _ := json.Marshal(testProxyTokenCreated)
|
||||
_, err = w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.ReverseProxyTokens.Create(context.Background(), api.ProxyTokenRequest{
|
||||
Name: "ci-runner",
|
||||
ExpiresIn: intPtr(3600),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testProxyTokenCreated.Id, ret.Id)
|
||||
assert.Equal(t, testProxyTokenCreated.PlainToken, ret.PlainToken,
|
||||
"PlainToken must be returned to the caller — it's the one-shot secret")
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyTokens_Create_Err(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/proxy-tokens", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "Bad", Code: 400})
|
||||
w.WriteHeader(400)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.ReverseProxyTokens.Create(context.Background(), api.ProxyTokenRequest{Name: ""})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, ret)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyTokens_Delete_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/proxy-tokens/tok-1", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "DELETE", r.Method, "Delete must use DELETE")
|
||||
w.WriteHeader(200)
|
||||
})
|
||||
err := c.ReverseProxyTokens.Delete(context.Background(), "tok-1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReverseProxyTokens_Delete_Err(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/reverse-proxies/proxy-tokens/tok-1", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "Not found", Code: 404})
|
||||
w.WriteHeader(404)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
err := c.ReverseProxyTokens.Delete(context.Background(), "tok-1")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -3067,6 +3067,17 @@ components:
|
||||
$ref: '#/components/schemas/AccessRestrictions'
|
||||
meta:
|
||||
$ref: '#/components/schemas/ServiceMeta'
|
||||
private:
|
||||
type: boolean
|
||||
description: When true, the service is NetBird-only — its target points at a proxy cluster, inbound peers authenticate via their WireGuard tunnel identity (no OIDC), and an ACL policy is auto-generated from access_groups to the cluster's proxy-peer group. Requires mode=http.
|
||||
default: false
|
||||
example: false
|
||||
access_groups:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: NetBird group IDs whose peers may reach this private service over the tunnel. Required when private=true; ignored otherwise. Mutually exclusive with bearer auth (SSO).
|
||||
example: ["group-engineering"]
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
@@ -3147,6 +3158,17 @@ components:
|
||||
$ref: '#/components/schemas/ServiceAuthConfig'
|
||||
access_restrictions:
|
||||
$ref: '#/components/schemas/AccessRestrictions'
|
||||
private:
|
||||
type: boolean
|
||||
description: When true, the service is NetBird-only — its target points at a proxy cluster, inbound peers authenticate via their WireGuard tunnel identity (no OIDC), and an ACL policy is auto-generated from access_groups to the cluster's proxy-peer group. Requires mode=http.
|
||||
default: false
|
||||
example: false
|
||||
access_groups:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: NetBird group IDs whose peers may reach this private service over the tunnel. Required when private=true; ignored otherwise. Mutually exclusive with bearer auth (SSO).
|
||||
example: ["group-engineering"]
|
||||
required:
|
||||
- name
|
||||
- domain
|
||||
@@ -3185,6 +3207,15 @@ components:
|
||||
type: string
|
||||
description: Idle timeout before a UDP session is reaped, as a Go duration string (e.g. "30s", "2m").
|
||||
example: "2m"
|
||||
direct_upstream:
|
||||
type: boolean
|
||||
description: |
|
||||
When true, the proxy dials this target via the host's network stack
|
||||
instead of through its embedded NetBird client. Use for upstreams
|
||||
reachable without WireGuard (public APIs, LAN services, localhost
|
||||
sidecars).
|
||||
default: false
|
||||
example: false
|
||||
ServiceTarget:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3195,7 +3226,7 @@ components:
|
||||
target_type:
|
||||
type: string
|
||||
description: Target type
|
||||
enum: [peer, host, domain, subnet]
|
||||
enum: [peer, host, domain, subnet, cluster]
|
||||
example: "subnet"
|
||||
path:
|
||||
type: string
|
||||
@@ -3417,19 +3448,47 @@ components:
|
||||
type: string
|
||||
description: Cluster address used for CNAME targets
|
||||
example: "eu.proxy.netbird.io"
|
||||
type:
|
||||
$ref: '#/components/schemas/ProxyClusterType'
|
||||
online:
|
||||
type: boolean
|
||||
description: Whether at least one proxy in the cluster has heartbeated within the active window
|
||||
example: true
|
||||
connected_proxies:
|
||||
type: integer
|
||||
description: Number of proxy nodes connected in this cluster
|
||||
description: Number of proxy nodes currently connected (heartbeat within the active window)
|
||||
example: 3
|
||||
self_hosted:
|
||||
supports_custom_ports:
|
||||
type: boolean
|
||||
description: Whether this cluster is a self-hosted (BYOP) proxy managed by the account owner
|
||||
description: Whether the cluster supports binding arbitrary TCP/UDP ports
|
||||
example: true
|
||||
require_subdomain:
|
||||
type: boolean
|
||||
description: Whether services on this cluster must include a subdomain label
|
||||
example: false
|
||||
supports_crowdsec:
|
||||
type: boolean
|
||||
description: Whether all active proxies in the cluster have CrowdSec configured
|
||||
example: false
|
||||
private:
|
||||
type: boolean
|
||||
description: True when at least one connected proxy in this cluster is running embedded in a netbird client (`netbird proxy`) and serving over a WireGuard tunnel. Lets the dashboard distinguish per-peer / private clusters from centralised ones.
|
||||
example: false
|
||||
required:
|
||||
- id
|
||||
- address
|
||||
- type
|
||||
- online
|
||||
- connected_proxies
|
||||
- self_hosted
|
||||
ProxyClusterType:
|
||||
type: string
|
||||
description: |
|
||||
Source of the proxy cluster. `account` clusters are owned and operated by the account (BYOP);
|
||||
`shared` clusters are operated by NetBird and shared across accounts.
|
||||
enum:
|
||||
- account
|
||||
- shared
|
||||
example: shared
|
||||
ReverseProxyDomainType:
|
||||
type: string
|
||||
description: Type of Reverse Proxy Domain
|
||||
@@ -3470,6 +3529,10 @@ components:
|
||||
type: boolean
|
||||
description: Whether the proxy cluster has CrowdSec configured
|
||||
example: false
|
||||
supports_private:
|
||||
type: boolean
|
||||
description: Whether the proxy cluster supports private (NetBird-only) services. True when at least one connected proxy in the cluster runs embedded in a netbird client.
|
||||
example: false
|
||||
required:
|
||||
- id
|
||||
- domain
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.6.0 DO NOT EDIT.
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.7.0 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
BearerAuthScopes = "BearerAuth.Scopes"
|
||||
TokenAuthScopes = "TokenAuth.Scopes"
|
||||
BearerAuthScopes bearerAuthContextKey = "BearerAuth.Scopes"
|
||||
TokenAuthScopes tokenAuthContextKey = "TokenAuth.Scopes"
|
||||
)
|
||||
|
||||
// Defines values for AccessRestrictionsCrowdsecMode.
|
||||
@@ -511,6 +511,7 @@ func (e GroupMinimumIssued) Valid() bool {
|
||||
|
||||
// Defines values for IdentityProviderType.
|
||||
const (
|
||||
IdentityProviderTypeAdfs IdentityProviderType = "adfs"
|
||||
IdentityProviderTypeEntra IdentityProviderType = "entra"
|
||||
IdentityProviderTypeGoogle IdentityProviderType = "google"
|
||||
IdentityProviderTypeMicrosoft IdentityProviderType = "microsoft"
|
||||
@@ -518,12 +519,13 @@ const (
|
||||
IdentityProviderTypeOkta IdentityProviderType = "okta"
|
||||
IdentityProviderTypePocketid IdentityProviderType = "pocketid"
|
||||
IdentityProviderTypeZitadel IdentityProviderType = "zitadel"
|
||||
IdentityProviderTypeAdfs IdentityProviderType = "adfs"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the IdentityProviderType enum.
|
||||
func (e IdentityProviderType) Valid() bool {
|
||||
switch e {
|
||||
case IdentityProviderTypeAdfs:
|
||||
return true
|
||||
case IdentityProviderTypeEntra:
|
||||
return true
|
||||
case IdentityProviderTypeGoogle:
|
||||
@@ -538,8 +540,6 @@ func (e IdentityProviderType) Valid() bool {
|
||||
return true
|
||||
case IdentityProviderTypeZitadel:
|
||||
return true
|
||||
case IdentityProviderTypeAdfs:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -878,6 +878,24 @@ func (e PolicyRuleUpdateProtocol) Valid() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for ProxyClusterType.
|
||||
const (
|
||||
ProxyClusterTypeAccount ProxyClusterType = "account"
|
||||
ProxyClusterTypeShared ProxyClusterType = "shared"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the ProxyClusterType enum.
|
||||
func (e ProxyClusterType) Valid() bool {
|
||||
switch e {
|
||||
case ProxyClusterTypeAccount:
|
||||
return true
|
||||
case ProxyClusterTypeShared:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for ResourceType.
|
||||
const (
|
||||
ResourceTypeDomain ResourceType = "domain"
|
||||
@@ -1045,15 +1063,18 @@ func (e ServiceTargetProtocol) Valid() bool {
|
||||
|
||||
// Defines values for ServiceTargetTargetType.
|
||||
const (
|
||||
ServiceTargetTargetTypeDomain ServiceTargetTargetType = "domain"
|
||||
ServiceTargetTargetTypeHost ServiceTargetTargetType = "host"
|
||||
ServiceTargetTargetTypePeer ServiceTargetTargetType = "peer"
|
||||
ServiceTargetTargetTypeSubnet ServiceTargetTargetType = "subnet"
|
||||
ServiceTargetTargetTypeCluster ServiceTargetTargetType = "cluster"
|
||||
ServiceTargetTargetTypeDomain ServiceTargetTargetType = "domain"
|
||||
ServiceTargetTargetTypeHost ServiceTargetTargetType = "host"
|
||||
ServiceTargetTargetTypePeer ServiceTargetTargetType = "peer"
|
||||
ServiceTargetTargetTypeSubnet ServiceTargetTargetType = "subnet"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the ServiceTargetTargetType enum.
|
||||
func (e ServiceTargetTargetType) Valid() bool {
|
||||
switch e {
|
||||
case ServiceTargetTargetTypeCluster:
|
||||
return true
|
||||
case ServiceTargetTargetTypeDomain:
|
||||
return true
|
||||
case ServiceTargetTargetTypeHost:
|
||||
@@ -1638,7 +1659,9 @@ type Checks struct {
|
||||
// OsVersionCheck Posture check for the version of operating system
|
||||
OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"`
|
||||
|
||||
// PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it contains any of the peer's local network interface IPs or its public connection (NAT egress) IP, so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128.
|
||||
// PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it
|
||||
// contains any of the peer's local network interface IPs or its public connection (NAT egress) IP,
|
||||
// so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128.
|
||||
PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:"peer_network_range_check,omitempty"`
|
||||
|
||||
// ProcessCheck Posture Check for binaries exist and are running in the peer’s system
|
||||
@@ -3330,7 +3353,9 @@ type PeerMinimum struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it contains any of the peer's local network interface IPs or its public connection (NAT egress) IP, so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128.
|
||||
// PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it
|
||||
// contains any of the peer's local network interface IPs or its public connection (NAT egress) IP,
|
||||
// so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128.
|
||||
type PeerNetworkRangeCheck struct {
|
||||
// Action Action to take upon policy match
|
||||
Action PeerNetworkRangeCheckAction `json:"action"`
|
||||
@@ -3785,19 +3810,39 @@ type ProxyAccessLogsResponse struct {
|
||||
|
||||
// ProxyCluster A proxy cluster represents a group of proxy nodes serving the same address
|
||||
type ProxyCluster struct {
|
||||
// Id Unique identifier of a proxy in this cluster
|
||||
Id string `json:"id"`
|
||||
|
||||
// Address Cluster address used for CNAME targets
|
||||
Address string `json:"address"`
|
||||
|
||||
// ConnectedProxies Number of proxy nodes connected in this cluster
|
||||
// ConnectedProxies Number of proxy nodes currently connected (heartbeat within the active window)
|
||||
ConnectedProxies int `json:"connected_proxies"`
|
||||
|
||||
// SelfHosted Whether this cluster is a self-hosted (BYOP) proxy managed by the account owner
|
||||
SelfHosted bool `json:"self_hosted"`
|
||||
// Id Unique identifier of a proxy in this cluster
|
||||
Id string `json:"id"`
|
||||
|
||||
// Online Whether at least one proxy in the cluster has heartbeated within the active window
|
||||
Online bool `json:"online"`
|
||||
|
||||
// Private True when at least one connected proxy in this cluster is running embedded in a netbird client (`netbird proxy`) and serving over a WireGuard tunnel. Lets the dashboard distinguish per-peer / private clusters from centralised ones.
|
||||
Private *bool `json:"private,omitempty"`
|
||||
|
||||
// RequireSubdomain Whether services on this cluster must include a subdomain label
|
||||
RequireSubdomain *bool `json:"require_subdomain,omitempty"`
|
||||
|
||||
// SupportsCrowdsec Whether all active proxies in the cluster have CrowdSec configured
|
||||
SupportsCrowdsec *bool `json:"supports_crowdsec,omitempty"`
|
||||
|
||||
// SupportsCustomPorts Whether the cluster supports binding arbitrary TCP/UDP ports
|
||||
SupportsCustomPorts *bool `json:"supports_custom_ports,omitempty"`
|
||||
|
||||
// Type Source of the proxy cluster. `account` clusters are owned and operated by the account (BYOP);
|
||||
// `shared` clusters are operated by NetBird and shared across accounts.
|
||||
Type ProxyClusterType `json:"type"`
|
||||
}
|
||||
|
||||
// ProxyClusterType Source of the proxy cluster. `account` clusters are owned and operated by the account (BYOP);
|
||||
// `shared` clusters are operated by NetBird and shared across accounts.
|
||||
type ProxyClusterType string
|
||||
|
||||
// ProxyToken defines model for ProxyToken.
|
||||
type ProxyToken struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
@@ -3857,6 +3902,9 @@ type ReverseProxyDomain struct {
|
||||
// SupportsCustomPorts Whether the cluster supports binding arbitrary TCP/UDP ports
|
||||
SupportsCustomPorts *bool `json:"supports_custom_ports,omitempty"`
|
||||
|
||||
// SupportsPrivate Whether the proxy cluster supports private (NetBird-only) services. True when at least one connected proxy in the cluster runs embedded in a netbird client.
|
||||
SupportsPrivate *bool `json:"supports_private,omitempty"`
|
||||
|
||||
// TargetCluster The proxy cluster this domain is validated against (only for custom domains)
|
||||
TargetCluster *string `json:"target_cluster,omitempty"`
|
||||
|
||||
@@ -4046,6 +4094,9 @@ type SentinelOneMatchAttributesNetworkStatus string
|
||||
|
||||
// Service defines model for Service.
|
||||
type Service struct {
|
||||
// AccessGroups NetBird group IDs whose peers may reach this private service over the tunnel. Required when private=true; ignored otherwise. Mutually exclusive with bearer auth (SSO).
|
||||
AccessGroups *[]string `json:"access_groups,omitempty"`
|
||||
|
||||
// AccessRestrictions Connection-level access restrictions based on IP address or geography. Applies to both HTTP and L4 services.
|
||||
AccessRestrictions *AccessRestrictions `json:"access_restrictions,omitempty"`
|
||||
Auth ServiceAuthConfig `json:"auth"`
|
||||
@@ -4075,6 +4126,9 @@ type Service struct {
|
||||
// PortAutoAssigned Whether the listen port was auto-assigned
|
||||
PortAutoAssigned *bool `json:"port_auto_assigned,omitempty"`
|
||||
|
||||
// Private When true, the service is NetBird-only — its target points at a proxy cluster, inbound peers authenticate via their WireGuard tunnel identity (no OIDC), and an ACL policy is auto-generated from access_groups to the cluster's proxy-peer group. Requires mode=http.
|
||||
Private *bool `json:"private,omitempty"`
|
||||
|
||||
// ProxyCluster The proxy cluster handling this service (derived from domain)
|
||||
ProxyCluster *string `json:"proxy_cluster,omitempty"`
|
||||
|
||||
@@ -4117,6 +4171,9 @@ type ServiceMetaStatus string
|
||||
|
||||
// ServiceRequest defines model for ServiceRequest.
|
||||
type ServiceRequest struct {
|
||||
// AccessGroups NetBird group IDs whose peers may reach this private service over the tunnel. Required when private=true; ignored otherwise. Mutually exclusive with bearer auth (SSO).
|
||||
AccessGroups *[]string `json:"access_groups,omitempty"`
|
||||
|
||||
// AccessRestrictions Connection-level access restrictions based on IP address or geography. Applies to both HTTP and L4 services.
|
||||
AccessRestrictions *AccessRestrictions `json:"access_restrictions,omitempty"`
|
||||
Auth *ServiceAuthConfig `json:"auth,omitempty"`
|
||||
@@ -4139,6 +4196,9 @@ type ServiceRequest struct {
|
||||
// PassHostHeader When true, the original client Host header is passed through to the backend instead of being rewritten to the backend's address
|
||||
PassHostHeader *bool `json:"pass_host_header,omitempty"`
|
||||
|
||||
// Private When true, the service is NetBird-only — its target points at a proxy cluster, inbound peers authenticate via their WireGuard tunnel identity (no OIDC), and an ACL policy is auto-generated from access_groups to the cluster's proxy-peer group. Requires mode=http.
|
||||
Private *bool `json:"private,omitempty"`
|
||||
|
||||
// RewriteRedirects When true, Location headers in backend responses are rewritten to replace the backend address with the public-facing domain
|
||||
RewriteRedirects *bool `json:"rewrite_redirects,omitempty"`
|
||||
|
||||
@@ -4185,6 +4245,12 @@ type ServiceTargetOptions struct {
|
||||
// CustomHeaders Extra headers sent to the backend. Hop-by-hop and proxy-managed headers (Host, Connection, Transfer-Encoding, etc.) are rejected.
|
||||
CustomHeaders *map[string]string `json:"custom_headers,omitempty"`
|
||||
|
||||
// DirectUpstream When true, the proxy dials this target via the host's network stack
|
||||
// instead of through its embedded NetBird client. Use for upstreams
|
||||
// reachable without WireGuard (public APIs, LAN services, localhost
|
||||
// sidecars).
|
||||
DirectUpstream *bool `json:"direct_upstream,omitempty"`
|
||||
|
||||
// PathRewrite Controls how the request path is rewritten before forwarding to the backend. Default strips the matched prefix. "preserve" keeps the full original request path.
|
||||
PathRewrite *ServiceTargetOptionsPathRewrite `json:"path_rewrite,omitempty"`
|
||||
|
||||
@@ -4820,6 +4886,12 @@ type ZoneRequest struct {
|
||||
// Conflict Standard error response. Note: The exact structure of this error response is inferred from `util.WriteErrorResponse` and `util.WriteError` usage in the provided Go code, as a specific Go struct for errors was not provided.
|
||||
type Conflict = ErrorResponse
|
||||
|
||||
// bearerAuthContextKey is the context key for BearerAuth security scheme
|
||||
type bearerAuthContextKey string
|
||||
|
||||
// tokenAuthContextKey is the context key for TokenAuth security scheme
|
||||
type tokenAuthContextKey string
|
||||
|
||||
// GetApiEventsNetworkTrafficParams defines parameters for GetApiEventsNetworkTraffic.
|
||||
type GetApiEventsNetworkTrafficParams struct {
|
||||
// Page Page number
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,15 @@ import "google/protobuf/timestamp.proto";
|
||||
service ProxyService {
|
||||
rpc GetMappingUpdate(GetMappingUpdateRequest) returns (stream GetMappingUpdateResponse);
|
||||
|
||||
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
|
||||
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
|
||||
// stream and then sends an ack after each batch is fully processed.
|
||||
// Management waits for the ack before sending the next batch, providing
|
||||
// application-level back-pressure during large initial syncs.
|
||||
// Old proxies continue using GetMappingUpdate; old management servers
|
||||
// return Unimplemented for this RPC and proxies fall back.
|
||||
rpc SyncMappings(stream SyncMappingsRequest) returns (stream SyncMappingsResponse);
|
||||
|
||||
rpc SendAccessLog(SendAccessLogRequest) returns (SendAccessLogResponse);
|
||||
|
||||
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
|
||||
@@ -25,6 +34,15 @@ service ProxyService {
|
||||
// ValidateSession validates a session token and checks user access permissions.
|
||||
// Called by the proxy after receiving a session token from OIDC callback.
|
||||
rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse);
|
||||
|
||||
// ValidateTunnelPeer resolves an inbound peer by its WireGuard tunnel IP and
|
||||
// checks the resolved user's access against the service's access_groups.
|
||||
// Acts as a fast-path equivalent of OIDC for requests originating on the
|
||||
// netbird mesh: when the source IP maps to a known peer in the calling
|
||||
// account and that peer is in the service's access_groups, the proxy can
|
||||
// issue a session cookie without redirecting through the OIDC flow.
|
||||
// Mirrors ValidateSession's response shape.
|
||||
rpc ValidateTunnelPeer(ValidateTunnelPeerRequest) returns (ValidateTunnelPeerResponse);
|
||||
}
|
||||
|
||||
// ProxyCapabilities describes what a proxy can handle.
|
||||
@@ -36,6 +54,13 @@ message ProxyCapabilities {
|
||||
optional bool require_subdomain = 2;
|
||||
// Whether the proxy has CrowdSec configured and can enforce IP reputation checks.
|
||||
optional bool supports_crowdsec = 3;
|
||||
// Whether the proxy is running embedded in the netbird client and serving
|
||||
// exclusively over the WireGuard tunnel (i.e. `netbird proxy` rather than
|
||||
// the standalone netbird-proxy binary). Surfaces upstream so dashboards can
|
||||
// distinguish per-peer / private clusters from centralised ones.
|
||||
optional bool private = 4;
|
||||
// Whether the proxy enforces ProxyMapping.private (fails closed on ValidateTunnelPeer failure). Management MUST NOT stream private mappings to proxies that don't claim this.
|
||||
optional bool supports_private_service = 5;
|
||||
}
|
||||
|
||||
// GetMappingUpdateRequest is sent to initialise a mapping stream.
|
||||
@@ -77,6 +102,11 @@ message PathTargetOptions {
|
||||
bool proxy_protocol = 5;
|
||||
// Idle timeout before a UDP session is reaped.
|
||||
google.protobuf.Duration session_idle_timeout = 6;
|
||||
// When true, the proxy dials this target via the host's network stack
|
||||
// instead of through the embedded NetBird client. Useful for upstreams
|
||||
// reachable without WireGuard (public APIs, LAN services, localhost
|
||||
// sidecars). Defaults to false — embedded client is the standard path.
|
||||
bool direct_upstream = 7;
|
||||
}
|
||||
|
||||
message PathMapping {
|
||||
@@ -129,6 +159,8 @@ message ProxyMapping {
|
||||
// For L4/TLS: the port the proxy listens on.
|
||||
int32 listen_port = 11;
|
||||
AccessRestrictions access_restrictions = 12;
|
||||
// NetBird-only: the proxy MUST call ValidateTunnelPeer and fail closed; operator auth schemes are bypassed.
|
||||
bool private = 13;
|
||||
}
|
||||
|
||||
// SendAccessLogRequest consists of one or more AccessLogs from a Proxy.
|
||||
@@ -204,6 +236,25 @@ message SendStatusUpdateRequest {
|
||||
ProxyStatus status = 3;
|
||||
bool certificate_issued = 4;
|
||||
optional string error_message = 5;
|
||||
// Per-account inbound listener state for the account that owns
|
||||
// service_id. Populated only when --private-inbound is enabled and the
|
||||
// embedded client for the account is up. Field numbers >=50 reserved
|
||||
// for observability extensions.
|
||||
optional ProxyInboundListener inbound_listener = 50;
|
||||
}
|
||||
|
||||
// ProxyInboundListener describes a per-account inbound listener that the
|
||||
// proxy has bound on the embedded netstack of the account's WireGuard
|
||||
// client. Surfaced so dashboards can render "this account is reachable
|
||||
// at <tunnel_ip>:<https_port> on this proxy".
|
||||
message ProxyInboundListener {
|
||||
// Tunnel IP the embedded netstack listens on. Same address other peers
|
||||
// in the account see for the proxy peer.
|
||||
string tunnel_ip = 1;
|
||||
// TLS port served on tunnel_ip (auto-detected, default 443).
|
||||
uint32 https_port = 2;
|
||||
// Plain-HTTP port served on tunnel_ip (auto-detected, default 80).
|
||||
uint32 http_port = 3;
|
||||
}
|
||||
|
||||
// SendStatusUpdateResponse is intentionally empty to allow for future expansion
|
||||
@@ -245,4 +296,83 @@ message ValidateSessionResponse {
|
||||
string user_id = 2;
|
||||
string user_email = 3;
|
||||
string denied_reason = 4;
|
||||
// peer_group_ids carries the calling user's group memberships so the
|
||||
// proxy can authorise policy-aware middlewares without an additional
|
||||
// management round-trip.
|
||||
repeated string peer_group_ids = 5;
|
||||
// peer_group_names carries the human-readable display names for the
|
||||
// ids in peer_group_ids, ordered identically (positional pairing).
|
||||
// Stamped onto upstream requests as X-NetBird-Groups so downstream
|
||||
// services can read names rather than opaque ids.
|
||||
repeated string peer_group_names = 6;
|
||||
}
|
||||
|
||||
// ValidateTunnelPeerRequest carries the inbound peer's tunnel IP and the
|
||||
// service domain whose group requirements should gate access. The calling
|
||||
// account is inferred from the proxy's gRPC metadata (ProxyToken).
|
||||
message ValidateTunnelPeerRequest {
|
||||
string tunnel_ip = 1;
|
||||
string domain = 2;
|
||||
}
|
||||
|
||||
// ValidateTunnelPeerResponse mirrors ValidateSessionResponse plus a freshly
|
||||
// minted session_token: when valid is true, the proxy installs the token as
|
||||
// a session cookie so subsequent requests skip the management round-trip,
|
||||
// matching the OIDC flow's UX. denied_reason values:
|
||||
// "peer_not_found" — no peer with that tunnel IP in the calling account
|
||||
// "no_user" — peer exists but is not bound to a user
|
||||
// "service_not_found"
|
||||
// "account_mismatch"
|
||||
// "not_in_group" — peer resolved but not in service.access_groups
|
||||
message ValidateTunnelPeerResponse {
|
||||
bool valid = 1;
|
||||
string user_id = 2;
|
||||
string user_email = 3;
|
||||
string denied_reason = 4;
|
||||
// session_token is set only when valid is true. Same shape as the JWT
|
||||
// the OIDC flow produces — proxy installs it via setSessionCookie so the
|
||||
// tunnel fast-path is indistinguishable from OIDC for subsequent requests.
|
||||
string session_token = 5;
|
||||
// peer_group_ids carries the resolved peer's user group memberships so
|
||||
// the proxy can authorise policy-aware middlewares without an additional
|
||||
// management round-trip.
|
||||
repeated string peer_group_ids = 6;
|
||||
// peer_group_names carries the human-readable display names for the
|
||||
// ids in peer_group_ids, ordered identically (positional pairing).
|
||||
// Stamped onto upstream requests as X-NetBird-Groups so downstream
|
||||
// services can read names rather than opaque ids.
|
||||
repeated string peer_group_names = 7;
|
||||
}
|
||||
|
||||
// SyncMappingsRequest is sent by the proxy on the bidirectional SyncMappings
|
||||
// stream. The first message MUST be an init; all subsequent messages MUST be
|
||||
// acks.
|
||||
message SyncMappingsRequest {
|
||||
oneof msg {
|
||||
SyncMappingsInit init = 1;
|
||||
SyncMappingsAck ack = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// SyncMappingsInit is the first message on the stream, carrying the same
|
||||
// identification fields as GetMappingUpdateRequest.
|
||||
message SyncMappingsInit {
|
||||
string proxy_id = 1;
|
||||
string version = 2;
|
||||
google.protobuf.Timestamp started_at = 3;
|
||||
string address = 4;
|
||||
ProxyCapabilities capabilities = 5;
|
||||
}
|
||||
|
||||
// SyncMappingsAck is sent by the proxy after it has fully processed a batch.
|
||||
// Management waits for this before sending the next batch.
|
||||
message SyncMappingsAck {}
|
||||
|
||||
// SyncMappingsResponse is a batch of mappings sent by management.
|
||||
// Identical semantics to GetMappingUpdateResponse.
|
||||
message SyncMappingsResponse {
|
||||
repeated ProxyMapping mapping = 1;
|
||||
// initial_sync_complete is set on the last message of the initial snapshot.
|
||||
bool initial_sync_complete = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@ const _ = grpc.SupportPackageIsVersion7
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ProxyServiceClient interface {
|
||||
GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (ProxyService_GetMappingUpdateClient, error)
|
||||
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
|
||||
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
|
||||
// stream and then sends an ack after each batch is fully processed.
|
||||
// Management waits for the ack before sending the next batch, providing
|
||||
// application-level back-pressure during large initial syncs.
|
||||
// Old proxies continue using GetMappingUpdate; old management servers
|
||||
// return Unimplemented for this RPC and proxies fall back.
|
||||
SyncMappings(ctx context.Context, opts ...grpc.CallOption) (ProxyService_SyncMappingsClient, error)
|
||||
SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error)
|
||||
Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error)
|
||||
SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error)
|
||||
@@ -27,6 +35,14 @@ type ProxyServiceClient interface {
|
||||
// ValidateSession validates a session token and checks user access permissions.
|
||||
// Called by the proxy after receiving a session token from OIDC callback.
|
||||
ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error)
|
||||
// ValidateTunnelPeer resolves an inbound peer by its WireGuard tunnel IP and
|
||||
// checks the resolved user's access against the service's access_groups.
|
||||
// Acts as a fast-path equivalent of OIDC for requests originating on the
|
||||
// netbird mesh: when the source IP maps to a known peer in the calling
|
||||
// account and that peer is in the service's access_groups, the proxy can
|
||||
// issue a session cookie without redirecting through the OIDC flow.
|
||||
// Mirrors ValidateSession's response shape.
|
||||
ValidateTunnelPeer(ctx context.Context, in *ValidateTunnelPeerRequest, opts ...grpc.CallOption) (*ValidateTunnelPeerResponse, error)
|
||||
}
|
||||
|
||||
type proxyServiceClient struct {
|
||||
@@ -69,6 +85,37 @@ func (x *proxyServiceGetMappingUpdateClient) Recv() (*GetMappingUpdateResponse,
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *proxyServiceClient) SyncMappings(ctx context.Context, opts ...grpc.CallOption) (ProxyService_SyncMappingsClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[1], "/management.ProxyService/SyncMappings", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &proxyServiceSyncMappingsClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type ProxyService_SyncMappingsClient interface {
|
||||
Send(*SyncMappingsRequest) error
|
||||
Recv() (*SyncMappingsResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type proxyServiceSyncMappingsClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *proxyServiceSyncMappingsClient) Send(m *SyncMappingsRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *proxyServiceSyncMappingsClient) Recv() (*SyncMappingsResponse, error) {
|
||||
m := new(SyncMappingsResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *proxyServiceClient) SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error) {
|
||||
out := new(SendAccessLogResponse)
|
||||
err := c.cc.Invoke(ctx, "/management.ProxyService/SendAccessLog", in, out, opts...)
|
||||
@@ -123,11 +170,28 @@ func (c *proxyServiceClient) ValidateSession(ctx context.Context, in *ValidateSe
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *proxyServiceClient) ValidateTunnelPeer(ctx context.Context, in *ValidateTunnelPeerRequest, opts ...grpc.CallOption) (*ValidateTunnelPeerResponse, error) {
|
||||
out := new(ValidateTunnelPeerResponse)
|
||||
err := c.cc.Invoke(ctx, "/management.ProxyService/ValidateTunnelPeer", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ProxyServiceServer is the server API for ProxyService service.
|
||||
// All implementations must embed UnimplementedProxyServiceServer
|
||||
// for forward compatibility
|
||||
type ProxyServiceServer interface {
|
||||
GetMappingUpdate(*GetMappingUpdateRequest, ProxyService_GetMappingUpdateServer) error
|
||||
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
|
||||
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
|
||||
// stream and then sends an ack after each batch is fully processed.
|
||||
// Management waits for the ack before sending the next batch, providing
|
||||
// application-level back-pressure during large initial syncs.
|
||||
// Old proxies continue using GetMappingUpdate; old management servers
|
||||
// return Unimplemented for this RPC and proxies fall back.
|
||||
SyncMappings(ProxyService_SyncMappingsServer) error
|
||||
SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error)
|
||||
Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error)
|
||||
SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error)
|
||||
@@ -136,6 +200,14 @@ type ProxyServiceServer interface {
|
||||
// ValidateSession validates a session token and checks user access permissions.
|
||||
// Called by the proxy after receiving a session token from OIDC callback.
|
||||
ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error)
|
||||
// ValidateTunnelPeer resolves an inbound peer by its WireGuard tunnel IP and
|
||||
// checks the resolved user's access against the service's access_groups.
|
||||
// Acts as a fast-path equivalent of OIDC for requests originating on the
|
||||
// netbird mesh: when the source IP maps to a known peer in the calling
|
||||
// account and that peer is in the service's access_groups, the proxy can
|
||||
// issue a session cookie without redirecting through the OIDC flow.
|
||||
// Mirrors ValidateSession's response shape.
|
||||
ValidateTunnelPeer(context.Context, *ValidateTunnelPeerRequest) (*ValidateTunnelPeerResponse, error)
|
||||
mustEmbedUnimplementedProxyServiceServer()
|
||||
}
|
||||
|
||||
@@ -146,6 +218,9 @@ type UnimplementedProxyServiceServer struct {
|
||||
func (UnimplementedProxyServiceServer) GetMappingUpdate(*GetMappingUpdateRequest, ProxyService_GetMappingUpdateServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetMappingUpdate not implemented")
|
||||
}
|
||||
func (UnimplementedProxyServiceServer) SyncMappings(ProxyService_SyncMappingsServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SyncMappings not implemented")
|
||||
}
|
||||
func (UnimplementedProxyServiceServer) SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SendAccessLog not implemented")
|
||||
}
|
||||
@@ -164,6 +239,9 @@ func (UnimplementedProxyServiceServer) GetOIDCURL(context.Context, *GetOIDCURLRe
|
||||
func (UnimplementedProxyServiceServer) ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ValidateSession not implemented")
|
||||
}
|
||||
func (UnimplementedProxyServiceServer) ValidateTunnelPeer(context.Context, *ValidateTunnelPeerRequest) (*ValidateTunnelPeerResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ValidateTunnelPeer not implemented")
|
||||
}
|
||||
func (UnimplementedProxyServiceServer) mustEmbedUnimplementedProxyServiceServer() {}
|
||||
|
||||
// UnsafeProxyServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
@@ -198,6 +276,32 @@ func (x *proxyServiceGetMappingUpdateServer) Send(m *GetMappingUpdateResponse) e
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _ProxyService_SyncMappings_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(ProxyServiceServer).SyncMappings(&proxyServiceSyncMappingsServer{stream})
|
||||
}
|
||||
|
||||
type ProxyService_SyncMappingsServer interface {
|
||||
Send(*SyncMappingsResponse) error
|
||||
Recv() (*SyncMappingsRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type proxyServiceSyncMappingsServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *proxyServiceSyncMappingsServer) Send(m *SyncMappingsResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *proxyServiceSyncMappingsServer) Recv() (*SyncMappingsRequest, error) {
|
||||
m := new(SyncMappingsRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _ProxyService_SendAccessLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SendAccessLogRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -306,6 +410,24 @@ func _ProxyService_ValidateSession_Handler(srv interface{}, ctx context.Context,
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ProxyService_ValidateTunnelPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ValidateTunnelPeerRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ProxyServiceServer).ValidateTunnelPeer(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/management.ProxyService/ValidateTunnelPeer",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ProxyServiceServer).ValidateTunnelPeer(ctx, req.(*ValidateTunnelPeerRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// ProxyService_ServiceDesc is the grpc.ServiceDesc for ProxyService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -337,6 +459,10 @@ var ProxyService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ValidateSession",
|
||||
Handler: _ProxyService_ValidateSession_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ValidateTunnelPeer",
|
||||
Handler: _ProxyService_ValidateTunnelPeer_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
@@ -344,6 +470,12 @@ var ProxyService_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _ProxyService_GetMappingUpdate_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SyncMappings",
|
||||
Handler: _ProxyService_SyncMappings_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "proxy_service.proto",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user