Skip to content

Commit

Permalink
m
Browse files Browse the repository at this point in the history
  • Loading branch information
komuw committed Sep 15, 2023
1 parent 90ee0c3 commit dcd58c1
Show file tree
Hide file tree
Showing 15 changed files with 98 additions and 105 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ func main() {
"localhost",
65081,
secretKey,
middleware.DirectIpStrategy,
config.DirectIpStrategy,
l,
) // dev options.
// alternatively for production:
// opts := config.LetsEncryptOpts("example.com", "secretKey", middleware.DirectIpStrategy, l, "[email protected]")
// opts := config.LetsEncryptOpts("example.com", "secretKey", config.DirectIpStrategy, l, "[email protected]")

mux := mux.New(
opts,
Expand Down
42 changes: 39 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,45 @@ const (
DefaultSessionCookieDuration = 14 * time.Hour
)

// ClientIPstrategy is a middleware option that describes the strategy to use when fetching the client's IP address.
type ClientIPstrategy = clientip.ClientIPstrategy

const (
// DirectIpStrategy derives the client IP from [http.Request.RemoteAddr].
// It should be used if the server accepts direct connections, rather than through a proxy.
//
// See the warning in [ClientIP]
DirectIpStrategy = clientip.DirectIpStrategy

// LeftIpStrategy derives the client IP from the leftmost valid & non-private IP address in the `X-Fowarded-For` or `Forwarded` header.
//
// See the warning in [ClientIP]
LeftIpStrategy = clientip.LeftIpStrategy

// RightIpStrategy derives the client IP from the rightmost valid & non-private IP address in the `X-Fowarded-For` or `Forwarded` header.
//
// See the warning in [ClientIP]
RightIpStrategy = clientip.RightIpStrategy

// ProxyStrategy derives the client IP from the [PROXY protocol v1].
// This should be used when your application is behind a TCP proxy that uses the v1 PROXY protocol.
//
// See the warning in [ClientIP]
//
// [PROXY protocol v1]: https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt
ProxyStrategy = clientip.ProxyStrategy
)

// SingleIpStrategy derives the client IP from http header headerName.
//
// headerName MUST NOT be either `X-Forwarded-For` or `Forwarded`.
// It can be something like `CF-Connecting-IP`, `Fastly-Client-IP`, `Fly-Client-IP`, etc; depending on your usecase.
//
// See the warning in [ClientIP]
func SingleIpStrategy(headerName string) ClientIPstrategy {
return ClientIPstrategy(headerName)
}

const (
// DefaultMaxBodyBytes is the value used as the limit for incoming request bodies, if a custom value was not provided.
//
Expand Down Expand Up @@ -109,9 +148,6 @@ const (
LetsEncryptStagingUrl = "https://acme-staging-v02.api.letsencrypt.org/directory"
)

// ClientIPstrategy is a middleware option that describes the strategy to use when fetching the client's IP address.
type ClientIPstrategy = clientip.ClientIPstrategy

// Opts are the various parameters(optionals) that can be used to configure ong.
//
// Use either [New], [WithOpts], [DevOpts], [CertOpts], [AcmeOpts] or [LetsEncryptOpts] to get a valid Opts.
Expand Down
5 changes: 1 addition & 4 deletions config/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ import (

func loginHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cspNonce := middleware.GetCspNonce(r.Context())
_ = cspNonce // use CSP nonce

_, _ = fmt.Fprint(w, "welcome to your favorite website.")
}
}
Expand All @@ -32,7 +29,7 @@ func ExampleNew() {
// The security key to use for securing signed data.
"super-h@rd-Pas1word",
// In this case, the actual client IP address is fetched from the given http header.
middleware.SingleIpStrategy("CF-Connecting-IP"),
config.SingleIpStrategy("CF-Connecting-IP"),
// Logger.
l,
// log 90% of all responses that are either rate-limited or loadshed.
Expand Down
2 changes: 1 addition & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {

api := NewApp(myDB{map[string]string{}}, l)
mux := mux.New(
config.WithOpts("localhost", 65081, secretKey, middleware.DirectIpStrategy, l),
config.WithOpts("localhost", 65081, secretKey, config.DirectIpStrategy, l),
nil,
mux.NewRoute(
"/health",
Expand Down
16 changes: 8 additions & 8 deletions internal/mx/mx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestNewRoute(t *testing.T) {
MethodGet,
middleware.Get(
someMuxHandler("msg"),
config.WithOpts("localhost", 443, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts("localhost", 443, tst.SecretKey(), config.DirectIpStrategy, l),
),
)
attest.Error(t, errB)
Expand All @@ -97,7 +97,7 @@ func TestMux(t *testing.T) {
)
attest.Ok(t, err)
mux, err := New(
config.WithOpts("localhost", 443, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts("localhost", 443, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
rt,
)
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestMux(t *testing.T) {
)
attest.Ok(t, err)
mux, err := New(
config.WithOpts(domain, httpsPort, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts(domain, httpsPort, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
rt,
)
Expand Down Expand Up @@ -175,7 +175,7 @@ func TestMux(t *testing.T) {
)
attest.Ok(t, err)
mux, err := New(
config.WithOpts(domain, httpsPort, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts(domain, httpsPort, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
rt,
)
Expand Down Expand Up @@ -219,7 +219,7 @@ func TestMux(t *testing.T) {
attest.Ok(t, err)

_, errC := New(
config.WithOpts("localhost", 443, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts("localhost", 443, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
rt1,
rt2,
Expand Down Expand Up @@ -250,7 +250,7 @@ func TestMux(t *testing.T) {
)
attest.Ok(t, err)
mux, err := New(
config.WithOpts("localhost", 443, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts("localhost", 443, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
rt1,
rt2,
Expand Down Expand Up @@ -343,7 +343,7 @@ func TestMux(t *testing.T) {
)
attest.Ok(t, err)
mux, err := New(
config.WithOpts(domain, httpsPort, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts(domain, httpsPort, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
rt,
)
Expand Down Expand Up @@ -438,7 +438,7 @@ func BenchmarkMuxNew(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
mux, err := New(
config.WithOpts("localhost", 443, tst.SecretKey(), middleware.DirectIpStrategy, l),
config.WithOpts("localhost", 443, tst.SecretKey(), config.DirectIpStrategy, l),
nil,
getManyRoutes(b)...,
)
Expand Down
50 changes: 6 additions & 44 deletions middleware/client_ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package middleware
import (
"net/http"

"github.com/komuw/ong/config"
"github.com/komuw/ong/internal/clientip"
)

Expand All @@ -11,45 +12,6 @@ import (
// (a) https://github.com/realclientip/realclientip-go whose license(BSD Zero Clause License) can be found here: https://github.com/realclientip/realclientip-go/blob/v1.0.0/LICENSE
//

// ClientIPstrategy is a middleware option that describes the strategy to use when fetching the client's IP address.
type ClientIPstrategy = clientip.ClientIPstrategy

const (
// DirectIpStrategy derives the client IP from [http.Request.RemoteAddr].
// It should be used if the server accepts direct connections, rather than through a proxy.
//
// See the warning in [ClientIP]
DirectIpStrategy = clientip.DirectIpStrategy

// LeftIpStrategy derives the client IP from the leftmost valid & non-private IP address in the `X-Fowarded-For` or `Forwarded` header.
//
// See the warning in [ClientIP]
LeftIpStrategy = clientip.LeftIpStrategy

// RightIpStrategy derives the client IP from the rightmost valid & non-private IP address in the `X-Fowarded-For` or `Forwarded` header.
//
// See the warning in [ClientIP]
RightIpStrategy = clientip.RightIpStrategy

// ProxyStrategy derives the client IP from the [PROXY protocol v1].
// This should be used when your application is behind a TCP proxy that uses the v1 PROXY protocol.
//
// See the warning in [ClientIP]
//
// [PROXY protocol v1]: https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt
ProxyStrategy = clientip.ProxyStrategy
)

// SingleIpStrategy derives the client IP from http header headerName.
//
// headerName MUST NOT be either `X-Forwarded-For` or `Forwarded`.
// It can be something like `CF-Connecting-IP`, `Fastly-Client-IP`, `Fly-Client-IP`, etc; depending on your usecase.
//
// See the warning in [ClientIP]
func SingleIpStrategy(headerName string) ClientIPstrategy {
return ClientIPstrategy(headerName)
}

// ClientIP returns the "real" client IP address. This will be based on the [ClientIPstrategy] that you chose.
//
// Warning: This should be used with caution. Clients CAN easily spoof IP addresses.
Expand All @@ -69,17 +31,17 @@ func ClientIP(r *http.Request) string {
// Fetching the "real" client is done in a best-effort basis and can be [grossly inaccurate & precarious].
//
// [grossly inaccurate & precarious]: https://adam-p.ca/blog/2022/03/x-forwarded-for/
func clientIP(wrappedHandler http.Handler, strategy ClientIPstrategy) http.HandlerFunc {
func clientIP(wrappedHandler http.Handler, strategy config.ClientIPstrategy) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var clientAddr string
switch v := strategy; v {
case DirectIpStrategy:
case config.DirectIpStrategy:
clientAddr = clientip.DirectAddress(r.RemoteAddr)
case LeftIpStrategy:
case config.LeftIpStrategy:
clientAddr = clientip.Leftmost(r.Header)
case RightIpStrategy:
case config.RightIpStrategy:
clientAddr = clientip.Rightmost(r.Header)
case ProxyStrategy:
case config.ProxyStrategy:
clientAddr = clientip.ProxyHeader(r.Header)
default:
// treat everything else as a `singleIP` strategy
Expand Down
17 changes: 9 additions & 8 deletions middleware/client_ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"
"testing"

"github.com/komuw/ong/config"
"go.akshayshah.org/attest"
)

Expand Down Expand Up @@ -35,7 +36,7 @@ func TestClientIP(t *testing.T) {
t.Parallel()

msg := "hello"
wrappedHandler := clientIP(someClientIpHandler(msg), DirectIpStrategy)
wrappedHandler := clientIP(someClientIpHandler(msg), config.DirectIpStrategy)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/someUri", nil)
wrappedHandler.ServeHTTP(rec, req)
Expand All @@ -55,18 +56,18 @@ func TestClientIP(t *testing.T) {

tests := []struct {
name string
strategy ClientIPstrategy
strategy config.ClientIPstrategy
req func() *http.Request
expected string
}{
{
name: "DirectIpStrategy",
strategy: DirectIpStrategy,
strategy: config.DirectIpStrategy,
req: func() *http.Request { return httptest.NewRequest(http.MethodGet, "/someUri", nil) },
},
{
name: "SingleIpStrategy",
strategy: SingleIpStrategy("Fly-Client-IP"),
strategy: config.SingleIpStrategy("Fly-Client-IP"),
req: func() *http.Request {
r := httptest.NewRequest(http.MethodGet, "/someUri", nil)
r.Header.Add("Fly-Client-IP", publicIP)
Expand All @@ -76,7 +77,7 @@ func TestClientIP(t *testing.T) {
},
{
name: "LeftIpStrategy",
strategy: LeftIpStrategy,
strategy: config.LeftIpStrategy,
req: func() *http.Request {
r := httptest.NewRequest(http.MethodGet, "/someUri", nil)
r.Header.Add(xForwardedForHeader, publicIP)
Expand All @@ -86,7 +87,7 @@ func TestClientIP(t *testing.T) {
},
{
name: "RightIpStrategy",
strategy: RightIpStrategy,
strategy: config.RightIpStrategy,
req: func() *http.Request {
r := httptest.NewRequest(http.MethodGet, "/someUri", nil)
r.Header.Add(xForwardedForHeader, publicIP)
Expand All @@ -96,7 +97,7 @@ func TestClientIP(t *testing.T) {
},
{
name: "ProxyStrategy",
strategy: ProxyStrategy,
strategy: config.ProxyStrategy,
req: func() *http.Request {
r := httptest.NewRequest(http.MethodGet, "/someUri", nil)
r.Header.Add(proxyHeader,
Expand Down Expand Up @@ -138,7 +139,7 @@ func TestClientIP(t *testing.T) {
t.Parallel()

msg := "hello"
wrappedHandler := clientIP(someClientIpHandler(msg), DirectIpStrategy)
wrappedHandler := clientIP(someClientIpHandler(msg), config.DirectIpStrategy)

runhandler := func() {
rec := httptest.NewRecorder()
Expand Down
10 changes: 5 additions & 5 deletions middleware/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func Example_getCspNonce() {
l := log.New(context.Background(), os.Stdout, 100)
handler := middleware.Get(
loginHandler(),
config.WithOpts("example.com", 443, "super-h@rd-Pas1word", middleware.DirectIpStrategy, l),
config.WithOpts("example.com", 443, "super-h@rd-Pas1word", config.DirectIpStrategy, l),
)
_ = handler // use handler

Expand All @@ -46,7 +46,7 @@ func Example_getCsrfToken() {
l := log.New(context.Background(), os.Stdout, 100)
handler := middleware.Get(
welcomeHandler(),
config.WithOpts("example.com", 443, "super-h@rd-Pas1word", middleware.DirectIpStrategy, l),
config.WithOpts("example.com", 443, "super-h@rd-Pas1word", config.DirectIpStrategy, l),
)
_ = handler // use handler

Expand All @@ -55,7 +55,7 @@ func Example_getCsrfToken() {

func ExampleGet() {
l := log.New(context.Background(), os.Stdout, 100)
opts := config.WithOpts("example.com", 443, "super-h@rd-Pas1word", middleware.DirectIpStrategy, l)
opts := config.WithOpts("example.com", 443, "super-h@rd-Pas1word", config.DirectIpStrategy, l)
handler := middleware.Get(loginHandler(), opts)
_ = handler // use handler

Expand All @@ -64,7 +64,7 @@ func ExampleGet() {

func ExampleAll() {
l := log.New(context.Background(), os.Stdout, 100)
opts := config.WithOpts("example.com", 443, "super-h@rd-Pas1word", middleware.DirectIpStrategy, l)
opts := config.WithOpts("example.com", 443, "super-h@rd-Pas1word", config.DirectIpStrategy, l)

myHandler := http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
Expand All @@ -87,7 +87,7 @@ func ExampleWithOpts() {
443,
"super-h@rd-Pas1word",
// assuming your application is deployed behind cloudflare.
middleware.SingleIpStrategy("CF-Connecting-IP"),
config.SingleIpStrategy("CF-Connecting-IP"),
l,
)
_ = opts
Expand Down
Loading

0 comments on commit dcd58c1

Please sign in to comment.