mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-23 14:07:11 +02:00
Optimize Policy RouteID (#5359)
This commit is contained in:
parent
82fb9cf29d
commit
ebd9eea30e
6 changed files with 540 additions and 122 deletions
|
@ -77,7 +77,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"autoHostRewrite": true,
|
"autoHostRewrite": true,
|
||||||
"cluster": "route-5d678ee30d16332b",
|
"cluster": "route-b8e37dd1f9d65ddd",
|
||||||
"hashPolicy": [
|
"hashPolicy": [
|
||||||
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
|
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
|
||||||
{ "connectionProperties": { "sourceIp": true }, "terminal": true }
|
{ "connectionProperties": { "sourceIp": true }, "terminal": true }
|
||||||
|
@ -94,7 +94,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "6730505273956774699"
|
"route_id": "13322630463485271517"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"autoHostRewrite": true,
|
"autoHostRewrite": true,
|
||||||
"cluster": "route-5d678ee30d16332b",
|
"cluster": "route-b8e37dd1f9d65ddd",
|
||||||
"hashPolicy": [
|
"hashPolicy": [
|
||||||
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
|
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
|
||||||
{ "connectionProperties": { "sourceIp": true }, "terminal": true }
|
{ "connectionProperties": { "sourceIp": true }, "terminal": true }
|
||||||
|
@ -147,7 +147,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "6730505273956774699"
|
"route_id": "13322630463485271517"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,79 +303,102 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
oneMinute := time.Minute
|
oneMinute := time.Minute
|
||||||
ten := time.Second * 10
|
ten := time.Second * 10
|
||||||
|
|
||||||
b := &Builder{filemgr: filemgr.NewManager(), reproxy: reproxy.New()}
|
// note: within each policy below, fields that do not affect the route ID
|
||||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
// are grouped separately, after the fields that do affect the route ID.
|
||||||
CookieName: "pomerium",
|
policies := []config.Policy{
|
||||||
DefaultUpstreamTimeout: time.Second * 3,
|
0: { // skipped by host filter
|
||||||
SharedKey: cryptutil.NewBase64Key(),
|
|
||||||
Policies: []config.Policy{
|
|
||||||
{
|
|
||||||
From: "https://ignore.example.com",
|
From: "https://ignore.example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
|
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
1: {
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
|
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
2: {
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Path: "/some/path",
|
Path: "/some/path",
|
||||||
|
|
||||||
AllowWebsockets: true,
|
AllowWebsockets: true,
|
||||||
PreserveHostHeader: true,
|
PreserveHostHeader: true,
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
3: {
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Prefix: "/some/prefix/",
|
Prefix: "/some/prefix/",
|
||||||
|
|
||||||
SetRequestHeaders: map[string]string{"HEADER-KEY": "HEADER-VALUE"},
|
SetRequestHeaders: map[string]string{"HEADER-KEY": "HEADER-VALUE"},
|
||||||
UpstreamTimeout: &oneMinute,
|
UpstreamTimeout: &oneMinute,
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
4: {
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Regex: `^/[a]+$`,
|
Regex: `^/[a]+$`,
|
||||||
|
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
5: { // same route ID as 3
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Prefix: "/some/prefix/",
|
Prefix: "/some/prefix/",
|
||||||
|
|
||||||
RemoveRequestHeaders: []string{"HEADER-KEY"},
|
RemoveRequestHeaders: []string{"HEADER-KEY"},
|
||||||
UpstreamTimeout: &oneMinute,
|
UpstreamTimeout: &oneMinute,
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
6: { // same route ID as 2
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Path: "/some/path",
|
Path: "/some/path",
|
||||||
|
|
||||||
AllowSPDY: true,
|
AllowSPDY: true,
|
||||||
PreserveHostHeader: true,
|
PreserveHostHeader: true,
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
7: { // same route ID as 2
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Path: "/some/path",
|
Path: "/some/path",
|
||||||
|
|
||||||
AllowSPDY: true,
|
AllowSPDY: true,
|
||||||
AllowWebsockets: true,
|
AllowWebsockets: true,
|
||||||
PreserveHostHeader: true,
|
PreserveHostHeader: true,
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
},
|
},
|
||||||
{
|
8: {
|
||||||
From: "https://example.com",
|
From: "https://example.com",
|
||||||
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
To: mustParseWeightedURLs(t, "https://to.example.com"),
|
||||||
Path: "/websocket-timeout",
|
Path: "/websocket-timeout",
|
||||||
|
|
||||||
AllowWebsockets: true,
|
AllowWebsockets: true,
|
||||||
PreserveHostHeader: true,
|
PreserveHostHeader: true,
|
||||||
PassIdentityHeaders: ptr(true),
|
PassIdentityHeaders: ptr(true),
|
||||||
UpstreamTimeout: &ten,
|
UpstreamTimeout: &ten,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
|
routeIDs := []string{
|
||||||
|
1: "772697672458217856",
|
||||||
|
2: "6032229746964560472",
|
||||||
|
3: "13317665674438641304",
|
||||||
|
4: "9768293332770157550",
|
||||||
|
5: "13317665674438641304", // same as 3
|
||||||
|
6: "6032229746964560472", // same as 2
|
||||||
|
7: "6032229746964560472", // same as 2
|
||||||
|
8: "1591581179179639728",
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &Builder{filemgr: filemgr.NewManager(), reproxy: reproxy.New()}
|
||||||
|
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||||
|
CookieName: "pomerium",
|
||||||
|
DefaultUpstreamTimeout: time.Second * 3,
|
||||||
|
SharedKey: cryptutil.NewBase64Key(),
|
||||||
|
Policies: policies,
|
||||||
}}, "example.com")
|
}}, "example.com")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -445,7 +468,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "16913502743845432363"
|
"route_id": "`+routeIDs[1]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -516,7 +539,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "911713133804109577"
|
"route_id": "`+routeIDs[2]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -586,7 +609,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "6407864870815560799"
|
"route_id": "`+routeIDs[3]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -658,7 +681,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "1103677309004574500"
|
"route_id": "`+routeIDs[4]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -729,7 +752,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "6407864870815560799"
|
"route_id": "`+routeIDs[5]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -799,7 +822,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "911713133804109577"
|
"route_id": "`+routeIDs[6]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -870,7 +893,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "911713133804109577"
|
"route_id": "`+routeIDs[7]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -941,7 +964,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "17831746838845374842"
|
"route_id": "`+routeIDs[8]+`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1124,7 +1147,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "10474912405080199536"
|
"route_id": "11959552038839924732"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1196,7 +1219,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "15730681265277585877"
|
"route_id": "9444248534316924938"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1294,7 +1317,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "16598125949405432745"
|
"route_id": "5652544858774142715"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1366,14 +1389,14 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
|
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
|
||||||
"header": {
|
"header": {
|
||||||
"key": "x-pomerium-reproxy-policy",
|
"key": "x-pomerium-reproxy-policy",
|
||||||
"value": "2222095689633600553"
|
"value": "5799631121007486501"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
|
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
|
||||||
"header": {
|
"header": {
|
||||||
"key": "x-pomerium-reproxy-policy-hmac",
|
"key": "x-pomerium-reproxy-policy-hmac",
|
||||||
"value": "/cH0S/ODZYaW4CALohG926c+TH22+/bD79Kb82k8/Eg="
|
"value": "v4w8DAUFdw2qw7RJLUZYBHWndqBOdz5Me6A+1vbDQPY="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -1405,7 +1428,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "2222095689633600553"
|
"route_id": "5799631121007486501"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1535,7 +1558,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "13828028232508831592"
|
"route_id": "1410576726089372267"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1606,7 +1629,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "13828028232508831592"
|
"route_id": "1410576726089372267"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1682,7 +1705,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "13828028232508831592"
|
"route_id": "1410576726089372267"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1753,7 +1776,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "13828028232508831592"
|
"route_id": "1410576726089372267"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1824,7 +1847,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "13828028232508831592"
|
"route_id": "1410576726089372267"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1900,7 +1923,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
||||||
"checkSettings": {
|
"checkSettings": {
|
||||||
"contextExtensions": {
|
"contextExtensions": {
|
||||||
"internal": "false",
|
"internal": "false",
|
||||||
"route_id": "13828028232508831592"
|
"route_id": "1410576726089372267"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -654,30 +654,63 @@ func (p *Policy) Checksum() uint64 {
|
||||||
return hashutil.MustHash(p)
|
return hashutil.MustHash(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouteID returns a unique identifier for a route
|
// RouteID returns a unique identifier for a route.
|
||||||
|
//
|
||||||
|
// The following fields are used to compute the ID:
|
||||||
|
// - from
|
||||||
|
// - prefix
|
||||||
|
// - path
|
||||||
|
// - regex
|
||||||
|
// - to/redirect/response (whichever is set)
|
||||||
func (p *Policy) RouteID() (uint64, error) {
|
func (p *Policy) RouteID() (uint64, error) {
|
||||||
id := routeID{
|
// this function is in the hot path, try not to allocate too much memory here
|
||||||
From: p.From,
|
hash := hashutil.NewDigest()
|
||||||
Prefix: p.Prefix,
|
hash.WriteStringWithLen(p.From)
|
||||||
Path: p.Path,
|
hash.WriteStringWithLen(p.Prefix)
|
||||||
Regex: p.Regex,
|
hash.WriteStringWithLen(p.Path)
|
||||||
}
|
hash.WriteStringWithLen(p.Regex)
|
||||||
|
switch {
|
||||||
if len(p.To) > 0 {
|
case len(p.To) > 0:
|
||||||
dst, _, err := p.To.Flatten()
|
_, _ = hash.Write([]byte{1}) // case 1
|
||||||
if err != nil {
|
hash.WriteInt32(int32(len(p.To)))
|
||||||
return 0, err
|
for _, to := range p.To {
|
||||||
}
|
hash.WriteStringWithLen(to.URL.Scheme)
|
||||||
id.To = dst
|
hash.WriteStringWithLen(to.URL.Opaque)
|
||||||
} else if p.Redirect != nil {
|
if to.URL.User == nil {
|
||||||
id.Redirect = p.Redirect
|
_, _ = hash.Write([]byte{0})
|
||||||
} else if p.Response != nil {
|
|
||||||
id.Response = p.Response
|
|
||||||
} else {
|
} else {
|
||||||
|
_, _ = hash.Write([]byte{1})
|
||||||
|
hash.WriteStringWithLen(to.URL.User.Username())
|
||||||
|
p, _ := to.URL.User.Password()
|
||||||
|
hash.WriteStringWithLen(p)
|
||||||
|
}
|
||||||
|
hash.WriteStringWithLen(to.URL.Host)
|
||||||
|
hash.WriteStringWithLen(to.URL.Path)
|
||||||
|
hash.WriteStringWithLen(to.URL.RawPath)
|
||||||
|
hash.WriteBool(to.URL.OmitHost)
|
||||||
|
hash.WriteBool(to.URL.ForceQuery)
|
||||||
|
hash.WriteStringWithLen(to.URL.Fragment)
|
||||||
|
hash.WriteStringWithLen(to.URL.RawFragment)
|
||||||
|
hash.WriteUint32(to.LbWeight)
|
||||||
|
}
|
||||||
|
case p.Redirect != nil:
|
||||||
|
_, _ = hash.Write([]byte{2}) // case 2
|
||||||
|
hash.WriteBoolPtr(p.Redirect.HTTPSRedirect)
|
||||||
|
hash.WriteStringPtrWithLen(p.Redirect.SchemeRedirect)
|
||||||
|
hash.WriteStringPtrWithLen(p.Redirect.HostRedirect)
|
||||||
|
hash.WriteUint32Ptr(p.Redirect.PortRedirect)
|
||||||
|
hash.WriteStringPtrWithLen(p.Redirect.PathRedirect)
|
||||||
|
hash.WriteStringPtrWithLen(p.Redirect.PrefixRewrite)
|
||||||
|
hash.WriteInt32Ptr(p.Redirect.ResponseCode)
|
||||||
|
hash.WriteBoolPtr(p.Redirect.StripQuery)
|
||||||
|
case p.Response != nil:
|
||||||
|
_, _ = hash.Write([]byte{3}) // case 3
|
||||||
|
hash.WriteInt32(int32(p.Response.Status))
|
||||||
|
hash.WriteStringWithLen(p.Response.Body)
|
||||||
|
default:
|
||||||
return 0, errEitherToOrRedirectOrResponseRequired
|
return 0, errEitherToOrRedirectOrResponseRequired
|
||||||
}
|
}
|
||||||
|
return hash.Sum64(), nil
|
||||||
return hashutil.Hash(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) MustRouteID() uint64 {
|
func (p *Policy) MustRouteID() uint64 {
|
||||||
|
@ -811,16 +844,6 @@ func (p *Policy) GetPassIdentityHeaders(options *Options) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type routeID struct {
|
|
||||||
From string
|
|
||||||
To []string
|
|
||||||
Prefix string
|
|
||||||
Path string
|
|
||||||
Regex string
|
|
||||||
Redirect *PolicyRedirect
|
|
||||||
Response *DirectResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SortPolicies sorts policies to match the following SQL order:
|
SortPolicies sorts policies to match the following SQL order:
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,9 @@ package config
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
mathrand "math/rand/v2"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||||
|
@ -13,6 +15,7 @@ import (
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/urlutil"
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_PolicyValidate(t *testing.T) {
|
func Test_PolicyValidate(t *testing.T) {
|
||||||
|
@ -421,3 +424,192 @@ func mustParseWeightedURLs(t testing.TB, urls ...string) []WeightedURL {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return wu
|
return wu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteID(t *testing.T) {
|
||||||
|
randomString := func() string {
|
||||||
|
return strings.TrimSuffix(cryptutil.NewRandomStringN(mathrand.IntN(31)+1), "=")
|
||||||
|
}
|
||||||
|
randomBool := func() bool {
|
||||||
|
return mathrand.N(2) == 0
|
||||||
|
}
|
||||||
|
randomURL := func() *url.URL {
|
||||||
|
u, err := url.Parse(fmt.Sprintf("https://%s.example.com/%s?foo=%s#%s",
|
||||||
|
randomString(), randomString(), randomString(), randomString()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
baseFieldMutators := []func(p *Policy){
|
||||||
|
func(p *Policy) { p.From = randomString() },
|
||||||
|
func(p *Policy) { p.Prefix = randomString() },
|
||||||
|
func(p *Policy) { p.Path = randomString() },
|
||||||
|
func(p *Policy) { p.Regex = randomString() },
|
||||||
|
}
|
||||||
|
toMutators := func(p *Policy) {
|
||||||
|
p.To = make(WeightedURLs, mathrand.N(9)+1)
|
||||||
|
for i := 0; i < len(p.To); i++ {
|
||||||
|
p.To[i] = WeightedURL{URL: *randomURL(), LbWeight: mathrand.Uint32()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redirectMutators := []func(p *PolicyRedirect){
|
||||||
|
func(p *PolicyRedirect) { p.HTTPSRedirect = randomPtr(10, randomBool()) },
|
||||||
|
func(p *PolicyRedirect) { p.SchemeRedirect = randomPtr(10, randomString()) },
|
||||||
|
func(p *PolicyRedirect) { p.HostRedirect = randomPtr(10, randomString()) },
|
||||||
|
func(p *PolicyRedirect) { p.PortRedirect = randomPtr(10, mathrand.Uint32()) },
|
||||||
|
func(p *PolicyRedirect) { p.PathRedirect = randomPtr(10, randomString()) },
|
||||||
|
func(p *PolicyRedirect) { p.PrefixRewrite = randomPtr(10, randomString()) },
|
||||||
|
func(p *PolicyRedirect) { p.ResponseCode = randomPtr(10, mathrand.Int32()) },
|
||||||
|
func(p *PolicyRedirect) { p.StripQuery = randomPtr(10, randomBool()) },
|
||||||
|
}
|
||||||
|
responseMutators := []func(p *DirectResponse){
|
||||||
|
func(p *DirectResponse) { p.Status = mathrand.Int() },
|
||||||
|
func(p *DirectResponse) { p.Body = randomString() },
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("random policies", func(t *testing.T) {
|
||||||
|
hashes := make(map[uint64]struct{}, 10000)
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
p := Policy{}
|
||||||
|
for _, m := range baseFieldMutators {
|
||||||
|
m(&p)
|
||||||
|
}
|
||||||
|
switch mathrand.IntN(3) {
|
||||||
|
case 0:
|
||||||
|
toMutators(&p)
|
||||||
|
case 1:
|
||||||
|
p.Redirect = &PolicyRedirect{}
|
||||||
|
for _, m := range redirectMutators {
|
||||||
|
m(p.Redirect)
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
p.Response = &DirectResponse{}
|
||||||
|
for _, m := range responseMutators {
|
||||||
|
m(p.Response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routeID, err := p.RouteID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
hashes[routeID] = struct{}{} // odds of a collision should be pretty low here
|
||||||
|
|
||||||
|
// check that computing the route id again results in the same value
|
||||||
|
routeID2, err := p.RouteID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, routeID, routeID2)
|
||||||
|
}
|
||||||
|
assert.Len(t, hashes, 10000)
|
||||||
|
})
|
||||||
|
t.Run("incremental policy", func(t *testing.T) {
|
||||||
|
hashes := make(map[uint64]Policy, 5000)
|
||||||
|
|
||||||
|
p := Policy{}
|
||||||
|
|
||||||
|
checkAdd := func(p *Policy) {
|
||||||
|
routeID, err := p.RouteID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
if existing, ok := hashes[routeID]; ok {
|
||||||
|
require.Equal(t, existing, *p)
|
||||||
|
} else {
|
||||||
|
hashes[routeID] = *p
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that computing the route id again results in the same value
|
||||||
|
routeID2, err := p.RouteID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, routeID, routeID2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// to
|
||||||
|
toMutators(&p)
|
||||||
|
checkAdd(&p)
|
||||||
|
|
||||||
|
// set base fields
|
||||||
|
for _, m := range baseFieldMutators {
|
||||||
|
m(&p)
|
||||||
|
checkAdd(&p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect
|
||||||
|
p.To = nil
|
||||||
|
p.Redirect = &PolicyRedirect{}
|
||||||
|
for range 1000 {
|
||||||
|
for _, m := range redirectMutators {
|
||||||
|
m(p.Redirect)
|
||||||
|
checkAdd(&p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update base fields
|
||||||
|
for _, m := range baseFieldMutators {
|
||||||
|
m(&p)
|
||||||
|
checkAdd(&p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// direct response
|
||||||
|
p.Redirect = nil
|
||||||
|
p.Response = &DirectResponse{}
|
||||||
|
for range 1000 {
|
||||||
|
for _, m := range responseMutators {
|
||||||
|
m(p.Response)
|
||||||
|
checkAdd(&p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update base fields
|
||||||
|
for _, m := range baseFieldMutators {
|
||||||
|
m(&p)
|
||||||
|
checkAdd(&p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
assert.Greater(t, len(hashes), 2000)
|
||||||
|
})
|
||||||
|
t.Run("field separation", func(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
a, b *Policy
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&Policy{From: "foo", Prefix: "bar"},
|
||||||
|
&Policy{From: "f", Prefix: "oobar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Policy{From: "foo", Prefix: "bar"},
|
||||||
|
&Policy{From: "foobar", Prefix: ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Policy{From: "foobar", Prefix: ""},
|
||||||
|
&Policy{From: "", Prefix: "foobar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Policy{From: "foo", Prefix: "", Path: "bar"},
|
||||||
|
&Policy{From: "foo", Prefix: "bar", Path: ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Policy{From: "", Prefix: "foo", Path: "bar"},
|
||||||
|
&Policy{From: "foo", Prefix: "bar", Path: ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Policy{From: "", Prefix: "foo", Path: "bar"},
|
||||||
|
&Policy{From: "foo", Prefix: "", Path: "bar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
c.a.To = mustParseWeightedURLs(t, "https://foo")
|
||||||
|
c.b.To = mustParseWeightedURLs(t, "https://foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
a, err := c.a.RouteID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
b, err := c.b.RouteID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEqual(t, a, b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomPtr[T any](nilChance int, t T) *T {
|
||||||
|
if mathrand.N(nilChance) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/testenv"
|
"github.com/pomerium/pomerium/internal/testenv"
|
||||||
|
@ -26,6 +27,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestLatency(t *testing.T) {
|
func TestRequestLatency(t *testing.T) {
|
||||||
|
runtime.MemProfileRate = 0
|
||||||
env := testenv.New(t, testenv.Silent())
|
env := testenv.New(t, testenv.Silent())
|
||||||
users := []*scenarios.User{}
|
users := []*scenarios.User{}
|
||||||
for i := range numRoutes {
|
for i := range numRoutes {
|
||||||
|
@ -51,6 +53,7 @@ func TestRequestLatency(t *testing.T) {
|
||||||
|
|
||||||
env.Start()
|
env.Start()
|
||||||
snippets.WaitStartupComplete(env)
|
snippets.WaitStartupComplete(env)
|
||||||
|
runtime.MemProfileRate = 512 * 1024
|
||||||
|
|
||||||
out := testing.Benchmark(func(b *testing.B) {
|
out := testing.Benchmark(func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
// Package hashutil provides NON-CRYPTOGRAPHIC utility functions for hashing.
|
// Package hashutil provides NON-CRYPTOGRAPHIC utility functions for hashing.
|
||||||
//
|
//
|
||||||
// http://cyan4973.github.io/xxHash/
|
// http://cyan4973.github.io/xxHash/
|
||||||
|
//
|
||||||
|
//nolint:errcheck
|
||||||
package hashutil
|
package hashutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/mitchellh/hashstructure/v2"
|
"github.com/mitchellh/hashstructure/v2"
|
||||||
)
|
)
|
||||||
|
@ -27,3 +31,176 @@ func Hash(v any) (uint64, error) {
|
||||||
}
|
}
|
||||||
return hashstructure.Hash(v, hashstructure.FormatV2, opts)
|
return hashstructure.Hash(v, hashstructure.FormatV2, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Digest struct {
|
||||||
|
xxhash.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDigest() *Digest {
|
||||||
|
var d Digest
|
||||||
|
d.Reset()
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStringWithLen writes the string's length, then its contents to the hash.
|
||||||
|
func (d *Digest) WriteStringWithLen(s string) {
|
||||||
|
d.WriteInt32(int32(len(s)))
|
||||||
|
d.WriteString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStringWithLen writes the byte array's length, then its contents to
|
||||||
|
// the hash.
|
||||||
|
func (d *Digest) WriteWithLen(b []byte) {
|
||||||
|
d.WriteInt32(int32(len(b)))
|
||||||
|
d.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBool writes a single byte (1 or 0) to the hash.
|
||||||
|
func (d *Digest) WriteBool(b bool) {
|
||||||
|
if b {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32 writes a uint16 to the hash.
|
||||||
|
func (d *Digest) WriteUint16(t uint16) {
|
||||||
|
var buf [2]byte
|
||||||
|
binary.LittleEndian.PutUint16(buf[:], t)
|
||||||
|
d.Write(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32 writes a uint32 to the hash.
|
||||||
|
func (d *Digest) WriteUint32(t uint32) {
|
||||||
|
var buf [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(buf[:], t)
|
||||||
|
d.Write(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32 writes a uint64 to the hash.
|
||||||
|
func (d *Digest) WriteUint64(t uint64) {
|
||||||
|
var buf [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(buf[:], t)
|
||||||
|
d.Write(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt16 writes an int16 to the hash.
|
||||||
|
func (d *Digest) WriteInt16(t int16) {
|
||||||
|
var buf [2]byte
|
||||||
|
binary.LittleEndian.PutUint16(buf[:], uint16(t))
|
||||||
|
d.Write(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt32 writes an int32 to the hash.
|
||||||
|
func (d *Digest) WriteInt32(t int32) {
|
||||||
|
var buf [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(buf[:], uint32(t))
|
||||||
|
d.Write(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt64 writes an int64 to the hash.
|
||||||
|
func (d *Digest) WriteInt64(t int64) {
|
||||||
|
var buf [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(buf[:], uint64(t))
|
||||||
|
d.Write(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStringPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteStringPtr(t *string) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteString(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStringPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the string's length and value, if present.
|
||||||
|
func (d *Digest) WriteStringPtrWithLen(t *string) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteStringWithLen(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBoolPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteBoolPtr(t *bool) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteBool(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint16Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteUint16Ptr(t *uint16) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteUint16(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteUint32Ptr(t *uint32) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteUint32(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint64Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteUint64Ptr(t *uint64) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteUint64(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt16Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteInt16Ptr(t *int16) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteInt16(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt32Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteInt32Ptr(t *int32) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteInt32(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt64Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
|
||||||
|
// followed by the value if present.
|
||||||
|
func (d *Digest) WriteInt64Ptr(t *int64) {
|
||||||
|
if t == nil {
|
||||||
|
d.Write([]byte{0})
|
||||||
|
} else {
|
||||||
|
d.Write([]byte{1})
|
||||||
|
d.WriteInt64(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue