Merge branch 'main' into kralicky/tracing

This commit is contained in:
Joe Kralicky 2025-01-15 19:21:32 +00:00
commit 1ada66ca6b
No known key found for this signature in database
GPG key ID: 75C4875F34A9FB79
12 changed files with 954 additions and 819 deletions

View file

@ -47,6 +47,7 @@ func ExtAuthzFilter(grpcClientTimeout *durationpb.Duration) *envoy_extensions_fi
func HTTPConnectionManagerFilter( func HTTPConnectionManagerFilter(
httpConnectionManager *envoy_extensions_filters_network_http_connection_manager.HttpConnectionManager, httpConnectionManager *envoy_extensions_filters_network_http_connection_manager.HttpConnectionManager,
) *envoy_config_listener_v3.Filter { ) *envoy_config_listener_v3.Filter {
applyGlobalHTTPConnectionManagerOptions(httpConnectionManager)
return &envoy_config_listener_v3.Filter{ return &envoy_config_listener_v3.Filter{
Name: "envoy.filters.network.http_connection_manager", Name: "envoy.filters.network.http_connection_manager",
ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{ ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{

View file

@ -8,6 +8,7 @@ import (
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/httputil"
@ -120,3 +121,24 @@ func (b *Builder) buildLocalReplyConfig(
}}, }},
}, nil }, nil
} }
func applyGlobalHTTPConnectionManagerOptions(hcm *envoy_http_connection_manager.HttpConnectionManager) {
if hcm.InternalAddressConfig == nil {
// see doc comment on InternalAddressConfig for details
hcm.InternalAddressConfig = &envoy_http_connection_manager.HttpConnectionManager_InternalAddressConfig{
CidrRanges: []*envoy_config_core_v3.CidrRange{
// localhost
{AddressPrefix: "127.0.0.1", PrefixLen: wrapperspb.UInt32(32)},
{AddressPrefix: "::1", PrefixLen: wrapperspb.UInt32(128)},
// RFC1918
{AddressPrefix: "10.0.0.0", PrefixLen: wrapperspb.UInt32(8)},
{AddressPrefix: "192.168.0.0", PrefixLen: wrapperspb.UInt32(16)},
{AddressPrefix: "172.16.0.0", PrefixLen: wrapperspb.UInt32(12)},
// RFC4193
{AddressPrefix: "fd00::", PrefixLen: wrapperspb.UInt32(8)},
},
}
}
}

View file

@ -42,7 +42,7 @@ func (b *Builder) buildOutboundListener(cfg *config.Config) (*envoy_config_liste
func (b *Builder) buildOutboundHTTPConnectionManager() *envoy_config_listener_v3.Filter { func (b *Builder) buildOutboundHTTPConnectionManager() *envoy_config_listener_v3.Filter {
rc := b.buildOutboundRouteConfiguration() rc := b.buildOutboundRouteConfiguration()
tc := marshalAny(&envoy_http_connection_manager.HttpConnectionManager{ return HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO, CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "grpc_egress", StatPrefix: "grpc_egress",
// limit request first byte to last byte time // limit request first byte to last byte time
@ -56,13 +56,6 @@ func (b *Builder) buildOutboundHTTPConnectionManager() *envoy_config_listener_v3
HTTPRouterFilter(), HTTPRouterFilter(),
}, },
}) })
return &envoy_config_listener_v3.Filter{
Name: "envoy.filters.network.http_connection_manager",
ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{
TypedConfig: tc,
},
}
} }
func (b *Builder) buildOutboundRouteConfiguration() *envoy_config_route_v3.RouteConfiguration { func (b *Builder) buildOutboundRouteConfiguration() *envoy_config_route_v3.RouteConfiguration {

View file

@ -227,6 +227,34 @@
"spawnUpstreamSpan": true "spawnUpstreamSpan": true
}, },
"useRemoteAddress": true, "useRemoteAddress": true,
"xffNumTrustedHops": 1 "xffNumTrustedHops": 1,
"internalAddressConfig": {
"cidrRanges": [
{
"addressPrefix": "127.0.0.1",
"prefixLen": 32
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "10.0.0.0",
"prefixLen": 8
},
{
"addressPrefix": "192.168.0.0",
"prefixLen": 16
},
{
"addressPrefix": "172.16.0.0",
"prefixLen": 12
},
{
"addressPrefix": "fd00::",
"prefixLen": 8
}
]
}
} }
} }

View file

@ -54,7 +54,35 @@
} }
] ]
}, },
"statPrefix": "metrics" "statPrefix": "metrics",
"internalAddressConfig": {
"cidrRanges": [
{
"addressPrefix": "127.0.0.1",
"prefixLen": 32
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "10.0.0.0",
"prefixLen": 8
},
{
"addressPrefix": "192.168.0.0",
"prefixLen": 16
},
{
"addressPrefix": "172.16.0.0",
"prefixLen": 12
},
{
"addressPrefix": "fd00::",
"prefixLen": 8
}
]
}
} }
} }
], ],

View file

@ -978,17 +978,6 @@ func TestOptions_ApplySettings(t *testing.T) {
}) })
} }
func TestXXX(t *testing.T) {
dir, _ := os.MkdirTemp("", "asdf")
t.Log(dir)
for i := 1; i <= 100; i++ {
crt, _ := cryptutil.GenerateCertificate(nil, fmt.Sprintf("route%d.localhost.pomerium.io", i))
crtBytes, keyBytes, _ := cryptutil.EncodeCertificate(crt)
os.WriteFile(fmt.Sprintf("%s/%d.crt", dir, i), crtBytes, 0o644)
os.WriteFile(fmt.Sprintf("%s/%d.key", dir, i), keyBytes, 0o600)
}
}
func TestOptions_GetSetResponseHeaders(t *testing.T) { func TestOptions_GetSetResponseHeaders(t *testing.T) {
t.Run("lax", func(t *testing.T) { t.Run("lax", func(t *testing.T) {
options := NewDefaultOptions() options := NewDefaultOptions()

View file

@ -27,7 +27,10 @@ import (
// Policy contains route specific configuration and access settings. // Policy contains route specific configuration and access settings.
type Policy struct { type Policy struct {
ID string `mapstructure:"-" yaml:"-" json:"-"` ID string `mapstructure:"-" yaml:"-" json:"-"`
Name string `mapstructure:"-" yaml:"-" json:"-"`
Description string `mapstructure:"description" yaml:"description,omitempty" json:"description,omitempty"`
LogoURL string `mapstructure:"logo_url" yaml:"logo_url,omitempty" json:"logo_url,omitempty"`
From string `mapstructure:"from" yaml:"from"` From string `mapstructure:"from" yaml:"from"`
To WeightedURLs `mapstructure:"to" yaml:"to"` To WeightedURLs `mapstructure:"to" yaml:"to"`
@ -285,6 +288,7 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
AllowSPDY: pb.GetAllowSpdy(), AllowSPDY: pb.GetAllowSpdy(),
AllowWebsockets: pb.GetAllowWebsockets(), AllowWebsockets: pb.GetAllowWebsockets(),
CORSAllowPreflight: pb.GetCorsAllowPreflight(), CORSAllowPreflight: pb.GetCorsAllowPreflight(),
Description: pb.GetDescription(),
EnableGoogleCloudServerlessAuthentication: pb.GetEnableGoogleCloudServerlessAuthentication(), EnableGoogleCloudServerlessAuthentication: pb.GetEnableGoogleCloudServerlessAuthentication(),
From: pb.GetFrom(), From: pb.GetFrom(),
HostPathRegexRewritePattern: pb.GetHostPathRegexRewritePattern(), HostPathRegexRewritePattern: pb.GetHostPathRegexRewritePattern(),
@ -298,6 +302,8 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
JWTGroupsFilter: NewJWTGroupsFilter(pb.JwtGroupsFilter), JWTGroupsFilter: NewJWTGroupsFilter(pb.JwtGroupsFilter),
KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(), KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(),
KubernetesServiceAccountTokenFile: pb.GetKubernetesServiceAccountTokenFile(), KubernetesServiceAccountTokenFile: pb.GetKubernetesServiceAccountTokenFile(),
LogoURL: pb.GetLogoUrl(),
Name: pb.GetName(),
PassIdentityHeaders: pb.PassIdentityHeaders, PassIdentityHeaders: pb.PassIdentityHeaders,
Path: pb.GetPath(), Path: pb.GetPath(),
Prefix: pb.GetPrefix(), Prefix: pb.GetPrefix(),
@ -438,6 +444,7 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
AllowSpdy: p.AllowSPDY, AllowSpdy: p.AllowSPDY,
AllowWebsockets: p.AllowWebsockets, AllowWebsockets: p.AllowWebsockets,
CorsAllowPreflight: p.CORSAllowPreflight, CorsAllowPreflight: p.CORSAllowPreflight,
Description: p.Description,
EnableGoogleCloudServerlessAuthentication: p.EnableGoogleCloudServerlessAuthentication, EnableGoogleCloudServerlessAuthentication: p.EnableGoogleCloudServerlessAuthentication,
EnvoyOpts: p.EnvoyOpts, EnvoyOpts: p.EnvoyOpts,
From: p.From, From: p.From,
@ -446,7 +453,8 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
JwtGroupsFilter: p.JWTGroupsFilter.ToSlice(), JwtGroupsFilter: p.JWTGroupsFilter.ToSlice(),
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken, KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
KubernetesServiceAccountTokenFile: p.KubernetesServiceAccountTokenFile, KubernetesServiceAccountTokenFile: p.KubernetesServiceAccountTokenFile,
Name: fmt.Sprint(p.RouteID()), LogoUrl: p.LogoURL,
Name: p.Name,
PassIdentityHeaders: p.PassIdentityHeaders, PassIdentityHeaders: p.PassIdentityHeaders,
Path: p.Path, Path: p.Path,
Policies: sps, Policies: sps,
@ -476,6 +484,9 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
TlsUpstreamAllowRenegotiation: p.TLSUpstreamAllowRenegotiation, TlsUpstreamAllowRenegotiation: p.TLSUpstreamAllowRenegotiation,
TlsUpstreamServerName: p.TLSUpstreamServerName, TlsUpstreamServerName: p.TLSUpstreamServerName,
} }
if pb.Name == "" {
pb.Name = fmt.Sprint(p.RouteID())
}
if p.HostPathRegexRewritePattern != "" { if p.HostPathRegexRewritePattern != "" {
pb.HostPathRegexRewritePattern = proto.String(p.HostPathRegexRewritePattern) pb.HostPathRegexRewritePattern = proto.String(p.HostPathRegexRewritePattern)
} }
@ -869,6 +880,26 @@ func (p *Policy) GetPassIdentityHeaders(options *Options) bool {
return false return false
} }
// GetFrom gets the from URL.
func (p *Policy) GetFrom() string {
return p.From
}
// GetPath gets the path.
func (p *Policy) GetPath() string {
return p.Path
}
// GetPrefix gets the prefix.
func (p *Policy) GetPrefix() string {
return p.Prefix
}
// GetRegex gets the regex.
func (p *Policy) GetRegex() string {
return p.Regex
}
/* /*
SortPolicies sorts policies to match the following SQL order: SortPolicies sorts policies to match the following SQL order:

View file

@ -218,6 +218,9 @@ func TestPolicy_FromToPb(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
p := &Policy{ p := &Policy{
Name: "ROUTE_NAME",
Description: "DESCRIPTION",
LogoURL: "LOGO_URL",
From: "https://pomerium.io", From: "https://pomerium.io",
To: mustParseWeightedURLs(t, "http://localhost"), To: mustParseWeightedURLs(t, "http://localhost"),
AllowedUsers: []string{"foo@bar.com"}, AllowedUsers: []string{"foo@bar.com"},
@ -235,6 +238,9 @@ func TestPolicy_FromToPb(t *testing.T) {
policyFromPb, err := NewPolicyFromProto(pbPolicy) policyFromPb, err := NewPolicyFromProto(pbPolicy)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, p.Name, policyFromPb.Name)
assert.Equal(t, p.Description, policyFromPb.Description)
assert.Equal(t, p.LogoURL, policyFromPb.LogoURL)
assert.Equal(t, p.From, policyFromPb.From) assert.Equal(t, p.From, policyFromPb.From)
assert.Equal(t, p.To, policyFromPb.To) assert.Equal(t, p.To, policyFromPb.To)
assert.Equal(t, p.AllowedUsers, policyFromPb.AllowedUsers) assert.Equal(t, p.AllowedUsers, policyFromPb.AllowedUsers)

File diff suppressed because it is too large Load diff

View file

@ -45,9 +45,11 @@ enum IssuerFormat {
IssuerURI = 1; IssuerURI = 1;
} }
// Next ID: 67. // Next ID: 69.
message Route { message Route {
string name = 1; string name = 1;
string description = 67;
string logo_url = 68;
string from = 2; string from = 2;
repeated string to = 3; repeated string to = 3;

View file

@ -11,7 +11,6 @@ import (
"strings" "strings"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
) )
func GenerateCertName(cert *x509.Certificate) *string { func GenerateCertName(cert *x509.Certificate) *string {
@ -59,18 +58,26 @@ func pickDNSName(names []string) string {
return names[0] return names[0]
} }
func GenerateRouteNames(routes []*configpb.Route) []string { type Route interface {
comparable
GetFrom() string
GetPrefix() string
GetPath() string
GetRegex() string
}
func GenerateRouteNames[T Route](routes []T) []string {
out := make([]string, len(routes)) out := make([]string, len(routes))
prefixes := make([][]string, len(routes)) prefixes := make([][]string, len(routes))
indexes := map[*configpb.Route]int{} indexes := map[T]int{}
trie := newDomainTrie() trie := newDomainTrie[T]()
for i, route := range routes { for i, route := range routes {
trie.Insert(route) trie.Insert(route)
indexes[route] = i indexes[route] = i
} }
trie.Compact() trie.Compact()
trie.Walk(func(parents []string, node *domainTreeNode) { trie.Walk(func(parents []string, node *domainTreeNode[T]) {
for subdomain, child := range node.children { for subdomain, child := range node.children {
for route, name := range differentiateRoutes(subdomain, child.routes) { for route, name := range differentiateRoutes(subdomain, child.routes) {
idx := indexes[route] idx := indexes[route]
@ -145,13 +152,13 @@ func trimCommonSubdomains(a, b string) (string, string) {
return strings.Join(aParts, "-"), strings.Join(bParts, "-") return strings.Join(aParts, "-"), strings.Join(bParts, "-")
} }
func differentiateRoutes(subdomain string, routes []*configpb.Route) iter.Seq2[*configpb.Route, string] { func differentiateRoutes[T Route](subdomain string, routes []T) iter.Seq2[T, string] {
return func(yield func(*configpb.Route, string) bool) { return func(yield func(T, string) bool) {
if len(routes) == 1 { if len(routes) == 1 {
yield(routes[0], subdomain) yield(routes[0], subdomain)
return return
} }
names := map[string][]*configpb.Route{} names := map[string][]T{}
replacer := strings.NewReplacer( replacer := strings.NewReplacer(
" ", "_", " ", "_",
"/", "-", "/", "-",
@ -180,14 +187,14 @@ func differentiateRoutes(subdomain string, routes []*configpb.Route) iter.Seq2[*
// each route will have the same domain, but a unique prefix/path/regex. // each route will have the same domain, but a unique prefix/path/regex.
var name string var name string
switch { switch {
case route.Prefix != "": case route.GetPrefix() != "":
name = simplePathName(route.Prefix) name = simplePathName(route.GetPrefix())
prefixCount++ prefixCount++
case route.Path != "": case route.GetPath() != "":
name = simplePathName(route.Path) name = simplePathName(route.GetPath())
pathCount++ pathCount++
case route.Regex != "": case route.GetRegex() != "":
name = regexName(route.Regex) name = regexName(route.GetRegex())
} }
names[name] = append(names[name], route) names[name] = append(names[name], route)
} }
@ -229,9 +236,9 @@ func differentiateRoutes(subdomain string, routes []*configpb.Route) iter.Seq2[*
b.WriteRune('-') b.WriteRune('-')
b.WriteString(name) b.WriteString(name)
} }
if route.Prefix != "" { if route.GetPrefix() != "" {
b.WriteString(prefixSuffix) b.WriteString(prefixSuffix)
} else if route.Path != "" { } else if route.GetPath() != "" {
b.WriteString(pathSuffix) b.WriteString(pathSuffix)
} }
@ -251,58 +258,58 @@ func differentiateRoutes(subdomain string, routes []*configpb.Route) iter.Seq2[*
} }
} }
type domainTreeNode struct { type domainTreeNode[T Route] struct {
parent *domainTreeNode parent *domainTreeNode[T]
children map[string]*domainTreeNode children map[string]*domainTreeNode[T]
routes []*configpb.Route routes []T
} }
func (n *domainTreeNode) insert(key string, route *configpb.Route) *domainTreeNode { func (n *domainTreeNode[T]) insert(key string, route T) *domainTreeNode[T] {
if existing, ok := n.children[key]; ok { if existing, ok := n.children[key]; ok {
if route != nil { var def T
if route != def {
existing.routes = append(existing.routes, route) existing.routes = append(existing.routes, route)
} }
return existing return existing
} }
node := &domainTreeNode{ node := &domainTreeNode[T]{
parent: n, parent: n,
children: map[string]*domainTreeNode{}, children: map[string]*domainTreeNode[T]{},
} }
if route != nil { var def T
if route != def {
node.routes = append(node.routes, route) node.routes = append(node.routes, route)
} }
n.children[key] = node n.children[key] = node
return node return node
} }
type domainTrie struct { type domainTrie[T Route] struct {
root *domainTreeNode root *domainTreeNode[T]
} }
func newDomainTrie() *domainTrie { func newDomainTrie[T Route]() *domainTrie[T] {
t := &domainTrie{ t := &domainTrie[T]{
root: &domainTreeNode{ root: &domainTreeNode[T]{
children: map[string]*domainTreeNode{}, children: map[string]*domainTreeNode[T]{},
}, },
} }
return t return t
} }
type walkFn = func(parents []string, node *domainTreeNode) func (t *domainTrie[T]) Walk(fn func(parents []string, node *domainTreeNode[T])) {
func (t *domainTrie) Walk(fn walkFn) {
t.root.walk(nil, fn) t.root.walk(nil, fn)
} }
func (n *domainTreeNode) walk(prefix []string, fn walkFn) { func (n *domainTreeNode[T]) walk(prefix []string, fn func(parents []string, node *domainTreeNode[T])) {
for key, child := range n.children { for key, child := range n.children {
fn(append(prefix, key), child) fn(append(prefix, key), child)
child.walk(append(prefix, key), fn) child.walk(append(prefix, key), fn)
} }
} }
func (t *domainTrie) Insert(route *configpb.Route) { func (t *domainTrie[T]) Insert(route T) {
u, _ := url.Parse(route.From) u, _ := url.Parse(route.GetFrom())
if u == nil { if u == nil {
// ignore invalid urls, they will be assigned generic fallback names // ignore invalid urls, they will be assigned generic fallback names
return return
@ -311,16 +318,17 @@ func (t *domainTrie) Insert(route *configpb.Route) {
slices.Reverse(parts) slices.Reverse(parts)
cur := t.root cur := t.root
for _, part := range parts[:len(parts)-1] { for _, part := range parts[:len(parts)-1] {
cur = cur.insert(part, nil) var def T
cur = cur.insert(part, def)
} }
cur.insert(parts[len(parts)-1], route) cur.insert(parts[len(parts)-1], route)
} }
func (t *domainTrie) Compact() { func (t *domainTrie[T]) Compact() {
t.root.compact() t.root.compact()
} }
func (n *domainTreeNode) compact() { func (n *domainTreeNode[T]) compact() {
for _, child := range n.children { for _, child := range n.children {
child.compact() child.compact()
} }
@ -328,7 +336,7 @@ func (n *domainTreeNode) compact() {
return return
} }
var firstKey string var firstKey string
var firstChild *domainTreeNode var firstChild *domainTreeNode[T]
for key, child := range n.children { for key, child := range n.children {
firstKey, firstChild = key, child firstKey, firstChild = key, child
break break
@ -340,7 +348,7 @@ func (n *domainTreeNode) compact() {
if child == n { if child == n {
delete(n.parent.children, key) delete(n.parent.children, key)
n.parent.children[fmt.Sprintf("%s.%s", key, firstKey)] = firstChild n.parent.children[fmt.Sprintf("%s.%s", key, firstKey)] = firstChild
*n = domainTreeNode{} *n = domainTreeNode[T]{}
break break
} }
} }

View file

@ -8,9 +8,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/config"
configpb "github.com/pomerium/pomerium/pkg/grpc/config" configpb "github.com/pomerium/pomerium/pkg/grpc/config"
"github.com/pomerium/pomerium/pkg/zero/importutil" "github.com/pomerium/pomerium/pkg/zero/importutil"
"github.com/stretchr/testify/assert"
) )
func TestGenerateCertName(t *testing.T) { func TestGenerateCertName(t *testing.T) {
@ -389,6 +391,11 @@ func TestGenerateRouteNames(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, importutil.GenerateRouteNames(tc.input)) assert.Equal(t, tc.expected, importutil.GenerateRouteNames(tc.input))
policies := make([]*config.Policy, len(tc.input))
for i := range tc.input {
policies[i], _ = config.NewPolicyFromProto(tc.input[i])
}
assert.Equal(t, tc.expected, importutil.GenerateRouteNames(policies))
}) })
} }
} }