Add transparent proxy inspection engine with envoy sidecar support

This commit is contained in:
Viktor Liu
2026-04-11 18:07:46 +02:00
parent 5259e5df51
commit afbddae472
65 changed files with 10428 additions and 763 deletions

View File

@@ -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

View File

@@ -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{

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}
}