Skip to content

Commit

Permalink
Merge pull request #106 from ngrok/mo/bot_filter_mw
Browse files Browse the repository at this point in the history
config:  add user agent module
  • Loading branch information
jrobsonchase authored Sep 12, 2023
2 parents d26a592 + c5c7044 commit 90456bf
Show file tree
Hide file tree
Showing 10 changed files with 510 additions and 211 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.4.0
1.4.1
4 changes: 4 additions & 0 deletions config/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ type httpOptions struct {
// WebhookVerification configuration.
// If nil, WebhookVerification is disabled.
WebhookVerification *webhookVerification
// UserAgentFilter configuration
// If nil, UserAgentFilter is disabled
UserAgentFilter *userAgentFilter
}

func (cfg *httpOptions) toProtoConfig() *proto.HTTPEndpoint {
Expand Down Expand Up @@ -113,6 +116,7 @@ func (cfg *httpOptions) toProtoConfig() *proto.HTTPEndpoint {
opts.OIDC = cfg.OIDC.toProtoConfig()
opts.WebhookVerification = cfg.WebhookVerification.toProtoConfig()
opts.IPRestriction = cfg.commonOpts.CIDRRestrictions.toProtoConfig()
opts.UserAgentFilter = cfg.UserAgentFilter.toProtoConfig()

return opts
}
Expand Down
74 changes: 74 additions & 0 deletions config/user_agent_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package config

import (
"golang.ngrok.com/ngrok/internal/pb"
)

// UserAgentFilter is a pair of strings slices that allow/deny traffic to an endpoint
type userAgentFilter struct {
// slice of regex strings for allowed user agents
Allow []string
// slice of regex strings for denied user agents
Deny []string
}

// WithAllowUserAgentFilter adds user agent filtering to the endpoint.
//
// The allow argument is a regular expressions for the user-agent
// header to allow
//
// Any invalid regular expression will result in an error when creating the tunnel.
//
// https://ngrok.com/docs/cloud-edge/modules/user-agent-filter
// ERR_NGROK_2090 for invalid allow/deny on connect.
// ERR_NGROK_3211 The server does not authorize requests from your user-agent
// ERR_NGROK_9022 Your account is not authorized to use user agent filtering.
func WithAllowUserAgentFilter(allow ...string) HTTPEndpointOption {
return userAgentFilter{
// slice of regex strings for allowed user agents
Allow: allow,
}
}

// WithDenyUserAgentFilter adds user agent filtering to the endpoint.
//
// The deny argument is a regular expressions to
// deny, with allows taking precedence over denies.
//
// Any invalid regular expression will result in an error when creating the tunnel.
//
// https://ngrok.com/docs/cloud-edge/modules/user-agent-filter
// ERR_NGROK_2090 for invalid allow/deny on connect.
// ERR_NGROK_3211 The server does not authorize requests from your user-agent
// ERR_NGROK_9022 Your account is not authorized to use user agent filtering.
func WithDenyUserAgentFilter(deny ...string) HTTPEndpointOption {
return userAgentFilter{
// slice of regex strings for denied user agents
Deny: deny,
}
}

func (b *userAgentFilter) toProtoConfig() *pb.MiddlewareConfiguration_UserAgentFilter {
if b == nil {
return nil
}
return &pb.MiddlewareConfiguration_UserAgentFilter{
Allow: b.Allow,
Deny: b.Deny,
}
}

func (b *userAgentFilter) merge(set userAgentFilter) *userAgentFilter {
if b == nil {
b = &userAgentFilter{}
}

b.Allow = append(b.Allow, set.Allow...)
b.Deny = append(b.Deny, set.Deny...)

return b
}

func (b userAgentFilter) ApplyHTTP(opts *httpOptions) {
opts.UserAgentFilter = opts.UserAgentFilter.merge(b)
}
102 changes: 102 additions & 0 deletions config/user_agent_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package config

import (
"testing"

"github.com/stretchr/testify/require"

"golang.ngrok.com/ngrok/internal/pb"
"golang.ngrok.com/ngrok/internal/tunnel/proto"
)

func testUserAgentFilter[T tunnelConfigPrivate, O any, OT any](t *testing.T,
makeOpts func(...OT) Tunnel,
getUserAgentFilter func(*O) *pb.MiddlewareConfiguration_UserAgentFilter,
) {
optsFunc := func(opts ...any) Tunnel {
return makeOpts(assertSlice[OT](opts)...)
}
cases := testCases[T, O]{
{
name: "test empty",
opts: optsFunc(),
expectOpts: func(t *testing.T, opts *O) {
actual := getUserAgentFilter(opts)
require.Nil(t, actual)
},
},
{
name: "test allow",
opts: optsFunc(
WithAllowUserAgentFilter(`(Pingdom\.com_bot_version_)(\d+)\.(\d+)`),
),
expectOpts: func(t *testing.T, opts *O) {
actual := getUserAgentFilter(opts)
require.NotNil(t, actual)
require.Nil(t, actual.Deny)
require.NotNil(t, actual.Allow)
require.Len(t, actual.Allow, 1)
require.Len(t, actual.Deny, 0)
require.Contains(t, actual.Allow, `(Pingdom\.com_bot_version_)(\d+)\.(\d+)`)
},
},
{
name: "test deny",
opts: optsFunc(
WithDenyUserAgentFilter(`(Pingdom\.com_bot_version_)(\d+)\.(\d+)`),
),
expectOpts: func(t *testing.T, opts *O) {
actual := getUserAgentFilter(opts)
require.NotNil(t, actual)
require.Nil(t, actual.Allow)
require.Len(t, actual.Allow, 0)
require.NotNil(t, actual.Deny)
require.Len(t, actual.Deny, 1)
require.Contains(t, actual.Deny, `(Pingdom\.com_bot_version_)(\d+)\.(\d+)`)
},
},
{
name: "test allow and deny",
opts: optsFunc(
WithAllowUserAgentFilter(`(Pingdom\.com_bot_version_)(\d+)\.(\d+)`),
WithDenyUserAgentFilter(`(Pingdom\.com_bot_version_)(\d+)\.(\d+)`),
),
expectOpts: func(t *testing.T, opts *O) {
actual := getUserAgentFilter(opts)
require.NotNil(t, actual)
require.Len(t, actual.Allow, 1)
require.Len(t, actual.Deny, 1)
require.Contains(t, actual.Allow, `(Pingdom\.com_bot_version_)(\d+)\.(\d+)`)
require.Contains(t, actual.Deny, `(Pingdom\.com_bot_version_)(\d+)\.(\d+)`)
},
},
{
name: "test multiple",
opts: optsFunc(
WithAllowUserAgentFilter(`(Pingdom\.com_bot_version_)(\d+)\.(\d+)`),
WithDenyUserAgentFilter(`(Pingdom\.com_bot_version_)(\d+)\.(\d+)`),
WithAllowUserAgentFilter(`(Pingdom2\.com_bot_version_)(\d+)\.(\d+)`),
WithDenyUserAgentFilter(`(Pingdom2\.com_bot_version_)(\d+)\.(\d+)`),
),
expectOpts: func(t *testing.T, opts *O) {
actual := getUserAgentFilter(opts)
require.NotNil(t, actual)
require.Len(t, actual.Allow, 2)
require.Len(t, actual.Deny, 2)
require.Contains(t, actual.Allow, `(Pingdom\.com_bot_version_)(\d+)\.(\d+)`)
require.Contains(t, actual.Deny, `(Pingdom\.com_bot_version_)(\d+)\.(\d+)`)
require.Contains(t, actual.Allow, `(Pingdom2\.com_bot_version_)(\d+)\.(\d+)`)
require.Contains(t, actual.Deny, `(Pingdom2\.com_bot_version_)(\d+)\.(\d+)`)
},
},
}

cases.runAll(t)
}

func TestUserAgentFilter(t *testing.T) {
testUserAgentFilter[httpOptions](t, HTTPEndpoint,
func(h *proto.HTTPEndpoint) *pb.MiddlewareConfiguration_UserAgentFilter {
return h.UserAgentFilter
})
}
7 changes: 4 additions & 3 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.ngrok.com/muxado/v2 v2.0.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)

Expand Down
16 changes: 9 additions & 7 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.ngrok.com/muxado/v2 v2.0.0 h1:bu9eIDhRdYNtIXNnqat/HyMeHYOAbUH55ebD7gTvW6c=
golang.ngrok.com/muxado/v2 v2.0.0/go.mod h1:wzxJYX4xiAtmwumzL+QsukVwFRXmPNv86vB8RPpOxyM=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
Expand Down
6 changes: 6 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
Expand All @@ -13,6 +14,7 @@ github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
Expand All @@ -22,19 +24,23 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.ngrok.com/muxado/v2 v2.0.0 h1:bu9eIDhRdYNtIXNnqat/HyMeHYOAbUH55ebD7gTvW6c=
golang.ngrok.com/muxado/v2 v2.0.0/go.mod h1:wzxJYX4xiAtmwumzL+QsukVwFRXmPNv86vB8RPpOxyM=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
Expand Down
Loading

0 comments on commit 90456bf

Please sign in to comment.