[management] switch to libopenapi for managing of openapi-based api (#8056)

* use libopenapi OpenAPI generator

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* added a handler for v1alpha1/peers

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* wire up request validator

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* wired up spec-based validation

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* use sync validation; set base url to v1alpha1

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* moved stuff around, extracted runtime libopenapu deps into runtime_tooling

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* testing /peers path params

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* fixed tests

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* added user schemas and paths

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* use api validation in tests

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* post-merge fixes

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* deleted tmp command used to test validator integration

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* require request body in post/put requests in order to force validation

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* making linter happy

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* switch to api/v1alpha1

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* go mod tidy

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* keep the original order of middleware: metrics, cors, then auth

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* return 422 on validation errors

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* cleanups

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* more cleanups

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* v1alpha1 spec cleanups

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* no need for a double-pointer

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* more linter fixes

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* removed more double pointers

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* use di to inject api_v0 and api_v1 http routers

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* do not re-add middleware on repeated call to ApiHandler

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* calling ApiRouter() now also calls ApiV1Router()

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

---------

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
dmitri-netbird
2026-10-07 19:07:31 +02:00
committed by GitHub
parent 192d3d0344
commit 24cb7b75c2
25 changed files with 4509 additions and 64 deletions
+47 -5
View File
@@ -36,9 +36,11 @@ import (
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
"github.com/netbirdio/netbird/management/server/activity"
activitystore "github.com/netbirdio/netbird/management/server/activity/store"
"github.com/netbirdio/netbird/management/server/api/v1alpha1"
nbcache "github.com/netbirdio/netbird/management/server/cache"
nbContext "github.com/netbirdio/netbird/management/server/context"
nbhttp "github.com/netbirdio/netbird/management/server/http"
"github.com/netbirdio/netbird/management/server/http/middleware"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/telemetry"
@@ -47,7 +49,10 @@ import (
"github.com/netbirdio/netbird/util/crypt"
)
const apiPrefix = "/api"
const (
apiPrefix = "/api"
apiV1Prefix = "/api/v1alpha1"
)
var (
kaep = keepalive.EnforcementPolicy{
@@ -158,13 +163,31 @@ func (s *BaseServer) EventStore() activity.Store {
}
func (s *BaseServer) APIHandler() http.Handler {
return Create(s, func() http.Handler {
httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.Router(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.PermissionsManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager(), s.ReverseProxyDomainManager(), s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies, s.RateLimiter(), s.IsValidChildAccount, s.AgentNetworkManager(), nil)
_ = CreateNamed(s, "http_v1api", func() http.Handler {
apiv1Router := s.ApiV1Router()
_, err := v1alpha1.NewAPIV1Handler(context.Background(), apiv1Router, s.AccountManager(), s.NetworkMapController(), s.PermissionsManager())
if err != nil {
log.Fatalf("failed to create API handler: %v", err)
}
return httpAPIHandler
return apiv1Router
})
_ = CreateNamed(s, "http_v0api", func() http.Handler {
apiRouter := s.ApiRouter()
_, err := nbhttp.NewAPIHandler(
context.Background(), apiRouter, s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(),
s.GroupsManager(), s.GeoLocationManager(), s.PermissionsManager(), s.SettingsManager(), s.ZonesManager(),
s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager(), s.ReverseProxyDomainManager(),
s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies,
s.AgentNetworkManager(), nil)
if err != nil {
log.Fatalf("failed to create API handler: %v", err)
}
return apiRouter
})
return s.Router()
}
// IDPHandler returns the HTTP handler for the embedded IdP (Dex), or nil if
@@ -177,9 +200,28 @@ func (s *BaseServer) IDPHandler() http.Handler {
return cors.AllowAll().Handler(embeddedIdP.Handler())
}
// Router returns the root HTTP router with the shared API middleware applied.
func (s *BaseServer) Router() *mux.Router {
return Create(s, func() *mux.Router {
return mux.NewRouter().PathPrefix(apiPrefix).Subrouter()
router := mux.NewRouter()
router.Use(middleware.BuildMiddleware(s.RateLimiter(), s.AuthManager(), s.AccountManager(), s.Metrics(), s.IsValidChildAccount)...)
return router
})
}
// ApiV1Router returns the subrouter for the versioned API under apiV1Prefix.
func (s *BaseServer) ApiV1Router() *mux.Router {
return CreateNamed(s, "apiv1_router", func() *mux.Router {
return s.Router().PathPrefix(apiV1Prefix).Subrouter()
})
}
// ApiRouter returns the subrouter for the unversioned API under apiPrefix.
func (s *BaseServer) ApiRouter() *mux.Router {
return CreateNamed(s, "apiv0_router", func() *mux.Router {
// The nested versioned prefix must be registered before the broader apiPrefix.
s.ApiV1Router()
return s.Router().PathPrefix(apiPrefix).Subrouter()
})
}
@@ -1,5 +1,3 @@
//go:build integration
package grpc
import (
@@ -195,7 +193,7 @@ func TestValidateSession_UserAllowed(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "test-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck
})
require.NoError(t, err)
@@ -216,7 +214,7 @@ func TestValidateSession_UserNotInAllowedGroup(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "restricted-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck
})
require.NoError(t, err)
@@ -240,7 +238,7 @@ func TestValidateSession_PendingApprovalUserDenied(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "restricted-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck
})
require.NoError(t, err)
@@ -265,7 +263,7 @@ func TestValidateSession_PendingApprovalUserInAllUsersGroupDenied(t *testing.T)
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "all-users-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck
})
require.NoError(t, err)
@@ -288,7 +286,7 @@ func TestValidateSession_BlockedUserDenied(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "restricted-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck
})
require.NoError(t, err)
@@ -312,7 +310,7 @@ func TestValidateSession_UserAllowedAfterApproval(t *testing.T) {
token := createSessionToken(t, proxy.SessionPrivateKey, pendingUserID, "restricted-proxy.example.com")
req := &proto.ValidateSessionRequest{
Domain: "restricted-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck,
}
resp, err := setup.proxyService.ValidateSession(ctx, req)
@@ -345,7 +343,7 @@ func TestValidateSession_UserInDifferentAccount(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "test-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck,
})
require.NoError(t, err)
@@ -364,7 +362,7 @@ func TestValidateSession_UserNotFound(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "test-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck,
})
require.NoError(t, err)
@@ -383,7 +381,7 @@ func TestValidateSession_ProxyNotFound(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "unknown-proxy.example.com",
SessionToken: token,
SessionToken: token, //nolint:staticcheck,
})
require.NoError(t, err)
@@ -397,7 +395,7 @@ func TestValidateSession_InvalidToken(t *testing.T) {
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
Domain: "test-proxy.example.com",
SessionToken: "invalid-token",
SessionToken: "invalid-token", //nolint:staticcheck,
})
require.NoError(t, err)
@@ -410,7 +408,7 @@ func TestValidateSession_MissingDomain(t *testing.T) {
defer setup.cleanup()
resp, err := setup.proxyService.ValidateSession(context.Background(), &proto.ValidateSessionRequest{
SessionToken: "some-token",
SessionToken: "some-token", //nolint:staticcheck,
})
require.NoError(t, err)
@@ -491,15 +489,15 @@ func (m *testValidateSessionServiceManager) GetAllServices(_ context.Context, _,
}
func (m *testValidateSessionServiceManager) GetService(_ context.Context, _, _, _ string) (*service.Service, error) {
return nil, nil
return nil, nil //nolint:nilnil
}
func (m *testValidateSessionServiceManager) CreateService(_ context.Context, _, _ string, _ *service.Service) (*service.Service, error) {
return nil, nil
return nil, nil //nolint:nilnil
}
func (m *testValidateSessionServiceManager) UpdateService(_ context.Context, _, _ string, _ *service.Service) (*service.Service, error) {
return nil, nil
return nil, nil //nolint:nilnil
}
func (m *testValidateSessionServiceManager) DeleteService(_ context.Context, _, _, _ string) error {
@@ -543,7 +541,7 @@ func (m *testValidateSessionServiceManager) GetServiceIDByTargetID(_ context.Con
}
func (m *testValidateSessionServiceManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *service.ExposeServiceRequest) (*service.ExposeServiceResponse, error) {
return nil, nil
return nil, nil //nolint:nilnil
}
func (m *testValidateSessionServiceManager) RenewServiceFromPeer(_ context.Context, _, _, _ string) error {
@@ -571,7 +569,7 @@ func (m *testValidateSessionServiceManager) DeleteAccountCluster(_ context.Conte
type testValidateSessionProxyManager struct{}
func (m *testValidateSessionProxyManager) Connect(_ context.Context, _, _, _, _, _ string, _ *string, _ *proxy.Capabilities) (*proxy.Proxy, error) {
return nil, nil
return nil, nil //nolint:nilnil
}
func (m *testValidateSessionProxyManager) Disconnect(_ context.Context, _, _ string) error {
@@ -603,7 +601,7 @@ func (m *testValidateSessionProxyManager) CleanupStale(_ context.Context, _ time
}
func (m *testValidateSessionProxyManager) GetAccountProxy(_ context.Context, _ string) (*proxy.Proxy, error) {
return nil, nil
return nil, nil //nolint:nilnil
}
func (m *testValidateSessionProxyManager) CountAccountProxies(_ context.Context, _ string) (int64, error) {
@@ -634,6 +632,10 @@ func (m *testValidateSessionProxyManager) ClusterSupportsPrivate(_ context.Conte
return nil
}
func (m *testValidateSessionProxyManager) ClusterAllProxiesPrivate(_ context.Context, _ string) *bool {
return nil
}
func (m *testValidateSessionProxyManager) ClusterSupportsSessionCode(_ context.Context, _ string) bool {
return false
}