mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 10:26:29 +02:00
authorize: compute "real" IP from X-Forwarded-For
WIP -- this doesn't work correctly yet Update the authorize log IP address field to match the access log IP address field. Add a new method getIPAddress() to compute the "real" client IP address based on the contents of the X-Forwarded-For header and the XffNumTrustedHops and SkipXffAppend configuration option. This is intended to match downstream_remote_address from the access log: https://www.envoyproxy.io/docs/envoy/latest/api-v3/data/accesslog/v3/accesslog.proto#envoy-v3-api-field-data-accesslog-v3-accesslogcommon-downstream-remote-address
This commit is contained in:
parent
fd84075af1
commit
42be134173
2 changed files with 115 additions and 4 deletions
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/authorize/evaluator"
|
"github.com/pomerium/pomerium/authorize/evaluator"
|
||||||
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/requestid"
|
"github.com/pomerium/pomerium/internal/telemetry/requestid"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||||
|
@ -32,9 +33,10 @@ func (a *Authorize) logAuthorizeCheck(
|
||||||
impersonateDetails := a.getImpersonateDetails(ctx, s)
|
impersonateDetails := a.getImpersonateDetails(ctx, s)
|
||||||
|
|
||||||
evt := log.Info(ctx).Str("service", "authorize")
|
evt := log.Info(ctx).Str("service", "authorize")
|
||||||
fields := a.currentOptions.Load().GetAuthorizeLogFields()
|
currentOptions := a.currentOptions.Load()
|
||||||
|
fields := currentOptions.GetAuthorizeLogFields()
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
evt = populateLogEvent(ctx, field, evt, in, s, u, hdrs, impersonateDetails)
|
evt = populateLogEvent(ctx, field, evt, in, s, u, hdrs, impersonateDetails, currentOptions)
|
||||||
}
|
}
|
||||||
evt = log.HTTPHeaders(evt, fields, hdrs)
|
evt = log.HTTPHeaders(evt, fields, hdrs)
|
||||||
|
|
||||||
|
@ -152,6 +154,7 @@ func populateLogEvent(
|
||||||
u *user.User,
|
u *user.User,
|
||||||
hdrs map[string]string,
|
hdrs map[string]string,
|
||||||
impersonateDetails *impersonateDetails,
|
impersonateDetails *impersonateDetails,
|
||||||
|
currentOptions *config.Options,
|
||||||
) *zerolog.Event {
|
) *zerolog.Event {
|
||||||
path, query, _ := strings.Cut(in.GetAttributes().GetRequest().GetHttp().GetPath(), "?")
|
path, query, _ := strings.Cut(in.GetAttributes().GetRequest().GetHttp().GetPath(), "?")
|
||||||
|
|
||||||
|
@ -192,7 +195,7 @@ func populateLogEvent(
|
||||||
}
|
}
|
||||||
return evt
|
return evt
|
||||||
case log.AuthorizeLogFieldIP:
|
case log.AuthorizeLogFieldIP:
|
||||||
return evt.Str(string(field), in.GetAttributes().GetSource().GetAddress().GetSocketAddress().GetAddress())
|
return evt.Str(string(field), getIPAddress(in.GetAttributes(), currentOptions))
|
||||||
case log.AuthorizeLogFieldMethod:
|
case log.AuthorizeLogFieldMethod:
|
||||||
return evt.Str(string(field), in.GetAttributes().GetRequest().GetHttp().GetMethod())
|
return evt.Str(string(field), in.GetAttributes().GetRequest().GetHttp().GetMethod())
|
||||||
case log.AuthorizeLogFieldPath:
|
case log.AuthorizeLogFieldPath:
|
||||||
|
@ -217,3 +220,34 @@ func populateLogEvent(
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getIPAddress(attributes *envoy_service_auth_v3.AttributeContext, cfg *config.Options) string {
|
||||||
|
socketAddress := attributes.GetSource().GetAddress().GetSocketAddress().GetAddress()
|
||||||
|
xForwardedFor := attributes.GetRequest().GetHttp().GetHeaders()["x-forwarded-for"]
|
||||||
|
|
||||||
|
// If XffNumTrustedHops is zero, we should not trust anything in the
|
||||||
|
// X-Forwarded-For header, so return the actual socket address.
|
||||||
|
// If the X-Forwarded-For header is empty, we should not trust it either.
|
||||||
|
if cfg.XffNumTrustedHops == 0 || xForwardedFor == "" {
|
||||||
|
return socketAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the X-Forwarded-For header by splitting on the ',' character.
|
||||||
|
addresses := strings.Split(xForwardedFor, ",")
|
||||||
|
|
||||||
|
// Unless SkipXffAppend is set, the last address in X-Forwarded-For was
|
||||||
|
// appended by Envoy, so it shouldn't count as a separate hop.
|
||||||
|
hops := cfg.XffNumTrustedHops
|
||||||
|
if !cfg.SkipXffAppend {
|
||||||
|
hops++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the actual socket address if the X-Forwarded-For header has
|
||||||
|
// fewer entries than expected (or if XffNumTrustedHops was set to exactly
|
||||||
|
// the maximum uint32 value and SkipXffAppend is false).
|
||||||
|
lenAddresses := uint32(len(addresses))
|
||||||
|
if hops > lenAddresses || hops == 0 {
|
||||||
|
return socketAddress
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(addresses[lenAddresses-hops])
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/requestid"
|
"github.com/pomerium/pomerium/internal/telemetry/requestid"
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||||
|
@ -62,6 +63,7 @@ func Test_populateLogEvent(t *testing.T) {
|
||||||
sessionID: "IMPERSONATE-SESSION-ID",
|
sessionID: "IMPERSONATE-SESSION-ID",
|
||||||
userID: "IMPERSONATE-USER-ID",
|
userID: "IMPERSONATE-USER-ID",
|
||||||
}
|
}
|
||||||
|
opts := &config.Options{}
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
field log.AuthorizeLogField
|
field log.AuthorizeLogField
|
||||||
|
@ -93,10 +95,85 @@ func Test_populateLogEvent(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
log := zerolog.New(&buf)
|
log := zerolog.New(&buf)
|
||||||
evt := log.Log()
|
evt := log.Log()
|
||||||
evt = populateLogEvent(ctx, tc.field, evt, checkRequest, tc.s, u, headers, impersonateDetails)
|
evt = populateLogEvent(ctx, tc.field, evt, checkRequest, tc.s, u, headers, impersonateDetails, opts)
|
||||||
evt.Send()
|
evt.Send()
|
||||||
|
|
||||||
assert.Equal(t, tc.expect, strings.TrimSpace(buf.String()))
|
assert.Equal(t, tc.expect, strings.TrimSpace(buf.String()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getIPAddress(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
label string
|
||||||
|
socketIP string
|
||||||
|
xForwardedFor string
|
||||||
|
xffNumTrustedHops uint32
|
||||||
|
skipXffAppend bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"xff_num_trusted_hops=0",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 0, false, "127.0.0.1"},
|
||||||
|
{"xff_num_trusted_hops=1",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 1, false, "10.1.1.1"},
|
||||||
|
{"xff_num_trusted_hops=2",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 2, false, "10.2.2.2"},
|
||||||
|
{"xff_num_trusted_hops=3",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 3, false, "127.0.0.1"},
|
||||||
|
{"xff_num_trusted_hops=4",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 4, false, "127.0.0.1"},
|
||||||
|
|
||||||
|
{"xff_num_trusted_hops=0 skip_xff_append",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1", 0, true, "127.0.0.1"},
|
||||||
|
{"xff_num_trusted_hops=1 skip_xff_append",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1", 1, true, "10.1.1.1"},
|
||||||
|
{"xff_num_trusted_hops=2 skip_xff_append",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1", 2, true, "10.2.2.2"},
|
||||||
|
{"xff_num_trusted_hops=3 skip_xff_append",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1", 3, true, "127.0.0.1"},
|
||||||
|
{"xff_num_trusted_hops=4 skip_xff_append",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1", 4, true, "127.0.0.1"},
|
||||||
|
|
||||||
|
{"xff_num_trusted_hops int32 overflow",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 0x80000000, false, "127.0.0.1"},
|
||||||
|
{"xff_num_trusted_hops uint32 max",
|
||||||
|
"127.0.0.1", "10.2.2.2, 10.1.1.1, 10.0.0.0", 0xffffffff, false, "127.0.0.1"},
|
||||||
|
|
||||||
|
{"empty X-Forwarded-For",
|
||||||
|
"127.0.0.1", "", 1, false, "127.0.0.1"},
|
||||||
|
{"empty X-Forwarded-For skip_xff_append",
|
||||||
|
"127.0.0.1", "", 1, true, "127.0.0.1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range cases {
|
||||||
|
c := &cases[i]
|
||||||
|
t.Run(c.label, func(t *testing.T) {
|
||||||
|
attributes := &envoy_service_auth_v3.AttributeContext{
|
||||||
|
Request: &envoy_service_auth_v3.AttributeContext_Request{
|
||||||
|
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"x-forwarded-for": c.xForwardedFor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Source: &envoy_service_auth_v3.AttributeContext_Peer{
|
||||||
|
Address: &envoy_config_core_v3.Address{
|
||||||
|
Address: &envoy_config_core_v3.Address_SocketAddress{
|
||||||
|
SocketAddress: &envoy_config_core_v3.SocketAddress{
|
||||||
|
Address: c.socketIP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
options := &config.Options{
|
||||||
|
XffNumTrustedHops: c.xffNumTrustedHops,
|
||||||
|
SkipXffAppend: c.skipXffAppend,
|
||||||
|
}
|
||||||
|
actual := getIPAddress(attributes, options)
|
||||||
|
assert.Equal(t, c.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue