mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Backend: fix IPv6 address parsing erroneous (#28585)
* Backend: Fix parsing of client IP address Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
parent
3d33de1751
commit
10ff4eecef
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/network"
|
||||||
"github.com/grafana/grafana/pkg/login"
|
"github.com/grafana/grafana/pkg/login"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -248,7 +249,15 @@ func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext)
|
|||||||
return errors.New("could not login user")
|
return errors.New("could not login user")
|
||||||
}
|
}
|
||||||
|
|
||||||
userToken, err := hs.AuthTokenService.CreateToken(c.Req.Context(), user.Id, c.RemoteAddr(), c.Req.UserAgent())
|
addr := c.RemoteAddr()
|
||||||
|
ip, err := network.GetIPFromAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
hs.log.Debug("Failed to get IP from client address", "addr", addr)
|
||||||
|
ip = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hs.log.Debug("Got IP address from client address", "addr", addr, "ip", ip)
|
||||||
|
userToken, err := hs.AuthTokenService.CreateToken(c.Req.Context(), user.Id, ip, c.Req.UserAgent())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errutil.Wrap("failed to create auth token", err)
|
return errutil.Wrap("failed to create auth token", err)
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,16 @@ type FakeLogger struct {
|
|||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stub *FakeLogger) Info(testMessage string, ctx ...interface{}) {
|
func (fl *FakeLogger) Debug(testMessage string, ctx ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fl *FakeLogger) Info(testMessage string, ctx ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fl *FakeLogger) Warn(testMessage string, ctx ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fl *FakeLogger) Error(testMessage string, ctx ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type redirectCase struct {
|
type redirectCase struct {
|
||||||
|
44
pkg/infra/network/address.go
Normal file
44
pkg/infra/network/address.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var reIPv4AndPort = regexp.MustCompile(`^(\d+\.\d+\.\d+\.\d+):\d+$`)
|
||||||
|
|
||||||
|
// An IPv6 address/port pair can consist of the IP address enclosed in square brackets followed by a colon and
|
||||||
|
// a port, although the colon/port component is actually optional in practice (e.g., we may receive [::1], where
|
||||||
|
// we should just strip off the square brackets).
|
||||||
|
var reIPv6AndPort = regexp.MustCompile(`^\[(.+)\](:\d+)?$`)
|
||||||
|
|
||||||
|
// GetIPFromAddress tries to get an IPv4 or IPv6 address from a host address, potentially including a port.
|
||||||
|
func GetIPFromAddress(input string) (net.IP, error) {
|
||||||
|
if a := net.ParseIP(input); len(a) > 0 {
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := fmt.Errorf("not a valid IP address or IP address/port pair: %q", input)
|
||||||
|
|
||||||
|
// It could potentially be an IP address/port pair
|
||||||
|
var addr string
|
||||||
|
ms := reIPv4AndPort.FindStringSubmatch(input)
|
||||||
|
if len(ms) == 0 {
|
||||||
|
ms := reIPv6AndPort.FindStringSubmatch(input)
|
||||||
|
if len(ms) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = ms[1]
|
||||||
|
} else {
|
||||||
|
// Strip off port
|
||||||
|
addr = ms[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if a := net.ParseIP(addr); len(a) > 0 {
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
82
pkg/infra/network/address_test.go
Normal file
82
pkg/infra/network/address_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetIPFromAddress(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
exp string
|
||||||
|
expErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Valid IPv4",
|
||||||
|
input: "192.168.2.1",
|
||||||
|
exp: "192.168.2.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Valid IPv6",
|
||||||
|
input: "2001:0db8:0000:0000:0000:ff00:0042:8329",
|
||||||
|
exp: "2001:db8::ff00:42:8329",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Valid IPv6 enclosed in square brackets",
|
||||||
|
input: "[2001:0db8:0000:0000:0000:ff00:0042:8329]",
|
||||||
|
exp: "2001:db8::ff00:42:8329",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Valid IPv4/port pair",
|
||||||
|
input: "192.168.2.1:5000",
|
||||||
|
exp: "192.168.2.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Valid IPv6/port pair",
|
||||||
|
input: "[2001:0db8:0000:0000:0000:ff00:0042:8329]:5000",
|
||||||
|
exp: "2001:db8::ff00:42:8329",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Invalid IPv6/port pair",
|
||||||
|
input: "[2001:0db8:0000:0000:0000:ff00:0042:8329]:5000:2000",
|
||||||
|
expErr: `not a valid IP address or IP address/port pair: "[2001:0db8:0000:0000:0000:ff00:0042:8329]:5000:2000"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "IPv6 with too many parts",
|
||||||
|
input: "2001:0db8:0000:0000:0000:ff00:0042:8329:1234",
|
||||||
|
expErr: `not a valid IP address or IP address/port pair: "2001:0db8:0000:0000:0000:ff00:0042:8329:1234"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "IPv6 with too few parts",
|
||||||
|
input: "2001:0db8:0000:0000:0000:ff00:0042",
|
||||||
|
expErr: `not a valid IP address or IP address/port pair: "2001:0db8:0000:0000:0000:ff00:0042"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Valid shortened IPv6",
|
||||||
|
input: "2001:db8::ff00:42:8329",
|
||||||
|
exp: "2001:db8::ff00:42:8329",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "IPv6 loopback address",
|
||||||
|
input: "::1",
|
||||||
|
exp: "::1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
ip, err := GetIPFromAddress(tc.input)
|
||||||
|
if tc.expErr == "" {
|
||||||
|
exp := net.ParseIP(tc.exp)
|
||||||
|
require.NotNil(t, exp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, exp, ip)
|
||||||
|
} else {
|
||||||
|
require.EqualError(t, err, tc.expErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -132,7 +132,10 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) {
|
|||||||
proxyObjs = append(proxyObjs, result)
|
proxyObjs = append(proxyObjs, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceIP, _, _ := net.SplitHostPort(ip)
|
sourceIP, _, err := net.SplitHostPort(ip)
|
||||||
|
if err != nil {
|
||||||
|
return false, newError("could not parse address", err)
|
||||||
|
}
|
||||||
sourceObj := net.ParseIP(sourceIP)
|
sourceObj := net.ParseIP(sourceIP)
|
||||||
|
|
||||||
for _, proxyObj := range proxyObjs {
|
for _, proxyObj := range proxyObjs {
|
||||||
@ -141,11 +144,10 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := fmt.Errorf(
|
err = fmt.Errorf(
|
||||||
"request for user (%s) from %s is not from the authentication proxy", auth.header,
|
"request for user (%s) from %s is not from the authentication proxy", auth.header,
|
||||||
sourceIP,
|
sourceIP,
|
||||||
)
|
)
|
||||||
|
|
||||||
return false, newError("Proxy authentication required", err)
|
return false, newError("Proxy authentication required", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/components/apikeygen"
|
"github.com/grafana/grafana/pkg/components/apikeygen"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/network"
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
"github.com/grafana/grafana/pkg/login"
|
"github.com/grafana/grafana/pkg/login"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -255,7 +256,13 @@ func rotateEndOfRequestFunc(ctx *models.ReqContext, authTokenService models.User
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated, err := authTokenService.TryRotateToken(ctx.Req.Context(), token, ctx.RemoteAddr(), ctx.Req.UserAgent())
|
addr := ctx.RemoteAddr()
|
||||||
|
ip, err := network.GetIPFromAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Debug("Failed to get client IP address", "addr", addr, "err", err)
|
||||||
|
ip = nil
|
||||||
|
}
|
||||||
|
rotated, err := authTokenService.TryRotateToken(ctx.Req.Context(), token, ip, ctx.Req.UserAgent())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Error("Failed to rotate token", "error", err)
|
ctx.Logger.Error("Failed to rotate token", "error", err)
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -249,7 +250,8 @@ func TestMiddlewareContext(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sc.userAuthTokenService.TryRotateTokenProvider = func(ctx context.Context, userToken *models.UserToken, clientIP, userAgent string) (bool, error) {
|
sc.userAuthTokenService.TryRotateTokenProvider = func(ctx context.Context, userToken *models.UserToken,
|
||||||
|
clientIP net.IP, userAgent string) (bool, error) {
|
||||||
userToken.UnhashedToken = "rotated"
|
userToken.UnhashedToken = "rotated"
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@ -593,7 +595,8 @@ func TestDontRotateTokensOnCancelledRequests(t *testing.T) {
|
|||||||
|
|
||||||
tryRotateCallCount := 0
|
tryRotateCallCount := 0
|
||||||
uts := &auth.FakeUserAuthTokenService{
|
uts := &auth.FakeUserAuthTokenService{
|
||||||
TryRotateTokenProvider: func(ctx context.Context, token *models.UserToken, clientIP, userAgent string) (bool, error) {
|
TryRotateTokenProvider: func(ctx context.Context, token *models.UserToken, clientIP net.IP,
|
||||||
|
userAgent string) (bool, error) {
|
||||||
tryRotateCallCount++
|
tryRotateCallCount++
|
||||||
return false, nil
|
return false, nil
|
||||||
},
|
},
|
||||||
@ -613,7 +616,8 @@ func TestTokenRotationAtEndOfRequest(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
uts := &auth.FakeUserAuthTokenService{
|
uts := &auth.FakeUserAuthTokenService{
|
||||||
TryRotateTokenProvider: func(ctx context.Context, token *models.UserToken, clientIP, userAgent string) (bool, error) {
|
TryRotateTokenProvider: func(ctx context.Context, token *models.UserToken, clientIP net.IP,
|
||||||
|
userAgent string) (bool, error) {
|
||||||
newToken, err := util.RandomHex(16)
|
newToken, err := util.RandomHex(16)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
token.AuthToken = newToken
|
token.AuthToken = newToken
|
||||||
|
@ -3,6 +3,7 @@ package models
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Typed errors
|
// Typed errors
|
||||||
@ -32,9 +33,9 @@ type RevokeAuthTokenCmd struct {
|
|||||||
|
|
||||||
// UserTokenService are used for generating and validating user tokens
|
// UserTokenService are used for generating and validating user tokens
|
||||||
type UserTokenService interface {
|
type UserTokenService interface {
|
||||||
CreateToken(ctx context.Context, userId int64, clientIP, userAgent string) (*UserToken, error)
|
CreateToken(ctx context.Context, userId int64, clientIP net.IP, userAgent string) (*UserToken, error)
|
||||||
LookupToken(ctx context.Context, unhashedToken string) (*UserToken, error)
|
LookupToken(ctx context.Context, unhashedToken string) (*UserToken, error)
|
||||||
TryRotateToken(ctx context.Context, token *UserToken, clientIP, userAgent string) (bool, error)
|
TryRotateToken(ctx context.Context, token *UserToken, clientIP net.IP, userAgent string) (bool, error)
|
||||||
RevokeToken(ctx context.Context, token *UserToken) error
|
RevokeToken(ctx context.Context, token *UserToken) error
|
||||||
RevokeAllUserTokens(ctx context.Context, userId int64) error
|
RevokeAllUserTokens(ctx context.Context, userId int64) error
|
||||||
ActiveTokenCount(ctx context.Context) (int64, error)
|
ActiveTokenCount(ctx context.Context) (int64, error)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -53,12 +54,7 @@ func (s *UserAuthTokenService) ActiveTokenCount(ctx context.Context) (int64, err
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserAuthTokenService) CreateToken(ctx context.Context, userId int64, clientAddr, userAgent string) (*models.UserToken, error) {
|
func (s *UserAuthTokenService) CreateToken(ctx context.Context, userId int64, clientIP net.IP, userAgent string) (*models.UserToken, error) {
|
||||||
clientIP, err := util.ParseIPAddress(clientAddr)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Debug("Failed to parse client IP address", "clientAddr", clientAddr, "err", err)
|
|
||||||
clientIP = ""
|
|
||||||
}
|
|
||||||
token, err := util.RandomHex(16)
|
token, err := util.RandomHex(16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -67,12 +63,16 @@ func (s *UserAuthTokenService) CreateToken(ctx context.Context, userId int64, cl
|
|||||||
hashedToken := hashToken(token)
|
hashedToken := hashToken(token)
|
||||||
|
|
||||||
now := getTime().Unix()
|
now := getTime().Unix()
|
||||||
|
clientIPStr := clientIP.String()
|
||||||
|
if len(clientIP) == 0 {
|
||||||
|
clientIPStr = ""
|
||||||
|
}
|
||||||
|
|
||||||
userAuthToken := userAuthToken{
|
userAuthToken := userAuthToken{
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
AuthToken: hashedToken,
|
AuthToken: hashedToken,
|
||||||
PrevAuthToken: hashedToken,
|
PrevAuthToken: hashedToken,
|
||||||
ClientIp: clientIP,
|
ClientIp: clientIPStr,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
RotatedAt: now,
|
RotatedAt: now,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
@ -193,7 +193,7 @@ func (s *UserAuthTokenService) LookupToken(ctx context.Context, unhashedToken st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserAuthTokenService) TryRotateToken(ctx context.Context, token *models.UserToken,
|
func (s *UserAuthTokenService) TryRotateToken(ctx context.Context, token *models.UserToken,
|
||||||
clientAddr, userAgent string) (bool, error) {
|
clientIP net.IP, userAgent string) (bool, error) {
|
||||||
if token == nil {
|
if token == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -219,12 +219,10 @@ func (s *UserAuthTokenService) TryRotateToken(ctx context.Context, token *models
|
|||||||
|
|
||||||
s.log.Debug("token needs rotation", "tokenId", model.Id, "authTokenSeen", model.AuthTokenSeen, "rotatedAt", rotatedAt)
|
s.log.Debug("token needs rotation", "tokenId", model.Id, "authTokenSeen", model.AuthTokenSeen, "rotatedAt", rotatedAt)
|
||||||
|
|
||||||
clientIP, err := util.ParseIPAddress(clientAddr)
|
clientIPStr := clientIP.String()
|
||||||
if err != nil {
|
if len(clientIP) == 0 {
|
||||||
s.log.Debug("Failed to parse client IP address", "clientAddr", clientAddr, "err", err)
|
clientIPStr = ""
|
||||||
clientIP = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newToken, err := util.RandomHex(16)
|
newToken, err := util.RandomHex(16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -246,7 +244,9 @@ func (s *UserAuthTokenService) TryRotateToken(ctx context.Context, token *models
|
|||||||
|
|
||||||
var affected int64
|
var affected int64
|
||||||
err = s.SQLStore.WithTransactionalDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
|
err = s.SQLStore.WithTransactionalDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
|
||||||
res, err := dbSession.Exec(sql, userAgent, clientIP, s.SQLStore.Dialect.BooleanStr(true), hashedToken, s.SQLStore.Dialect.BooleanStr(false), now.Unix(), model.Id, s.SQLStore.Dialect.BooleanStr(true), now.Add(-30*time.Second).Unix())
|
res, err := dbSession.Exec(sql, userAgent, clientIPStr, s.SQLStore.Dialect.BooleanStr(true), hashedToken,
|
||||||
|
s.SQLStore.Dialect.BooleanStr(false), now.Unix(), model.Id, s.SQLStore.Dialect.BooleanStr(true),
|
||||||
|
now.Add(-30*time.Second).Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -27,7 +28,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Convey("When creating token", func() {
|
Convey("When creating token", func() {
|
||||||
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(userToken, ShouldNotBeNil)
|
So(userToken, ShouldNotBeNil)
|
||||||
So(userToken.AuthTokenSeen, ShouldBeFalse)
|
So(userToken.AuthTokenSeen, ShouldBeFalse)
|
||||||
@ -78,7 +80,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("When creating an additional token", func() {
|
Convey("When creating an additional token", func() {
|
||||||
userToken2, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken2, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(userToken2, ShouldNotBeNil)
|
So(userToken2, ShouldNotBeNil)
|
||||||
|
|
||||||
@ -124,7 +127,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
userId := userID + int64(i+1)
|
userId := userID + int64(i+1)
|
||||||
userIds = append(userIds, userId)
|
userIds = append(userIds, userId)
|
||||||
_, err := userAuthTokenService.CreateToken(context.Background(), userId, "192.168.10.11:1234", "some user agent")
|
_, err := userAuthTokenService.CreateToken(context.Background(), userId,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +145,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("expires correctly", func() {
|
Convey("expires correctly", func() {
|
||||||
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
userToken, err = userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
|
userToken, err = userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
|
||||||
@ -151,7 +156,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
return t.Add(time.Hour)
|
return t.Add(time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "192.168.10.11:1234", "some user agent")
|
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
@ -221,13 +227,15 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("can properly rotate tokens", func() {
|
Convey("can properly rotate tokens", func() {
|
||||||
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
prevToken := userToken.AuthToken
|
prevToken := userToken.AuthToken
|
||||||
unhashedPrev := userToken.UnhashedToken
|
unhashedPrev := userToken.UnhashedToken
|
||||||
|
|
||||||
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "192.168.10.12:1234", "a new user agent")
|
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("192.168.10.12"), "a new user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeFalse)
|
So(rotated, ShouldBeFalse)
|
||||||
|
|
||||||
@ -246,7 +254,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
return t.Add(time.Hour)
|
return t.Add(time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated, err = userAuthTokenService.TryRotateToken(context.Background(), &tok, "192.168.10.12:1234", "a new user agent")
|
rotated, err = userAuthTokenService.TryRotateToken(context.Background(), &tok,
|
||||||
|
net.ParseIP("192.168.10.12"), "a new user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
@ -291,7 +300,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
So(lookedUpModel, ShouldNotBeNil)
|
So(lookedUpModel, ShouldNotBeNil)
|
||||||
So(lookedUpModel.AuthTokenSeen, ShouldBeFalse)
|
So(lookedUpModel.AuthTokenSeen, ShouldBeFalse)
|
||||||
|
|
||||||
rotated, err = userAuthTokenService.TryRotateToken(context.Background(), userToken, "192.168.10.12:1234", "a new user agent")
|
rotated, err = userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("192.168.10.12"), "a new user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
@ -302,7 +312,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("keeps prev token valid for 1 minute after it is confirmed", func() {
|
Convey("keeps prev token valid for 1 minute after it is confirmed", func() {
|
||||||
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(userToken, ShouldNotBeNil)
|
So(userToken, ShouldNotBeNil)
|
||||||
|
|
||||||
@ -315,7 +326,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prevToken := userToken.UnhashedToken
|
prevToken := userToken.UnhashedToken
|
||||||
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
|
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("1.1.1.1"), "firefox")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
@ -333,7 +345,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("will not mark token unseen when prev and current are the same", func() {
|
Convey("will not mark token unseen when prev and current are the same", func() {
|
||||||
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(userToken, ShouldNotBeNil)
|
So(userToken, ShouldNotBeNil)
|
||||||
|
|
||||||
@ -352,7 +365,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("Rotate token", func() {
|
Convey("Rotate token", func() {
|
||||||
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
|
userToken, err := userAuthTokenService.CreateToken(context.Background(), userID,
|
||||||
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(userToken, ShouldNotBeNil)
|
So(userToken, ShouldNotBeNil)
|
||||||
|
|
||||||
@ -367,7 +381,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
return t.Add(10 * time.Minute)
|
return t.Add(10 * time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
|
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("1.1.1.1"), "firefox")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
@ -388,7 +403,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
return t.Add(20 * time.Minute)
|
return t.Add(20 * time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated, err = userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
|
rotated, err = userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("1.1.1.1"), "firefox")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
@ -407,7 +423,8 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
return t.Add(2 * time.Minute)
|
return t.Add(2 * time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
|
rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken,
|
||||||
|
net.ParseIP("1.1.1.1"), "firefox")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(rotated, ShouldBeTrue)
|
So(rotated, ShouldBeTrue)
|
||||||
|
|
||||||
|
@ -2,13 +2,14 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FakeUserAuthTokenService struct {
|
type FakeUserAuthTokenService struct {
|
||||||
CreateTokenProvider func(ctx context.Context, userId int64, clientIP, userAgent string) (*models.UserToken, error)
|
CreateTokenProvider func(ctx context.Context, userId int64, clientIP net.IP, userAgent string) (*models.UserToken, error)
|
||||||
TryRotateTokenProvider func(ctx context.Context, token *models.UserToken, clientIP, userAgent string) (bool, error)
|
TryRotateTokenProvider func(ctx context.Context, token *models.UserToken, clientIP net.IP, userAgent string) (bool, error)
|
||||||
LookupTokenProvider func(ctx context.Context, unhashedToken string) (*models.UserToken, error)
|
LookupTokenProvider func(ctx context.Context, unhashedToken string) (*models.UserToken, error)
|
||||||
RevokeTokenProvider func(ctx context.Context, token *models.UserToken) error
|
RevokeTokenProvider func(ctx context.Context, token *models.UserToken) error
|
||||||
RevokeAllUserTokensProvider func(ctx context.Context, userId int64) error
|
RevokeAllUserTokensProvider func(ctx context.Context, userId int64) error
|
||||||
@ -20,13 +21,13 @@ type FakeUserAuthTokenService struct {
|
|||||||
|
|
||||||
func NewFakeUserAuthTokenService() *FakeUserAuthTokenService {
|
func NewFakeUserAuthTokenService() *FakeUserAuthTokenService {
|
||||||
return &FakeUserAuthTokenService{
|
return &FakeUserAuthTokenService{
|
||||||
CreateTokenProvider: func(ctx context.Context, userId int64, clientIP, userAgent string) (*models.UserToken, error) {
|
CreateTokenProvider: func(ctx context.Context, userId int64, clientIP net.IP, userAgent string) (*models.UserToken, error) {
|
||||||
return &models.UserToken{
|
return &models.UserToken{
|
||||||
UserId: 0,
|
UserId: 0,
|
||||||
UnhashedToken: "",
|
UnhashedToken: "",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
TryRotateTokenProvider: func(ctx context.Context, token *models.UserToken, clientIP, userAgent string) (bool, error) {
|
TryRotateTokenProvider: func(ctx context.Context, token *models.UserToken, clientIP net.IP, userAgent string) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
},
|
},
|
||||||
LookupTokenProvider: func(ctx context.Context, unhashedToken string) (*models.UserToken, error) {
|
LookupTokenProvider: func(ctx context.Context, unhashedToken string) (*models.UserToken, error) {
|
||||||
@ -56,7 +57,8 @@ func NewFakeUserAuthTokenService() *FakeUserAuthTokenService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FakeUserAuthTokenService) CreateToken(ctx context.Context, userId int64, clientIP, userAgent string) (*models.UserToken, error) {
|
func (s *FakeUserAuthTokenService) CreateToken(ctx context.Context, userId int64, clientIP net.IP,
|
||||||
|
userAgent string) (*models.UserToken, error) {
|
||||||
return s.CreateTokenProvider(context.Background(), userId, clientIP, userAgent)
|
return s.CreateTokenProvider(context.Background(), userId, clientIP, userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +66,8 @@ func (s *FakeUserAuthTokenService) LookupToken(ctx context.Context, unhashedToke
|
|||||||
return s.LookupTokenProvider(context.Background(), unhashedToken)
|
return s.LookupTokenProvider(context.Background(), unhashedToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FakeUserAuthTokenService) TryRotateToken(ctx context.Context, token *models.UserToken, clientIP, userAgent string) (bool, error) {
|
func (s *FakeUserAuthTokenService) TryRotateToken(ctx context.Context, token *models.UserToken, clientIP net.IP,
|
||||||
|
userAgent string) (bool, error) {
|
||||||
return s.TryRotateTokenProvider(context.Background(), token, clientIP, userAgent)
|
return s.TryRotateTokenProvider(context.Background(), token, clientIP, userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,6 @@ func (ns *NotificationService) setFiles(
|
|||||||
|
|
||||||
func (ns *NotificationService) createDialer() (*gomail.Dialer, error) {
|
func (ns *NotificationService) createDialer() (*gomail.Dialer, error) {
|
||||||
host, port, err := net.SplitHostPort(ns.Cfg.Smtp.Host)
|
host, port, err := net.SplitHostPort(ns.Cfg.Smtp.Host)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,9 @@ func (ss *SQLStore) buildConnectionString() (string, error) {
|
|||||||
if ss.dbCfg.User == "" {
|
if ss.dbCfg.User == "" {
|
||||||
ss.dbCfg.User = "''"
|
ss.dbCfg.User = "''"
|
||||||
}
|
}
|
||||||
cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", ss.dbCfg.User, ss.dbCfg.Pwd, addr.Host, addr.Port, ss.dbCfg.Name, ss.dbCfg.SslMode, ss.dbCfg.ClientCertPath, ss.dbCfg.ClientKeyPath, ss.dbCfg.CaCertPath)
|
cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s",
|
||||||
|
ss.dbCfg.User, ss.dbCfg.Pwd, addr.Host, addr.Port, ss.dbCfg.Name, ss.dbCfg.SslMode, ss.dbCfg.ClientCertPath,
|
||||||
|
ss.dbCfg.ClientKeyPath, ss.dbCfg.CaCertPath)
|
||||||
|
|
||||||
cnnstr += ss.buildExtraConnectionString(' ')
|
cnnstr += ss.buildExtraConnectionString(' ')
|
||||||
case migrator.SQLite:
|
case migrator.SQLite:
|
||||||
|
@ -8,30 +8,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/util/errutil"
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseIPAddress parses an IP address and removes port and/or IPV6 format
|
|
||||||
func ParseIPAddress(input string) (string, error) {
|
|
||||||
addr, err := SplitHostPort(input)
|
|
||||||
if err != nil {
|
|
||||||
return "", errutil.Wrapf(err, "failed to split network address %q by host and port",
|
|
||||||
input)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(addr.Host)
|
|
||||||
if ip == nil {
|
|
||||||
return addr.Host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip.IsLoopback() {
|
|
||||||
if strings.Contains(addr.Host, ":") {
|
|
||||||
// IPv6
|
|
||||||
return "::1", nil
|
|
||||||
}
|
|
||||||
return "127.0.0.1", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkAddress struct {
|
type NetworkAddress struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
@ -79,11 +55,3 @@ func SplitHostPortDefault(input, defaultHost, defaultPort string) (NetworkAddres
|
|||||||
|
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitHostPort splits ip address/hostname string by host and port
|
|
||||||
func SplitHostPort(input string) (NetworkAddress, error) {
|
|
||||||
if len(input) == 0 {
|
|
||||||
return NetworkAddress{}, fmt.Errorf("input is empty")
|
|
||||||
}
|
|
||||||
return SplitHostPortDefault(input, "", "")
|
|
||||||
}
|
|
||||||
|
@ -4,52 +4,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseIPAddress_Valid(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{input: "127.0.0.1", expected: "127.0.0.1"},
|
|
||||||
{input: "192.168.0.140:456", expected: "192.168.0.140"},
|
|
||||||
{input: "192.168.0.140", expected: "192.168.0.140"},
|
|
||||||
{input: "[::1]:456", expected: "::1"},
|
|
||||||
{input: "[::1]", expected: "::1"},
|
|
||||||
}
|
|
||||||
for _, testcase := range tests {
|
|
||||||
addr, err := ParseIPAddress(testcase.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testcase.expected, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseIPAddress_Invalid(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "[::1",
|
|
||||||
err: "failed to split network address \"[::1\" by host and port: malformed IPv6 address: '[::1'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "::1]",
|
|
||||||
err: "failed to split network address \"::1]\" by host and port: net.SplitHostPort failed for '::1]': address ::1]: too many colons in address",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "",
|
|
||||||
err: "failed to split network address \"\" by host and port: input is empty",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testcase := range tests {
|
|
||||||
addr, err := ParseIPAddress(testcase.input)
|
|
||||||
assert.EqualError(t, err, testcase.err)
|
|
||||||
assert.Empty(t, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplitHostPortDefault_Valid(t *testing.T) {
|
func TestSplitHostPortDefault_Valid(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
@ -76,25 +32,3 @@ func TestSplitHostPortDefault_Valid(t *testing.T) {
|
|||||||
assert.Equal(t, testcase.port, addr.Port)
|
assert.Equal(t, testcase.port, addr.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSplitHostPort_Valid(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
host string
|
|
||||||
port string
|
|
||||||
}{
|
|
||||||
{input: "192.168.0.140:456", host: "192.168.0.140", port: "456"},
|
|
||||||
{input: "192.168.0.140", host: "192.168.0.140", port: ""},
|
|
||||||
{input: "[::1]:456", host: "::1", port: "456"},
|
|
||||||
{input: "[::1]", host: "::1", port: ""},
|
|
||||||
{input: ":456", host: "", port: "456"},
|
|
||||||
{input: "xyz.rds.amazonaws.com", host: "xyz.rds.amazonaws.com", port: ""},
|
|
||||||
{input: "xyz.rds.amazonaws.com:123", host: "xyz.rds.amazonaws.com", port: "123"},
|
|
||||||
}
|
|
||||||
for _, testcase := range tests {
|
|
||||||
addr, err := SplitHostPort(testcase.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testcase.host, addr.Host)
|
|
||||||
assert.Equal(t, testcase.port, addr.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user