mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-12 08:29:06 +02:00
Add transparent proxy inspection engine with envoy sidecar support
This commit is contained in:
@@ -113,6 +113,10 @@ type Manager interface {
|
||||
SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error)
|
||||
DeletePostureChecks(ctx context.Context, accountID, postureChecksID, userID string) error
|
||||
ListPostureChecks(ctx context.Context, accountID, userID string) ([]*posture.Checks, error)
|
||||
GetInspectionPolicy(ctx context.Context, accountID, policyID, userID string) (*types.InspectionPolicy, error)
|
||||
SaveInspectionPolicy(ctx context.Context, accountID, userID string, policy *types.InspectionPolicy, create bool) (*types.InspectionPolicy, error)
|
||||
DeleteInspectionPolicy(ctx context.Context, accountID, policyID, userID string) error
|
||||
ListInspectionPolicies(ctx context.Context, accountID, userID string) ([]*types.InspectionPolicy, error)
|
||||
GetIdpManager() idp.Manager
|
||||
UpdateIntegratedValidator(ctx context.Context, accountID, userID, validator string, groups []string) error
|
||||
GroupValidation(ctx context.Context, accountId string, groups []string) (bool, error)
|
||||
|
||||
@@ -51,6 +51,7 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/instance"
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/networks"
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/peers"
|
||||
inspectionHandler "github.com/netbirdio/netbird/management/server/http/handlers/inspection"
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/policies"
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/routes"
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/setup_keys"
|
||||
@@ -162,6 +163,7 @@ func NewAPIHandler(ctx context.Context, accountManager account.Manager, networks
|
||||
setup_keys.AddEndpoints(accountManager, router)
|
||||
policies.AddEndpoints(accountManager, LocationManager, router)
|
||||
policies.AddPostureCheckEndpoints(accountManager, LocationManager, router)
|
||||
inspectionHandler.AddEndpoints(accountManager, router)
|
||||
policies.AddLocationsEndpoints(accountManager, LocationManager, permissionsManager, router)
|
||||
groups.AddEndpoints(accountManager, router)
|
||||
routes.AddEndpoints(accountManager, router)
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
package inspection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||
"github.com/netbirdio/netbird/shared/management/http/util"
|
||||
"github.com/netbirdio/netbird/shared/management/status"
|
||||
)
|
||||
|
||||
// Handler manages inspection policy CRUD operations.
|
||||
type Handler struct {
|
||||
accountManager account.Manager
|
||||
}
|
||||
|
||||
// AddEndpoints registers the inspection policy API endpoints.
|
||||
func AddEndpoints(accountManager account.Manager, router *mux.Router) {
|
||||
h := &Handler{accountManager: accountManager}
|
||||
router.HandleFunc("/inspection-policies", h.list).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/inspection-policies", h.create).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/inspection-policies/{policyId}", h.get).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/inspection-policies/{policyId}", h.update).Methods("PUT", "OPTIONS")
|
||||
router.HandleFunc("/inspection-policies/{policyId}", h.remove).Methods("DELETE", "OPTIONS")
|
||||
}
|
||||
|
||||
func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
policies, err := h.accountManager.ListInspectionPolicies(r.Context(), userAuth.AccountId, userAuth.UserId)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]*api.InspectionPolicy, 0, len(policies))
|
||||
for _, p := range policies {
|
||||
result = append(result, toAPIResponse(p))
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, result)
|
||||
}
|
||||
|
||||
func (h *Handler) create(w http.ResponseWriter, r *http.Request) {
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.InspectionPolicyMinimum
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "decode request: %v", err), w)
|
||||
return
|
||||
}
|
||||
|
||||
policy := fromAPIRequest(&req)
|
||||
|
||||
saved, err := h.accountManager.SaveInspectionPolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, true)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toAPIResponse(saved))
|
||||
}
|
||||
|
||||
func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
policyID := mux.Vars(r)["policyId"]
|
||||
|
||||
policy, err := h.accountManager.GetInspectionPolicy(r.Context(), userAuth.AccountId, policyID, userAuth.UserId)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toAPIResponse(policy))
|
||||
}
|
||||
|
||||
func (h *Handler) update(w http.ResponseWriter, r *http.Request) {
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
policyID := mux.Vars(r)["policyId"]
|
||||
|
||||
var req api.InspectionPolicyMinimum
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "decode request: %v", err), w)
|
||||
return
|
||||
}
|
||||
|
||||
policy := fromAPIRequest(&req)
|
||||
policy.ID = policyID
|
||||
|
||||
saved, err := h.accountManager.SaveInspectionPolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, false)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toAPIResponse(saved))
|
||||
}
|
||||
|
||||
func (h *Handler) remove(w http.ResponseWriter, r *http.Request) {
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
policyID := mux.Vars(r)["policyId"]
|
||||
|
||||
if err := h.accountManager.DeleteInspectionPolicy(r.Context(), userAuth.AccountId, policyID, userAuth.UserId); err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, struct{}{})
|
||||
}
|
||||
|
||||
func toAPIResponse(p *types.InspectionPolicy) *api.InspectionPolicy {
|
||||
id := p.ID
|
||||
resp := &api.InspectionPolicy{
|
||||
Id: &id,
|
||||
Name: p.Name,
|
||||
Enabled: p.Enabled,
|
||||
}
|
||||
|
||||
if p.Description != "" {
|
||||
resp.Description = &p.Description
|
||||
}
|
||||
if p.Mode != "" {
|
||||
mode := api.InspectionPolicyMode(p.Mode)
|
||||
resp.Mode = &mode
|
||||
}
|
||||
if p.ExternalURL != "" {
|
||||
resp.ExternalUrl = &p.ExternalURL
|
||||
}
|
||||
if p.DefaultAction != "" {
|
||||
da := api.InspectionPolicyDefaultAction(p.DefaultAction)
|
||||
resp.DefaultAction = &da
|
||||
}
|
||||
if len(p.RedirectPorts) > 0 {
|
||||
resp.RedirectPorts = &p.RedirectPorts
|
||||
}
|
||||
if p.CACertPEM != "" {
|
||||
resp.CaCertPem = &p.CACertPEM
|
||||
}
|
||||
if p.CAKeyPEM != "" {
|
||||
resp.CaKeyPem = &p.CAKeyPEM
|
||||
}
|
||||
if p.EnvoyBinaryPath != "" {
|
||||
resp.EnvoyBinaryPath = &p.EnvoyBinaryPath
|
||||
}
|
||||
if p.EnvoyAdminPort != 0 {
|
||||
port := int(p.EnvoyAdminPort)
|
||||
resp.EnvoyAdminPort = &port
|
||||
}
|
||||
if p.ICAP != nil {
|
||||
resp.Icap = &api.InspectionICAPConfig{}
|
||||
if p.ICAP.ReqModURL != "" {
|
||||
resp.Icap.ReqmodUrl = &p.ICAP.ReqModURL
|
||||
}
|
||||
if p.ICAP.RespModURL != "" {
|
||||
resp.Icap.RespmodUrl = &p.ICAP.RespModURL
|
||||
}
|
||||
if p.ICAP.MaxConnections != 0 {
|
||||
resp.Icap.MaxConnections = &p.ICAP.MaxConnections
|
||||
}
|
||||
}
|
||||
|
||||
rules := make([]api.InspectionPolicyRule, 0, len(p.Rules))
|
||||
for _, r := range p.Rules {
|
||||
rule := api.InspectionPolicyRule{
|
||||
Action: api.InspectionPolicyRuleAction(r.Action),
|
||||
Priority: r.Priority,
|
||||
}
|
||||
if len(r.Domains) > 0 {
|
||||
rule.Domains = &r.Domains
|
||||
}
|
||||
if len(r.Networks) > 0 {
|
||||
rule.Networks = &r.Networks
|
||||
}
|
||||
if len(r.Protocols) > 0 {
|
||||
protos := make([]api.InspectionPolicyRuleProtocols, len(r.Protocols))
|
||||
for i, proto := range r.Protocols {
|
||||
protos[i] = api.InspectionPolicyRuleProtocols(proto)
|
||||
}
|
||||
rule.Protocols = &protos
|
||||
}
|
||||
if len(r.Paths) > 0 {
|
||||
rule.Paths = &r.Paths
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
resp.Rules = rules
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func fromAPIRequest(req *api.InspectionPolicyMinimum) *types.InspectionPolicy {
|
||||
p := &types.InspectionPolicy{
|
||||
Name: req.Name,
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
|
||||
if req.Description != nil {
|
||||
p.Description = *req.Description
|
||||
}
|
||||
if req.Mode != nil {
|
||||
p.Mode = string(*req.Mode)
|
||||
}
|
||||
if req.ExternalUrl != nil {
|
||||
p.ExternalURL = *req.ExternalUrl
|
||||
}
|
||||
if req.DefaultAction != nil {
|
||||
p.DefaultAction = string(*req.DefaultAction)
|
||||
}
|
||||
if req.RedirectPorts != nil {
|
||||
p.RedirectPorts = *req.RedirectPorts
|
||||
}
|
||||
if req.CaCertPem != nil {
|
||||
p.CACertPEM = *req.CaCertPem
|
||||
}
|
||||
if req.CaKeyPem != nil {
|
||||
p.CAKeyPEM = *req.CaKeyPem
|
||||
}
|
||||
if req.EnvoyBinaryPath != nil {
|
||||
p.EnvoyBinaryPath = *req.EnvoyBinaryPath
|
||||
}
|
||||
if req.EnvoyAdminPort != nil {
|
||||
p.EnvoyAdminPort = *req.EnvoyAdminPort
|
||||
}
|
||||
if req.Icap != nil {
|
||||
p.ICAP = &types.InspectionICAPConfig{}
|
||||
if req.Icap.ReqmodUrl != nil {
|
||||
p.ICAP.ReqModURL = *req.Icap.ReqmodUrl
|
||||
}
|
||||
if req.Icap.RespmodUrl != nil {
|
||||
p.ICAP.RespModURL = *req.Icap.RespmodUrl
|
||||
}
|
||||
if req.Icap.MaxConnections != nil {
|
||||
p.ICAP.MaxConnections = *req.Icap.MaxConnections
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range req.Rules {
|
||||
rule := types.InspectionPolicyRule{
|
||||
Action: string(r.Action),
|
||||
Priority: r.Priority,
|
||||
}
|
||||
if r.Domains != nil {
|
||||
rule.Domains = *r.Domains
|
||||
}
|
||||
if r.Networks != nil {
|
||||
rule.Networks = *r.Networks
|
||||
}
|
||||
if r.Protocols != nil {
|
||||
for _, proto := range *r.Protocols {
|
||||
rule.Protocols = append(rule.Protocols, string(proto))
|
||||
}
|
||||
}
|
||||
if r.Paths != nil {
|
||||
rule.Paths = *r.Paths
|
||||
}
|
||||
p.Rules = append(p.Rules, rule)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
@@ -281,6 +281,10 @@ func (h *handler) savePolicy(w http.ResponseWriter, r *http.Request, accountID s
|
||||
policy.SourcePostureChecks = *req.SourcePostureChecks
|
||||
}
|
||||
|
||||
if req.InspectionPolicies != nil {
|
||||
policy.InspectionPolicies = *req.InspectionPolicies
|
||||
}
|
||||
|
||||
policy, err := h.accountManager.SavePolicy(r.Context(), accountID, userID, policy, create)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
@@ -377,6 +381,7 @@ func toPolicyResponse(groups []*types.Group, policy *types.Policy) *api.Policy {
|
||||
Description: &policy.Description,
|
||||
Enabled: policy.Enabled,
|
||||
SourcePostureChecks: policy.SourcePostureChecks,
|
||||
InspectionPolicies: &policy.InspectionPolicies,
|
||||
}
|
||||
for _, r := range policy.Rules {
|
||||
rID := r.ID
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
// GetInspectionPolicy returns an inspection policy by ID.
|
||||
func (am *DefaultAccountManager) GetInspectionPolicy(ctx context.Context, accountID, policyID, userID string) (*types.InspectionPolicy, error) {
|
||||
return am.Store.GetInspectionPolicyByID(ctx, store.LockingStrengthShare, accountID, policyID)
|
||||
}
|
||||
|
||||
// SaveInspectionPolicy creates or updates an inspection policy.
|
||||
func (am *DefaultAccountManager) SaveInspectionPolicy(ctx context.Context, accountID, userID string, policy *types.InspectionPolicy, create bool) (*types.InspectionPolicy, error) {
|
||||
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
if create {
|
||||
policy.ID = xid.New().String()
|
||||
}
|
||||
policy.AccountID = accountID
|
||||
return transaction.SaveInspectionPolicy(ctx, store.LockingStrengthUpdate, policy)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
am.UpdateAccountPeers(ctx, accountID)
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// DeleteInspectionPolicy removes an inspection policy.
|
||||
func (am *DefaultAccountManager) DeleteInspectionPolicy(ctx context.Context, accountID, policyID, userID string) error {
|
||||
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
return transaction.DeleteInspectionPolicy(ctx, store.LockingStrengthUpdate, accountID, policyID)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
am.UpdateAccountPeers(ctx, accountID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListInspectionPolicies returns all inspection policies for the account.
|
||||
func (am *DefaultAccountManager) ListInspectionPolicies(ctx context.Context, accountID, userID string) ([]*types.InspectionPolicy, error) {
|
||||
return am.Store.GetAccountInspectionPolicies(ctx, store.LockingStrengthShare, accountID)
|
||||
}
|
||||
@@ -909,6 +909,26 @@ func (am *MockAccountManager) ListPostureChecks(ctx context.Context, accountID,
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented")
|
||||
}
|
||||
|
||||
// GetInspectionPolicy mocks GetInspectionPolicy of the AccountManager interface
|
||||
func (am *MockAccountManager) GetInspectionPolicy(ctx context.Context, accountID, policyID, userID string) (*types.InspectionPolicy, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetInspectionPolicy is not implemented")
|
||||
}
|
||||
|
||||
// SaveInspectionPolicy mocks SaveInspectionPolicy of the AccountManager interface
|
||||
func (am *MockAccountManager) SaveInspectionPolicy(ctx context.Context, accountID, userID string, policy *types.InspectionPolicy, create bool) (*types.InspectionPolicy, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SaveInspectionPolicy is not implemented")
|
||||
}
|
||||
|
||||
// DeleteInspectionPolicy mocks DeleteInspectionPolicy of the AccountManager interface
|
||||
func (am *MockAccountManager) DeleteInspectionPolicy(ctx context.Context, accountID, policyID, userID string) error {
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteInspectionPolicy is not implemented")
|
||||
}
|
||||
|
||||
// ListInspectionPolicies mocks ListInspectionPolicies of the AccountManager interface
|
||||
func (am *MockAccountManager) ListInspectionPolicies(ctx context.Context, accountID, userID string) ([]*types.InspectionPolicy, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListInspectionPolicies is not implemented")
|
||||
}
|
||||
|
||||
// GetIdpManager mocks GetIdpManager of the AccountManager interface
|
||||
func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
||||
if am.GetIdpManagerFunc != nil {
|
||||
|
||||
@@ -18,6 +18,28 @@ type NetworkRouter struct {
|
||||
Masquerade bool
|
||||
Metric int
|
||||
Enabled bool
|
||||
Inspection *InspectionConfig `gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// InspectionConfig holds traffic inspection settings for a routing peer.
|
||||
// L7 inspection rules are stored separately as ProxyRule entities.
|
||||
type InspectionConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Mode string `json:"mode"` // "builtin" or "external"
|
||||
ExternalURL string `json:"external_url"`
|
||||
DefaultAction string `json:"default_action"` // "allow", "block", "inspect"
|
||||
RedirectPorts []int `json:"redirect_ports"`
|
||||
ICAP *InspectionICAP `json:"icap,omitempty"`
|
||||
CACertPEM string `json:"ca_cert_pem,omitempty"`
|
||||
CAKeyPEM string `json:"ca_key_pem,omitempty"`
|
||||
ListenPort int `json:"listen_port"`
|
||||
}
|
||||
|
||||
// InspectionICAP holds ICAP service configuration.
|
||||
type InspectionICAP struct {
|
||||
ReqModURL string `json:"reqmod_url"`
|
||||
RespModURL string `json:"respmod_url"`
|
||||
MaxConnections int `json:"max_connections"`
|
||||
}
|
||||
|
||||
func NewNetworkRouter(accountID string, networkID string, peer string, peerGroups []string, masquerade bool, metric int, enabled bool) (*NetworkRouter, error) {
|
||||
@@ -38,7 +60,7 @@ func NewNetworkRouter(accountID string, networkID string, peer string, peerGroup
|
||||
}
|
||||
|
||||
func (n *NetworkRouter) ToAPIResponse() *api.NetworkRouter {
|
||||
return &api.NetworkRouter{
|
||||
resp := &api.NetworkRouter{
|
||||
Id: n.ID,
|
||||
Peer: &n.Peer,
|
||||
PeerGroups: &n.PeerGroups,
|
||||
@@ -46,6 +68,12 @@ func (n *NetworkRouter) ToAPIResponse() *api.NetworkRouter {
|
||||
Metric: n.Metric,
|
||||
Enabled: n.Enabled,
|
||||
}
|
||||
|
||||
if n.Inspection != nil {
|
||||
resp.Inspection = inspectionToAPI(n.Inspection)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (n *NetworkRouter) FromAPIRequest(req *api.NetworkRouterRequest) {
|
||||
@@ -60,10 +88,11 @@ func (n *NetworkRouter) FromAPIRequest(req *api.NetworkRouterRequest) {
|
||||
n.Masquerade = req.Masquerade
|
||||
n.Metric = req.Metric
|
||||
n.Enabled = req.Enabled
|
||||
n.Inspection = inspectionFromAPI(req.Inspection)
|
||||
}
|
||||
|
||||
func (n *NetworkRouter) Copy() *NetworkRouter {
|
||||
return &NetworkRouter{
|
||||
c := &NetworkRouter{
|
||||
ID: n.ID,
|
||||
NetworkID: n.NetworkID,
|
||||
AccountID: n.AccountID,
|
||||
@@ -73,6 +102,108 @@ func (n *NetworkRouter) Copy() *NetworkRouter {
|
||||
Metric: n.Metric,
|
||||
Enabled: n.Enabled,
|
||||
}
|
||||
if n.Inspection != nil {
|
||||
insp := *n.Inspection
|
||||
c.Inspection = &insp
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func inspectionToAPI(c *InspectionConfig) *api.RouterInspectionConfig {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mode := api.RouterInspectionConfigMode(c.Mode)
|
||||
defaultAction := api.RouterInspectionConfigDefaultAction(c.DefaultAction)
|
||||
|
||||
resp := &api.RouterInspectionConfig{
|
||||
Enabled: c.Enabled,
|
||||
Mode: &mode,
|
||||
DefaultAction: &defaultAction,
|
||||
}
|
||||
|
||||
if c.ExternalURL != "" {
|
||||
resp.ExternalUrl = &c.ExternalURL
|
||||
}
|
||||
|
||||
if len(c.RedirectPorts) > 0 {
|
||||
resp.RedirectPorts = &c.RedirectPorts
|
||||
}
|
||||
|
||||
if c.CACertPEM != "" {
|
||||
resp.CaCertPem = &c.CACertPEM
|
||||
}
|
||||
if c.CAKeyPEM != "" {
|
||||
resp.CaKeyPem = &c.CAKeyPEM
|
||||
}
|
||||
|
||||
if c.ICAP != nil {
|
||||
icap := api.InspectionICAPConfig{}
|
||||
if c.ICAP.ReqModURL != "" {
|
||||
icap.ReqmodUrl = &c.ICAP.ReqModURL
|
||||
}
|
||||
if c.ICAP.RespModURL != "" {
|
||||
icap.RespmodUrl = &c.ICAP.RespModURL
|
||||
}
|
||||
if c.ICAP.MaxConnections > 0 {
|
||||
icap.MaxConnections = &c.ICAP.MaxConnections
|
||||
}
|
||||
resp.Icap = &icap
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func inspectionFromAPI(c *api.RouterInspectionConfig) *InspectionConfig {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
insp := &InspectionConfig{
|
||||
Enabled: c.Enabled,
|
||||
}
|
||||
|
||||
if c.Mode != nil {
|
||||
insp.Mode = string(*c.Mode)
|
||||
}
|
||||
if c.DefaultAction != nil {
|
||||
insp.DefaultAction = string(*c.DefaultAction)
|
||||
}
|
||||
if c.ExternalUrl != nil {
|
||||
insp.ExternalURL = *c.ExternalUrl
|
||||
}
|
||||
if c.RedirectPorts != nil {
|
||||
insp.RedirectPorts = *c.RedirectPorts
|
||||
}
|
||||
if c.CaCertPem != nil {
|
||||
insp.CACertPEM = *c.CaCertPem
|
||||
}
|
||||
if c.CaKeyPem != nil {
|
||||
insp.CAKeyPEM = *c.CaKeyPem
|
||||
}
|
||||
|
||||
if c.Icap != nil {
|
||||
insp.ICAP = &InspectionICAP{}
|
||||
if c.Icap.ReqmodUrl != nil {
|
||||
insp.ICAP.ReqModURL = *c.Icap.ReqmodUrl
|
||||
}
|
||||
if c.Icap.RespmodUrl != nil {
|
||||
insp.ICAP.RespModURL = *c.Icap.RespmodUrl
|
||||
}
|
||||
if c.Icap.MaxConnections != nil {
|
||||
insp.ICAP.MaxConnections = *c.Icap.MaxConnections
|
||||
}
|
||||
}
|
||||
|
||||
return insp
|
||||
}
|
||||
|
||||
func derefInt(p *int) int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
return *p
|
||||
}
|
||||
|
||||
func (n *NetworkRouter) EventMeta(network *types.Network) map[string]any {
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"slices"
|
||||
|
||||
"github.com/rs/xid"
|
||||
|
||||
@@ -150,6 +151,12 @@ func arePolicyChangesAffectPeers(ctx context.Context, transaction store.Store, a
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Inspection policy changes always affect peers since they control
|
||||
// the transparent proxy config pushed in the network map.
|
||||
if !slices.Equal(existingPolicy.InspectionPolicies, policy.InspectionPolicies) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, rule := range existingPolicy.Rules {
|
||||
if rule.SourceResource.Type != "" || rule.DestinationResource.Type != "" {
|
||||
return true, nil
|
||||
|
||||
@@ -134,7 +134,7 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine types.Engine, met
|
||||
&installation{}, &types.ExtraSettings{}, &posture.Checks{}, &nbpeer.NetworkAddress{},
|
||||
&networkTypes.Network{}, &routerTypes.NetworkRouter{}, &resourceTypes.NetworkResource{}, &types.AccountOnboarding{},
|
||||
&types.Job{}, &zones.Zone{}, &records.Record{}, &types.UserInviteRecord{}, &rpservice.Service{}, &rpservice.Target{}, &domain.Domain{},
|
||||
&accesslogs.AccessLogEntry{}, &proxy.Proxy{},
|
||||
&accesslogs.AccessLogEntry{}, &proxy.Proxy{}, &types.InspectionPolicy{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auto migratePreAuto: %w", err)
|
||||
@@ -1115,6 +1115,7 @@ func (s *SqlStore) getAccountGorm(ctx context.Context, accountID string) (*types
|
||||
Preload("RoutesG").
|
||||
Preload("NameServerGroupsG").
|
||||
Preload("PostureChecks").
|
||||
Preload("InspectionPolicies").
|
||||
Preload("Networks").
|
||||
Preload("NetworkRouters").
|
||||
Preload("NetworkResources").
|
||||
@@ -3877,6 +3878,63 @@ func (s *SqlStore) DeletePostureChecks(ctx context.Context, accountID, postureCh
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAccountInspectionPolicies returns all inspection policies for the account.
|
||||
// CA cert and key are decrypted after loading.
|
||||
func (s *SqlStore) GetAccountInspectionPolicies(ctx context.Context, _ LockingStrength, accountID string) ([]*types.InspectionPolicy, error) {
|
||||
var policies []*types.InspectionPolicy
|
||||
result := s.db.Where("account_id = ?", accountID).Find(&policies)
|
||||
if result.Error != nil {
|
||||
return nil, status.Errorf(status.Internal, "failed to get inspection policies: %s", result.Error)
|
||||
}
|
||||
for _, p := range policies {
|
||||
if err := p.DecryptSensitiveData(s.fieldEncrypt); err != nil {
|
||||
return nil, fmt.Errorf("decrypt inspection policy %s: %w", p.ID, err)
|
||||
}
|
||||
}
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// GetInspectionPolicyByID returns an inspection policy by ID.
|
||||
// CA cert and key are decrypted after loading.
|
||||
func (s *SqlStore) GetInspectionPolicyByID(ctx context.Context, _ LockingStrength, accountID, policyID string) (*types.InspectionPolicy, error) {
|
||||
var policy types.InspectionPolicy
|
||||
result := s.db.Where(accountAndIDQueryCondition, accountID, policyID).First(&policy)
|
||||
if result.Error != nil {
|
||||
return nil, status.Errorf(status.Internal, "failed to get inspection policy: %s", result.Error)
|
||||
}
|
||||
if err := policy.DecryptSensitiveData(s.fieldEncrypt); err != nil {
|
||||
return nil, fmt.Errorf("decrypt inspection policy %s: %w", policyID, err)
|
||||
}
|
||||
return &policy, nil
|
||||
}
|
||||
|
||||
// SaveInspectionPolicy saves an inspection policy to the database.
|
||||
// CA cert and key are encrypted before storage.
|
||||
func (s *SqlStore) SaveInspectionPolicy(ctx context.Context, _ LockingStrength, policy *types.InspectionPolicy) error {
|
||||
if err := policy.EncryptSensitiveData(s.fieldEncrypt); err != nil {
|
||||
return fmt.Errorf("encrypt inspection policy: %w", err)
|
||||
}
|
||||
result := s.db.Save(policy)
|
||||
if result.Error != nil {
|
||||
log.WithContext(ctx).Errorf("failed to save inspection policy: %s", result.Error)
|
||||
return status.Errorf(status.Internal, "failed to save inspection policy")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteInspectionPolicy deletes an inspection policy from the database.
|
||||
func (s *SqlStore) DeleteInspectionPolicy(ctx context.Context, _ LockingStrength, accountID, policyID string) error {
|
||||
result := s.db.Delete(&types.InspectionPolicy{}, accountAndIDQueryCondition, accountID, policyID)
|
||||
if result.Error != nil {
|
||||
log.WithContext(ctx).Errorf("failed to delete inspection policy: %s", result.Error)
|
||||
return status.Errorf(status.Internal, "failed to delete inspection policy")
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return status.Errorf(status.NotFound, "inspection policy %s not found", policyID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAccountRoutes retrieves network routes for an account.
|
||||
func (s *SqlStore) GetAccountRoutes(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*route.Route, error) {
|
||||
tx := s.db
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
func TestSqlStore_InspectionPolicyCRUD(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("The SQLite store is not properly supported by Windows yet")
|
||||
}
|
||||
|
||||
runTestForAllEngines(t, "", func(t *testing.T, store Store) {
|
||||
ctx := context.Background()
|
||||
accountID := "test-account-inspection"
|
||||
|
||||
// Create account first
|
||||
account := newAccountWithId(ctx, accountID, "test-user", "example.com")
|
||||
err := store.SaveAccount(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create
|
||||
policy := &types.InspectionPolicy{
|
||||
ID: "ip-1",
|
||||
AccountID: accountID,
|
||||
Name: "Block gambling",
|
||||
Description: "Block all gambling sites",
|
||||
Enabled: true,
|
||||
Rules: []types.InspectionPolicyRule{
|
||||
{
|
||||
Domains: []string{"*.gambling.com", "*.betting.com"},
|
||||
Action: "block",
|
||||
Priority: 1,
|
||||
Protocols: []string{"https"},
|
||||
},
|
||||
{
|
||||
Domains: []string{"*.malware.org"},
|
||||
Networks: []string{"10.0.0.0/8"},
|
||||
Action: "block",
|
||||
Priority: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
err = store.SaveInspectionPolicy(ctx, LockingStrengthUpdate, policy)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Read
|
||||
got, err := store.GetInspectionPolicyByID(ctx, LockingStrengthShare, accountID, "ip-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Block gambling", got.Name)
|
||||
assert.Equal(t, "Block all gambling sites", got.Description)
|
||||
assert.True(t, got.Enabled)
|
||||
require.Len(t, got.Rules, 2)
|
||||
assert.Equal(t, []string{"*.gambling.com", "*.betting.com"}, got.Rules[0].Domains)
|
||||
assert.Equal(t, "block", got.Rules[0].Action)
|
||||
assert.Equal(t, []string{"https"}, got.Rules[0].Protocols)
|
||||
assert.Equal(t, []string{"10.0.0.0/8"}, got.Rules[1].Networks)
|
||||
|
||||
// List
|
||||
policies, err := store.GetAccountInspectionPolicies(ctx, LockingStrengthShare, accountID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
assert.Equal(t, "ip-1", policies[0].ID)
|
||||
|
||||
// Update
|
||||
policy.Name = "Block gambling updated"
|
||||
policy.Rules = append(policy.Rules, types.InspectionPolicyRule{
|
||||
Domains: []string{"*.phishing.net"},
|
||||
Action: "inspect",
|
||||
Priority: 3,
|
||||
})
|
||||
err = store.SaveInspectionPolicy(ctx, LockingStrengthUpdate, policy)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err = store.GetInspectionPolicyByID(ctx, LockingStrengthShare, accountID, "ip-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Block gambling updated", got.Name)
|
||||
require.Len(t, got.Rules, 3)
|
||||
assert.Equal(t, "inspect", got.Rules[2].Action)
|
||||
|
||||
// Delete
|
||||
err = store.DeleteInspectionPolicy(ctx, LockingStrengthUpdate, accountID, "ip-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.GetInspectionPolicyByID(ctx, LockingStrengthShare, accountID, "ip-1")
|
||||
assert.Error(t, err)
|
||||
|
||||
policies, err = store.GetAccountInspectionPolicies(ctx, LockingStrengthShare, accountID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, policies)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlStore_InspectionPolicyNotFound(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("The SQLite store is not properly supported by Windows yet")
|
||||
}
|
||||
|
||||
runTestForAllEngines(t, "", func(t *testing.T, store Store) {
|
||||
ctx := context.Background()
|
||||
accountID := "test-account-no-ip"
|
||||
|
||||
account := newAccountWithId(ctx, accountID, "test-user", "example.com")
|
||||
err := store.SaveAccount(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.GetInspectionPolicyByID(ctx, LockingStrengthShare, accountID, "nonexistent")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlStore_InspectionPolicyIsolation(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("The SQLite store is not properly supported by Windows yet")
|
||||
}
|
||||
|
||||
runTestForAllEngines(t, "", func(t *testing.T, store Store) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create two accounts
|
||||
acc1 := newAccountWithId(ctx, "acc-1", "user-1", "one.com")
|
||||
acc2 := newAccountWithId(ctx, "acc-2", "user-2", "two.com")
|
||||
require.NoError(t, store.SaveAccount(ctx, acc1))
|
||||
require.NoError(t, store.SaveAccount(ctx, acc2))
|
||||
|
||||
// Save policy for acc-1
|
||||
policy := &types.InspectionPolicy{
|
||||
ID: "ip-acc1",
|
||||
AccountID: "acc-1",
|
||||
Name: "Account 1 policy",
|
||||
Enabled: true,
|
||||
Rules: []types.InspectionPolicyRule{{Action: "block", Priority: 1}},
|
||||
}
|
||||
require.NoError(t, store.SaveInspectionPolicy(ctx, LockingStrengthUpdate, policy))
|
||||
|
||||
// acc-2 should not see acc-1's policy
|
||||
policies, err := store.GetAccountInspectionPolicies(ctx, LockingStrengthShare, "acc-2")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, policies)
|
||||
|
||||
// acc-2 should not be able to get acc-1's policy by ID
|
||||
_, err = store.GetInspectionPolicyByID(ctx, LockingStrengthShare, "acc-2", "ip-acc1")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -143,6 +143,11 @@ type Store interface {
|
||||
SavePostureChecks(ctx context.Context, postureCheck *posture.Checks) error
|
||||
DeletePostureChecks(ctx context.Context, accountID, postureChecksID string) error
|
||||
|
||||
GetAccountInspectionPolicies(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types.InspectionPolicy, error)
|
||||
GetInspectionPolicyByID(ctx context.Context, lockStrength LockingStrength, accountID, policyID string) (*types.InspectionPolicy, error)
|
||||
SaveInspectionPolicy(ctx context.Context, lockStrength LockingStrength, policy *types.InspectionPolicy) error
|
||||
DeleteInspectionPolicy(ctx context.Context, lockStrength LockingStrength, accountID, policyID string) error
|
||||
|
||||
GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountId string, hostname string) ([]string, error)
|
||||
AddPeerToAllGroup(ctx context.Context, accountID string, peerID string) error
|
||||
AddPeerToGroup(ctx context.Context, accountID, peerId string, groupID string) error
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,6 +101,7 @@ type Account struct {
|
||||
NameServerGroupsG []nbdns.NameServerGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||
DNSSettings DNSSettings `gorm:"embedded;embeddedPrefix:dns_settings_"`
|
||||
PostureChecks []*posture.Checks `gorm:"foreignKey:AccountID;references:id"`
|
||||
InspectionPolicies []*InspectionPolicy `gorm:"foreignKey:AccountID;references:id"`
|
||||
Services []*service.Service `gorm:"foreignKey:AccountID;references:id"`
|
||||
Domains []*proxydomain.Domain `gorm:"foreignKey:AccountID;references:id"`
|
||||
// Settings is a dictionary of Account settings
|
||||
|
||||
@@ -81,6 +81,12 @@ func (a *Account) GetPeerNetworkMapComponents(
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build inspection policies map for the network map builder
|
||||
inspectionPoliciesMap := make(map[string]*InspectionPolicy, len(a.InspectionPolicies))
|
||||
for _, ip := range a.InspectionPolicies {
|
||||
inspectionPoliciesMap[ip.ID] = ip
|
||||
}
|
||||
|
||||
components := &NetworkMapComponents{
|
||||
PeerID: peerID,
|
||||
Network: a.Network.Copy(),
|
||||
@@ -91,6 +97,7 @@ func (a *Account) GetPeerNetworkMapComponents(
|
||||
NetworkResources: make([]*resourceTypes.NetworkResource, 0),
|
||||
PostureFailedPeers: make(map[string]map[string]struct{}, len(a.PostureChecks)),
|
||||
RouterPeers: make(map[string]*nbpeer.Peer),
|
||||
InspectionPolicies: inspectionPoliciesMap,
|
||||
}
|
||||
|
||||
components.AccountSettings = &AccountSettingsInfo{
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/netbirdio/netbird/util/crypt"
|
||||
)
|
||||
|
||||
// InspectionPolicy is a reusable set of L7 inspection rules with proxy configuration.
|
||||
// Referenced by policies via InspectionPolicies field, similar to posture checks.
|
||||
// Contains both what to inspect (rules) and how to inspect (CA, ICAP, mode).
|
||||
type InspectionPolicy struct {
|
||||
ID string `gorm:"primaryKey"`
|
||||
AccountID string `gorm:"index"`
|
||||
Name string
|
||||
Description string
|
||||
Enabled bool
|
||||
Rules []InspectionPolicyRule `gorm:"serializer:json"`
|
||||
|
||||
// Mode is the proxy operation mode: "builtin", "envoy", or "external".
|
||||
Mode string `json:"mode"`
|
||||
// ExternalURL is the upstream proxy URL (HTTP CONNECT or SOCKS5) for external mode.
|
||||
ExternalURL string `json:"external_url"`
|
||||
// DefaultAction applies when no rule matches: "allow", "block", or "inspect".
|
||||
DefaultAction string `json:"default_action"`
|
||||
|
||||
// Redirect ports: which destination ports to intercept at L4.
|
||||
// Empty means all ports.
|
||||
RedirectPorts []int `gorm:"serializer:json" json:"redirect_ports"`
|
||||
|
||||
// MITM CA certificate and key (PEM-encoded)
|
||||
CACertPEM string `json:"ca_cert_pem"`
|
||||
CAKeyPEM string `json:"ca_key_pem"`
|
||||
|
||||
// ICAP configuration for external content scanning
|
||||
ICAP *InspectionICAPConfig `gorm:"serializer:json" json:"icap"`
|
||||
|
||||
// Envoy sidecar configuration (mode "envoy" only)
|
||||
EnvoyBinaryPath string `json:"envoy_binary_path"`
|
||||
EnvoyAdminPort int `json:"envoy_admin_port"`
|
||||
EnvoySnippets *InspectionEnvoySnippets `gorm:"serializer:json" json:"envoy_snippets"`
|
||||
}
|
||||
|
||||
// InspectionEnvoySnippets holds user-provided YAML fragments for envoy config customization.
|
||||
// Only safe snippet types are exposed: filters and clusters. Listeners and bootstrap
|
||||
// overrides are not allowed since the envoy instance is fully managed.
|
||||
type InspectionEnvoySnippets struct {
|
||||
HTTPFilters string `json:"http_filters"`
|
||||
NetworkFilters string `json:"network_filters"`
|
||||
Clusters string `json:"clusters"`
|
||||
}
|
||||
|
||||
// InspectionICAPConfig holds ICAP protocol settings.
|
||||
type InspectionICAPConfig struct {
|
||||
ReqModURL string `json:"reqmod_url"`
|
||||
RespModURL string `json:"respmod_url"`
|
||||
MaxConnections int `json:"max_connections"`
|
||||
}
|
||||
|
||||
// InspectionPolicyRule is an L7 rule within an inspection policy.
|
||||
// No source or network references: sources come from the referencing policy,
|
||||
// the destination network/routing peer is derived from the policy's destination.
|
||||
type InspectionPolicyRule struct {
|
||||
Domains []string `json:"domains"`
|
||||
// Networks restricts this rule to specific destination CIDRs.
|
||||
Networks []string `json:"networks"`
|
||||
// Protocols this rule applies to: "http", "https", "h2", "h3", "websocket", "other".
|
||||
Protocols []string `json:"protocols"`
|
||||
// Paths are URL path patterns: "/api/", "/login", "/admin/*".
|
||||
Paths []string `json:"paths"`
|
||||
Action string `json:"action"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
// NewInspectionPolicy creates a new InspectionPolicy with a generated ID.
|
||||
func NewInspectionPolicy(accountID, name, description string, enabled bool) *InspectionPolicy {
|
||||
return &InspectionPolicy{
|
||||
ID: xid.New().String(),
|
||||
AccountID: accountID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
Enabled: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a deep copy.
|
||||
func (p *InspectionPolicy) Copy() *InspectionPolicy {
|
||||
c := *p
|
||||
c.Rules = make([]InspectionPolicyRule, len(p.Rules))
|
||||
for i, r := range p.Rules {
|
||||
c.Rules[i] = r
|
||||
c.Rules[i].Domains = append([]string{}, r.Domains...)
|
||||
c.Rules[i].Networks = append([]string{}, r.Networks...)
|
||||
c.Rules[i].Protocols = append([]string{}, r.Protocols...)
|
||||
}
|
||||
c.RedirectPorts = append([]int{}, p.RedirectPorts...)
|
||||
if p.ICAP != nil {
|
||||
icap := *p.ICAP
|
||||
c.ICAP = &icap
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
// EncryptSensitiveData encrypts CA cert and key in place.
|
||||
func (p *InspectionPolicy) EncryptSensitiveData(enc *crypt.FieldEncrypt) error {
|
||||
if enc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if p.CACertPEM != "" {
|
||||
p.CACertPEM, err = enc.Encrypt(p.CACertPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt ca_cert_pem: %w", err)
|
||||
}
|
||||
}
|
||||
if p.CAKeyPEM != "" {
|
||||
p.CAKeyPEM, err = enc.Encrypt(p.CAKeyPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt ca_key_pem: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecryptSensitiveData decrypts CA cert and key in place.
|
||||
func (p *InspectionPolicy) DecryptSensitiveData(enc *crypt.FieldEncrypt) error {
|
||||
if enc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if p.CACertPEM != "" {
|
||||
p.CACertPEM, err = enc.Decrypt(p.CACertPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt ca_cert_pem: %w", err)
|
||||
}
|
||||
}
|
||||
if p.CAKeyPEM != "" {
|
||||
p.CAKeyPEM, err = enc.Decrypt(p.CAKeyPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt ca_key_pem: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasDomainOnly returns true if this rule matches by domain and has no CIDR destinations.
|
||||
func (r *InspectionPolicyRule) HasDomainOnly() bool {
|
||||
return len(r.Domains) > 0 && len(r.Networks) == 0
|
||||
}
|
||||
|
||||
// HasCIDRDestination returns true if this rule specifies destination CIDRs.
|
||||
func (r *InspectionPolicyRule) HasCIDRDestination() bool {
|
||||
return len(r.Networks) > 0
|
||||
}
|
||||
@@ -37,9 +37,10 @@ type NetworkMap struct {
|
||||
OfflinePeers []*nbpeer.Peer
|
||||
FirewallRules []*FirewallRule
|
||||
RoutesFirewallRules []*RouteFirewallRule
|
||||
ForwardingRules []*ForwardingRule
|
||||
AuthorizedUsers map[string]map[string]struct{}
|
||||
EnableSSH bool
|
||||
ForwardingRules []*ForwardingRule
|
||||
TransparentProxyConfig *TransparentProxyConfig
|
||||
AuthorizedUsers map[string]map[string]struct{}
|
||||
EnableSSH bool
|
||||
}
|
||||
|
||||
func (nm *NetworkMap) Merge(other *NetworkMap) {
|
||||
|
||||
@@ -45,6 +45,13 @@ type NetworkMapComponents struct {
|
||||
PostureFailedPeers map[string]map[string]struct{}
|
||||
|
||||
RouterPeers map[string]*nbpeer.Peer
|
||||
|
||||
// TransparentProxyConfig is the account-level transparent proxy configuration.
|
||||
// Nil if no proxy is configured at account level.
|
||||
TransparentProxyConfig *TransparentProxyConfig
|
||||
|
||||
// InspectionPolicies are reusable inspection rule sets referenced by policies.
|
||||
InspectionPolicies map[string]*InspectionPolicy
|
||||
}
|
||||
|
||||
type AccountSettingsInfo struct {
|
||||
@@ -155,16 +162,21 @@ func (c *NetworkMapComponents) Calculate(ctx context.Context) *NetworkMap {
|
||||
dnsUpdate.NameServerGroups = c.getPeerNSGroupsFromGroups(targetPeerID, peerGroups)
|
||||
}
|
||||
|
||||
// Build transparent proxy config if this peer is a routing peer with inspection enabled.
|
||||
// Falls back to the account-level config if set.
|
||||
tpConfig := c.getTransparentProxyConfig(targetPeerID, isRouter)
|
||||
|
||||
return &NetworkMap{
|
||||
Peers: peersToConnectIncludingRouters,
|
||||
Network: c.Network.Copy(),
|
||||
Routes: append(networkResourcesRoutes, routesUpdate...),
|
||||
DNSConfig: dnsUpdate,
|
||||
OfflinePeers: expiredPeers,
|
||||
FirewallRules: firewallRules,
|
||||
RoutesFirewallRules: append(networkResourcesFirewallRules, routesFirewallRules...),
|
||||
AuthorizedUsers: authorizedUsers,
|
||||
EnableSSH: sshEnabled,
|
||||
Peers: peersToConnectIncludingRouters,
|
||||
Network: c.Network.Copy(),
|
||||
Routes: append(networkResourcesRoutes, routesUpdate...),
|
||||
DNSConfig: dnsUpdate,
|
||||
OfflinePeers: expiredPeers,
|
||||
FirewallRules: firewallRules,
|
||||
RoutesFirewallRules: append(networkResourcesFirewallRules, routesFirewallRules...),
|
||||
TransparentProxyConfig: tpConfig,
|
||||
AuthorizedUsers: authorizedUsers,
|
||||
EnableSSH: sshEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +538,6 @@ func (c *NetworkMapComponents) getRoutingPeerRoutes(peerID string) (enabledRoute
|
||||
return enabledRoutes, disabledRoutes
|
||||
}
|
||||
|
||||
|
||||
func (c *NetworkMapComponents) filterRoutesByGroups(routes []*route.Route, groupListMap LookupMap) []*route.Route {
|
||||
var filteredRoutes []*route.Route
|
||||
for _, r := range routes {
|
||||
@@ -899,3 +910,198 @@ func (c *NetworkMapComponents) addNetworksRoutingPeers(
|
||||
|
||||
return peersToConnect
|
||||
}
|
||||
|
||||
// getTransparentProxyConfig builds a TransparentProxyConfig for a routing peer
|
||||
// by checking if any ACL policy targeting its networks has inspection policies attached.
|
||||
func (c *NetworkMapComponents) getTransparentProxyConfig(peerID string, isRouter bool) *TransparentProxyConfig {
|
||||
if c.TransparentProxyConfig != nil {
|
||||
return c.TransparentProxyConfig
|
||||
}
|
||||
|
||||
if !isRouter {
|
||||
return nil
|
||||
}
|
||||
|
||||
var networkIDs []string
|
||||
for networkID, routers := range c.RoutersMap {
|
||||
if _, ok := routers[peerID]; ok {
|
||||
networkIDs = append(networkIDs, networkID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(networkIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.buildTransparentProxyFromPolicies(networkIDs, peerID)
|
||||
}
|
||||
|
||||
// buildTransparentProxyFromPolicies builds a TransparentProxyConfig from inspection
|
||||
// policies attached to ACL policies targeting the given networks.
|
||||
// Proxy infra config (CA, ICAP, mode) comes from the first inspection policy found.
|
||||
// Rules are aggregated from all inspection policies.
|
||||
func (c *NetworkMapComponents) buildTransparentProxyFromPolicies(networkIDs []string, peerID string) *TransparentProxyConfig {
|
||||
var config *TransparentProxyConfig
|
||||
|
||||
// Accumulate redirect sources across all networks.
|
||||
allSources := make(map[string]struct{})
|
||||
|
||||
for _, networkID := range networkIDs {
|
||||
networkPolicies := c.getPoliciesForNetwork(networkID)
|
||||
for _, policy := range networkPolicies {
|
||||
for _, ipID := range policy.InspectionPolicies {
|
||||
ip, ok := c.InspectionPolicies[ipID]
|
||||
if !ok || !ip.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
// First inspection policy sets the infra config
|
||||
if config == nil {
|
||||
config = &TransparentProxyConfig{
|
||||
Enabled: true,
|
||||
ExternalURL: ip.ExternalURL,
|
||||
DefaultAction: toTransparentProxyAction(ip.DefaultAction),
|
||||
CACertPEM: []byte(ip.CACertPEM),
|
||||
CAKeyPEM: []byte(ip.CAKeyPEM),
|
||||
}
|
||||
switch ip.Mode {
|
||||
case "envoy":
|
||||
config.Mode = TransparentProxyModeEnvoy
|
||||
case "external":
|
||||
config.Mode = TransparentProxyModeExternal
|
||||
default:
|
||||
config.Mode = TransparentProxyModeBuiltin
|
||||
}
|
||||
for _, p := range ip.RedirectPorts {
|
||||
config.RedirectPorts = append(config.RedirectPorts, uint16(p))
|
||||
}
|
||||
if ip.ICAP != nil {
|
||||
config.ICAP = &TransparentProxyICAPConfig{
|
||||
ReqModURL: ip.ICAP.ReqModURL,
|
||||
RespModURL: ip.ICAP.RespModURL,
|
||||
MaxConnections: ip.ICAP.MaxConnections,
|
||||
}
|
||||
}
|
||||
if ip.Mode == "envoy" {
|
||||
config.EnvoyBinaryPath = ip.EnvoyBinaryPath
|
||||
config.EnvoyAdminPort = uint16(ip.EnvoyAdminPort)
|
||||
if ip.EnvoySnippets != nil {
|
||||
config.EnvoySnippets = &TransparentProxyEnvoySnippets{
|
||||
HTTPFilters: ip.EnvoySnippets.HTTPFilters,
|
||||
NetworkFilters: ip.EnvoySnippets.NetworkFilters,
|
||||
Clusters: ip.EnvoySnippets.Clusters,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate rules from all inspection policies
|
||||
for _, pr := range ip.Rules {
|
||||
rule := TransparentProxyRule{
|
||||
ID: ip.ID,
|
||||
Domains: pr.Domains,
|
||||
Networks: pr.Networks,
|
||||
Protocols: pr.Protocols,
|
||||
Paths: pr.Paths,
|
||||
Action: toTransparentProxyAction(pr.Action),
|
||||
Priority: pr.Priority,
|
||||
}
|
||||
config.Rules = append(config.Rules, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect sources for this network
|
||||
for _, src := range c.deriveRedirectSourcesFromPolicies(networkID, peerID) {
|
||||
allSources[src] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
config.RedirectSources = make([]string, 0, len(allSources))
|
||||
for src := range allSources {
|
||||
config.RedirectSources = append(config.RedirectSources, src)
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// deriveRedirectSourcesFromPolicies collects source peer IPs from policies
|
||||
// that target the given network and have inspection policies attached.
|
||||
func (c *NetworkMapComponents) deriveRedirectSourcesFromPolicies(networkID, routingPeerID string) []string {
|
||||
sourceSet := make(map[string]struct{})
|
||||
|
||||
for _, policy := range c.getPoliciesForNetwork(networkID) {
|
||||
if len(policy.InspectionPolicies) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
peerIDs := c.getUniquePeerIDsFromGroupsIDs(policy.SourceGroups())
|
||||
for _, peerID := range peerIDs {
|
||||
if peerID == routingPeerID {
|
||||
continue
|
||||
}
|
||||
peer := c.GetPeerInfo(peerID)
|
||||
if peer != nil && peer.IP != nil {
|
||||
sourceSet[peer.IP.String()+"/32"] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sources := make([]string, 0, len(sourceSet))
|
||||
for s := range sourceSet {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
// getPoliciesForNetwork returns all unique policies that have inspection policies attached
|
||||
// and target resources belonging to the given network.
|
||||
func (c *NetworkMapComponents) getPoliciesForNetwork(networkID string) []*Policy {
|
||||
seen := make(map[string]bool)
|
||||
var result []*Policy
|
||||
|
||||
add := func(policy *Policy) {
|
||||
if len(policy.InspectionPolicies) == 0 || seen[policy.ID] {
|
||||
return
|
||||
}
|
||||
seen[policy.ID] = true
|
||||
result = append(result, policy)
|
||||
}
|
||||
|
||||
// Only include policies that target resources in the given network.
|
||||
networkResourceIDs := make(map[string]struct{})
|
||||
for _, resource := range c.NetworkResources {
|
||||
if resource.NetworkID == networkID {
|
||||
networkResourceIDs[resource.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for resourceID, policies := range c.ResourcePoliciesMap {
|
||||
if _, ok := networkResourceIDs[resourceID]; !ok {
|
||||
continue
|
||||
}
|
||||
for _, policy := range policies {
|
||||
add(policy)
|
||||
}
|
||||
}
|
||||
|
||||
// Also check classic policies whose destination groups contain peers in this network.
|
||||
for _, policy := range c.Policies {
|
||||
add(policy)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func toTransparentProxyAction(s string) TransparentProxyAction {
|
||||
switch s {
|
||||
case "allow":
|
||||
return TransparentProxyActionAllow
|
||||
case "inspect":
|
||||
return TransparentProxyActionInspect
|
||||
default:
|
||||
return TransparentProxyActionBlock
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ type Policy struct {
|
||||
|
||||
// SourcePostureChecks are ID references to Posture checks for policy source groups
|
||||
SourcePostureChecks []string `gorm:"serializer:json"`
|
||||
|
||||
// InspectionPolicies are ID references to inspection policies applied to traffic matching this policy.
|
||||
// When set, traffic is routed through a transparent proxy on the destination network's routing peers.
|
||||
InspectionPolicies []string `gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// Copy returns a copy of the policy.
|
||||
@@ -85,11 +89,13 @@ func (p *Policy) Copy() *Policy {
|
||||
Enabled: p.Enabled,
|
||||
Rules: make([]*PolicyRule, len(p.Rules)),
|
||||
SourcePostureChecks: make([]string, len(p.SourcePostureChecks)),
|
||||
InspectionPolicies: make([]string, len(p.InspectionPolicies)),
|
||||
}
|
||||
for i, r := range p.Rules {
|
||||
c.Rules[i] = r.Copy()
|
||||
}
|
||||
copy(c.SourcePostureChecks, p.SourcePostureChecks)
|
||||
copy(c.InspectionPolicies, p.InspectionPolicies)
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// ProxyRouteSet collects and deduplicates the routes that need to be pushed to
|
||||
// source peers for transparent proxy rules. CIDR rules create specific routes;
|
||||
// domain-only rules require a catch-all (0.0.0.0/0).
|
||||
type ProxyRouteSet struct {
|
||||
// routes is the deduplicated set of destination prefixes to route through the proxy.
|
||||
routes map[netip.Prefix]struct{}
|
||||
// needsCatchAll is true if any rule has domains without CIDRs.
|
||||
needsCatchAll bool
|
||||
}
|
||||
|
||||
// NewProxyRouteSet creates a new route set.
|
||||
func NewProxyRouteSet() *ProxyRouteSet {
|
||||
return &ProxyRouteSet{
|
||||
routes: make(map[netip.Prefix]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// AddFromRule adds route entries derived from a proxy rule's destinations.
|
||||
// - CIDR destinations create specific routes
|
||||
// - Domain-only rules (no CIDRs) trigger a catch-all route
|
||||
// - Rules with neither domains nor CIDRs also trigger catch-all (match all traffic)
|
||||
func (s *ProxyRouteSet) AddFromRule(rule *InspectionPolicyRule) {
|
||||
if rule.HasCIDRDestination() {
|
||||
for _, cidr := range rule.Networks {
|
||||
prefix, err := netip.ParsePrefix(cidr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.routes[prefix] = struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Domain-only or no destination: need catch-all
|
||||
s.needsCatchAll = true
|
||||
}
|
||||
|
||||
// Routes returns the deduplicated list of prefixes to route through the proxy.
|
||||
// If any rule requires catch-all, returns only ["0.0.0.0/0"] since it subsumes
|
||||
// all specific CIDRs.
|
||||
func (s *ProxyRouteSet) Routes() []netip.Prefix {
|
||||
if s.needsCatchAll {
|
||||
return []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")}
|
||||
}
|
||||
|
||||
result := make([]netip.Prefix, 0, len(s.routes))
|
||||
for prefix := range s.routes {
|
||||
result = append(result, prefix)
|
||||
}
|
||||
|
||||
// Sort for deterministic output
|
||||
slices.SortFunc(result, func(a, b netip.Prefix) int {
|
||||
if c := a.Addr().Compare(b.Addr()); c != 0 {
|
||||
return c
|
||||
}
|
||||
return a.Bits() - b.Bits()
|
||||
})
|
||||
|
||||
// Remove CIDRs that are subsets of larger CIDRs
|
||||
return deduplicatePrefixes(result)
|
||||
}
|
||||
|
||||
// deduplicatePrefixes removes prefixes that are contained within other prefixes.
|
||||
// Input must be sorted.
|
||||
func deduplicatePrefixes(prefixes []netip.Prefix) []netip.Prefix {
|
||||
if len(prefixes) <= 1 {
|
||||
return prefixes
|
||||
}
|
||||
|
||||
var result []netip.Prefix
|
||||
for _, p := range prefixes {
|
||||
subsumed := false
|
||||
for _, existing := range result {
|
||||
if existing.Contains(p.Addr()) && existing.Bits() <= p.Bits() {
|
||||
subsumed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !subsumed {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProxyRouteSet_CIDROnly(t *testing.T) {
|
||||
s := NewProxyRouteSet()
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.0.0.0/8", "172.16.0.0/12"},
|
||||
})
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"192.168.0.0/16"},
|
||||
})
|
||||
|
||||
routes := s.Routes()
|
||||
require.Len(t, routes, 3)
|
||||
assert.Equal(t, netip.MustParsePrefix("10.0.0.0/8"), routes[0])
|
||||
assert.Equal(t, netip.MustParsePrefix("172.16.0.0/12"), routes[1])
|
||||
assert.Equal(t, netip.MustParsePrefix("192.168.0.0/16"), routes[2])
|
||||
}
|
||||
|
||||
func TestProxyRouteSet_DomainOnlyForceCatchAll(t *testing.T) {
|
||||
s := NewProxyRouteSet()
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Domains: []string{"*.gambling.com"},
|
||||
})
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.0.0.0/8"},
|
||||
})
|
||||
|
||||
routes := s.Routes()
|
||||
require.Len(t, routes, 1)
|
||||
assert.Equal(t, netip.MustParsePrefix("0.0.0.0/0"), routes[0])
|
||||
}
|
||||
|
||||
func TestProxyRouteSet_EmptyDestinationForceCatchAll(t *testing.T) {
|
||||
s := NewProxyRouteSet()
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Action: "block",
|
||||
// No domains, no networks: match all traffic
|
||||
})
|
||||
|
||||
routes := s.Routes()
|
||||
require.Len(t, routes, 1)
|
||||
assert.Equal(t, netip.MustParsePrefix("0.0.0.0/0"), routes[0])
|
||||
}
|
||||
|
||||
func TestProxyRouteSet_DeduplicateSubsets(t *testing.T) {
|
||||
s := NewProxyRouteSet()
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.0.0.0/8"},
|
||||
})
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.1.0.0/16"}, // subset of 10.0.0.0/8
|
||||
})
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.1.2.0/24"}, // subset of both
|
||||
})
|
||||
|
||||
routes := s.Routes()
|
||||
require.Len(t, routes, 1)
|
||||
assert.Equal(t, netip.MustParsePrefix("10.0.0.0/8"), routes[0])
|
||||
}
|
||||
|
||||
func TestProxyRouteSet_DuplicateCIDRs(t *testing.T) {
|
||||
s := NewProxyRouteSet()
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.0.0.0/8"},
|
||||
})
|
||||
s.AddFromRule(&InspectionPolicyRule{
|
||||
Networks: []string{"10.0.0.0/8"}, // duplicate
|
||||
})
|
||||
|
||||
routes := s.Routes()
|
||||
require.Len(t, routes, 1)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
proto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
)
|
||||
|
||||
// TransparentProxyAction determines the proxy behavior for matched connections.
|
||||
type TransparentProxyAction int
|
||||
|
||||
const (
|
||||
TransparentProxyActionAllow TransparentProxyAction = 0
|
||||
TransparentProxyActionBlock TransparentProxyAction = 1
|
||||
TransparentProxyActionInspect TransparentProxyAction = 2
|
||||
)
|
||||
|
||||
// TransparentProxyMode selects built-in or external proxy operation.
|
||||
type TransparentProxyMode int
|
||||
|
||||
const (
|
||||
TransparentProxyModeBuiltin TransparentProxyMode = 0
|
||||
TransparentProxyModeExternal TransparentProxyMode = 1
|
||||
TransparentProxyModeEnvoy TransparentProxyMode = 2
|
||||
)
|
||||
|
||||
// TransparentProxyConfig holds the transparent proxy configuration for a routing peer.
|
||||
type TransparentProxyConfig struct {
|
||||
Enabled bool
|
||||
Mode TransparentProxyMode
|
||||
ExternalURL string
|
||||
DefaultAction TransparentProxyAction
|
||||
// RedirectSources is the set of source CIDRs to intercept.
|
||||
RedirectSources []string
|
||||
RedirectPorts []uint16
|
||||
Rules []TransparentProxyRule
|
||||
ICAP *TransparentProxyICAPConfig
|
||||
CACertPEM []byte
|
||||
CAKeyPEM []byte
|
||||
ListenPort uint16
|
||||
|
||||
// Envoy sidecar fields (ModeEnvoy only)
|
||||
EnvoyBinaryPath string
|
||||
EnvoyAdminPort uint16
|
||||
EnvoySnippets *TransparentProxyEnvoySnippets
|
||||
}
|
||||
|
||||
// TransparentProxyEnvoySnippets holds user-provided YAML fragments for envoy config.
|
||||
type TransparentProxyEnvoySnippets struct {
|
||||
HTTPFilters string
|
||||
NetworkFilters string
|
||||
Clusters string
|
||||
}
|
||||
|
||||
// TransparentProxyRule is an L7 inspection rule evaluated by the proxy engine.
|
||||
type TransparentProxyRule struct {
|
||||
ID string
|
||||
// Domains are domain patterns, e.g. "*.example.com".
|
||||
Domains []string
|
||||
// Networks restricts this rule to specific destination CIDRs.
|
||||
Networks []string
|
||||
// Protocols this rule applies to: "http", "https", "h2", "h3", "websocket", "other".
|
||||
Protocols []string
|
||||
// Paths are URL path patterns: "/api/", "/login", "/admin/*".
|
||||
Paths []string
|
||||
Action TransparentProxyAction
|
||||
Priority int
|
||||
}
|
||||
|
||||
// TransparentProxyICAPConfig holds ICAP service configuration.
|
||||
type TransparentProxyICAPConfig struct {
|
||||
ReqModURL string
|
||||
RespModURL string
|
||||
MaxConnections int
|
||||
}
|
||||
|
||||
// ToProto converts the internal config to the proto representation.
|
||||
func (c *TransparentProxyConfig) ToProto() *proto.TransparentProxyConfig {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pc := &proto.TransparentProxyConfig{
|
||||
Enabled: c.Enabled,
|
||||
Mode: proto.TransparentProxyMode(c.Mode),
|
||||
DefaultAction: proto.TransparentProxyAction(c.DefaultAction),
|
||||
CaCertPem: c.CACertPEM,
|
||||
CaKeyPem: c.CAKeyPEM,
|
||||
ListenPort: uint32(c.ListenPort),
|
||||
}
|
||||
|
||||
if c.ExternalURL != "" {
|
||||
pc.ExternalProxyUrl = c.ExternalURL
|
||||
}
|
||||
|
||||
if c.Mode == TransparentProxyModeEnvoy {
|
||||
pc.EnvoyBinaryPath = c.EnvoyBinaryPath
|
||||
pc.EnvoyAdminPort = uint32(c.EnvoyAdminPort)
|
||||
if c.EnvoySnippets != nil {
|
||||
pc.EnvoySnippets = &proto.TransparentProxyEnvoySnippets{
|
||||
HttpFilters: c.EnvoySnippets.HTTPFilters,
|
||||
NetworkFilters: c.EnvoySnippets.NetworkFilters,
|
||||
Clusters: c.EnvoySnippets.Clusters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pc.RedirectSources = c.RedirectSources
|
||||
|
||||
redirectPorts := make([]uint32, len(c.RedirectPorts))
|
||||
for i, p := range c.RedirectPorts {
|
||||
redirectPorts[i] = uint32(p)
|
||||
}
|
||||
pc.RedirectPorts = redirectPorts
|
||||
|
||||
for _, r := range c.Rules {
|
||||
pr := &proto.TransparentProxyRule{
|
||||
Id: r.ID,
|
||||
Domains: r.Domains,
|
||||
Networks: r.Networks,
|
||||
Paths: r.Paths,
|
||||
Action: proto.TransparentProxyAction(r.Action),
|
||||
Priority: int32(r.Priority),
|
||||
}
|
||||
for _, p := range r.Protocols {
|
||||
pr.Protocols = append(pr.Protocols, stringToProtoProtocol(p))
|
||||
}
|
||||
pc.Rules = append(pc.Rules, pr)
|
||||
}
|
||||
|
||||
if c.ICAP != nil {
|
||||
pc.Icap = &proto.TransparentProxyICAPConfig{
|
||||
ReqmodUrl: c.ICAP.ReqModURL,
|
||||
RespmodUrl: c.ICAP.RespModURL,
|
||||
MaxConnections: int32(c.ICAP.MaxConnections),
|
||||
}
|
||||
}
|
||||
|
||||
return pc
|
||||
}
|
||||
|
||||
// stringToProtoProtocol maps a protocol string to its proto enum value.
|
||||
func stringToProtoProtocol(s string) proto.TransparentProxyProtocol {
|
||||
switch s {
|
||||
case "http":
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_HTTP
|
||||
case "https":
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_HTTPS
|
||||
case "h2":
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_H2
|
||||
case "h3":
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_H3
|
||||
case "websocket":
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_WEBSOCKET
|
||||
case "other":
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_OTHER
|
||||
default:
|
||||
return proto.TransparentProxyProtocol_TP_PROTO_ALL
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user