Skip to content

Commit

Permalink
home: real ip in logs
Browse files Browse the repository at this point in the history
  • Loading branch information
schzhn committed Mar 11, 2024
1 parent 36f9fec commit 2aee9a6
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 38 deletions.
31 changes: 20 additions & 11 deletions internal/home/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"go.etcd.io/bbolt"
"golang.org/x/crypto/bcrypt"
)
Expand Down Expand Up @@ -53,12 +54,13 @@ func (s *session) deserialize(data []byte) bool {

// Auth - global object
type Auth struct {
db *bbolt.DB
rateLimiter *authRateLimiter
sessions map[string]*session
users []webUser
lock sync.Mutex
sessionTTL uint32
db *bbolt.DB
rateLimiter *authRateLimiter
sessions map[string]*session
users []webUser
trustedProxies []netutil.Prefix
lock sync.Mutex
sessionTTL uint32
}

// webUser represents a user of the Web UI.
Expand All @@ -70,14 +72,21 @@ type webUser struct {
}

// InitAuth - create a global object
func InitAuth(dbFilename string, users []webUser, sessionTTL uint32, rateLimiter *authRateLimiter) *Auth {
func InitAuth(
dbFilename string,
users []webUser,
sessionTTL uint32,
rateLimiter *authRateLimiter,
trustedProxies []netutil.Prefix,
) *Auth {
log.Info("Initializing auth module: %s", dbFilename)

a := &Auth{
sessionTTL: sessionTTL,
rateLimiter: rateLimiter,
sessions: make(map[string]*session),
users: users,
sessionTTL: sessionTTL,
rateLimiter: rateLimiter,
sessions: make(map[string]*session),
users: users,
trustedProxies: trustedProxies,
}
var err error
a.db, err = bbolt.Open(dbFilename, 0o644, nil)
Expand Down
6 changes: 3 additions & 3 deletions internal/home/auth_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestAuth(t *testing.T) {
Name: "name",
PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2",
}}
a := InitAuth(fn, nil, 60, nil)
a := InitAuth(fn, nil, 60, nil, nil)
s := session{}

user := webUser{Name: "name"}
Expand Down Expand Up @@ -66,7 +66,7 @@ func TestAuth(t *testing.T) {
a.Close()

// load saved session
a = InitAuth(fn, users, 60, nil)
a = InitAuth(fn, users, 60, nil, nil)

// the session is still alive
assert.Equal(t, checkSessionOK, a.checkSession(sessStr))
Expand All @@ -82,7 +82,7 @@ func TestAuth(t *testing.T) {
time.Sleep(3 * time.Second)

// load and remove expired sessions
a = InitAuth(fn, users, 60, nil)
a = InitAuth(fn, users, 60, nil, nil)
assert.Equal(t, checkSessionNotFound, a.checkSession(sessStr))

a.Close()
Expand Down
50 changes: 34 additions & 16 deletions internal/home/authhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"path"
"strconv"
"strings"
Expand Down Expand Up @@ -78,7 +78,7 @@ func (a *Auth) newCookie(req loginJSON, addr string) (c *http.Cookie, err error)
// a well-maintained third-party module.
//
// TODO(a.garipov): Support header Forwarded from RFC 7329.
func realIP(r *http.Request) (ip net.IP, err error) {
func realIP(r *http.Request) (ip netip.Addr, err error) {
proxyHeaders := []string{
httphdr.CFConnectingIP,
httphdr.TrueClientIP,
Expand All @@ -87,8 +87,8 @@ func realIP(r *http.Request) (ip net.IP, err error) {

for _, h := range proxyHeaders {
v := r.Header.Get(h)
ip = net.ParseIP(v)
if ip != nil {
ip, err = netip.ParseAddr(v)
if err == nil {
return ip, nil
}
}
Expand All @@ -97,19 +97,19 @@ func realIP(r *http.Request) (ip net.IP, err error) {
// from the X-Forwarded-For header.
s := r.Header.Get(httphdr.XForwardedFor)
ipStrs := strings.SplitN(s, ", ", 2)
ip = net.ParseIP(ipStrs[0])
if ip != nil {
ip, err = netip.ParseAddr(ipStrs[0])
if err == nil {
return ip, nil
}

// When everything else fails, just return the remote address as understood
// by the stdlib.
ipStr, err := netutil.SplitHost(r.RemoteAddr)
if err != nil {
return nil, fmt.Errorf("getting ip from client addr: %w", err)
return netip.Addr{}, fmt.Errorf("getting ip from client addr: %w", err)
}

return net.ParseIP(ipStr), nil
return netip.ParseAddr(ipStr)
}

// writeErrorWithIP is like [aghhttp.Error], but includes the remote IP address
Expand Down Expand Up @@ -173,20 +173,25 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
}
}

cookie, err := Context.auth.newCookie(req, remoteIP)
if err != nil {
writeErrorWithIP(r, w, http.StatusForbidden, remoteIP, "%s", err)

return
}

// Use realIP here, since this IP address is only used for logging.
ip, err := realIP(r)
if err != nil {
log.Error("auth: getting real ip from request with remote ip %s: %s", remoteIP, err)
}

log.Info("auth: user %q successfully logged in from ip %v", req.Name, ip)
cookie, err := Context.auth.newCookie(req, remoteIP)
if err != nil {
logIP := remoteIP
if isTrustedIP(ip, Context.auth.trustedProxies) {
logIP = ip.String()
}

writeErrorWithIP(r, w, http.StatusForbidden, logIP, "%s", err)

return
}

log.Info("auth: user %q successfully logged in from ip %s", req.Name, ip)

http.SetCookie(w, cookie)

Expand All @@ -198,6 +203,19 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
aghhttp.OK(w)
}

// isTrustedIP returns true if the trustedProxies include ip.
func isTrustedIP(ip netip.Addr, trustedProxies []netutil.Prefix) (ok bool) {
ip = ip.Unmap()

for _, p := range trustedProxies {
if p.Contains(ip) {
return true
}
}

return false
}

// handleLogout is the handler for the GET /control/logout HTTP API.
func handleLogout(w http.ResponseWriter, r *http.Request) {
respHdr := w.Header()
Expand Down
14 changes: 7 additions & 7 deletions internal/home/authhttp_internal_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package home

import (
"net"
"net/http"
"net/netip"
"net/textproto"
"net/url"
"path/filepath"
Expand Down Expand Up @@ -39,7 +39,7 @@ func TestAuthHTTP(t *testing.T) {
users := []webUser{
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
}
Context.auth = InitAuth(fn, users, 60, nil)
Context.auth = InitAuth(fn, users, 60, nil, nil)

handlerCalled := false
handler := func(_ http.ResponseWriter, _ *http.Request) {
Expand Down Expand Up @@ -125,21 +125,21 @@ func TestRealIP(t *testing.T) {
header http.Header
remoteAddr string
wantErrMsg string
wantIP net.IP
wantIP netip.Addr
}{{
name: "success_no_proxy",
header: nil,
remoteAddr: remoteAddr,
wantErrMsg: "",
wantIP: net.IPv4(1, 2, 3, 4),
wantIP: netip.MustParseAddr("1.2.3.4"),
}, {
name: "success_proxy",
header: http.Header{
textproto.CanonicalMIMEHeaderKey(httphdr.XRealIP): []string{"1.2.3.5"},
},
remoteAddr: remoteAddr,
wantErrMsg: "",
wantIP: net.IPv4(1, 2, 3, 5),
wantIP: netip.MustParseAddr("1.2.3.5"),
}, {
name: "success_proxy_multiple",
header: http.Header{
Expand All @@ -149,14 +149,14 @@ func TestRealIP(t *testing.T) {
},
remoteAddr: remoteAddr,
wantErrMsg: "",
wantIP: net.IPv4(1, 2, 3, 6),
wantIP: netip.MustParseAddr("1.2.3.6"),
}, {
name: "error_no_proxy",
header: nil,
remoteAddr: "1:::2",
wantErrMsg: `getting ip from client addr: address 1:::2: ` +
`too many colons in address`,
wantIP: nil,
wantIP: netip.Addr{},
}}

for _, tc := range testCases {
Expand Down
4 changes: 3 additions & 1 deletion internal/home/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,10 @@ func initUsers() (auth *Auth, err error) {
log.Info("authratelimiter is disabled")
}

trustedProxies := slices.Clone(config.DNS.TrustedProxies)

sessionTTL := config.HTTPConfig.SessionTTL.Seconds()
auth = InitAuth(sessFilename, config.Users, uint32(sessionTTL), rateLimiter)
auth = InitAuth(sessFilename, config.Users, uint32(sessionTTL), rateLimiter, trustedProxies)
if auth == nil {
return nil, errors.Error("initializing auth module failed")
}
Expand Down

0 comments on commit 2aee9a6

Please sign in to comment.