The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/authn/clients/proxy_test.go

242 lines
6.4 KiB

package clients
import (
"context"
"errors"
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/setting"
)
func TestProxy_Authenticate(t *testing.T) {
type testCase struct {
desc string
req *authn.Request
ips string
proxyHeader string
proxyHeaders map[string]string
expectedErr error
expectedUsername string
expectedAdditional map[string]string
}
tests := []testCase{
{
desc: "should authenticate using passed in proxy client",
ips: "127.0.0.1",
req: &authn.Request{
HTTPRequest: &http.Request{
Header: map[string][]string{
"X-Username": {"username"},
"X-Name": {"name"},
"X-Email": {"email"},
"X-Login": {"login"},
"X-Role": {"Viewer"},
"X-Group": {"grp1,grp2"},
},
RemoteAddr: "127.0.0.1:333",
},
},
proxyHeader: "X-Username",
proxyHeaders: map[string]string{
proxyFieldName: "X-Name",
proxyFieldEmail: "X-Email",
proxyFieldLogin: "X-Login",
proxyFieldRole: "X-Role",
proxyFieldGroups: "X-Group",
},
expectedUsername: "username",
expectedAdditional: map[string]string{
proxyFieldName: "name",
proxyFieldEmail: "email",
proxyFieldLogin: "login",
proxyFieldRole: "Viewer",
proxyFieldGroups: "grp1,grp2",
},
},
{
desc: "should fail when proxy header is empty",
req: &authn.Request{
HTTPRequest: &http.Request{Header: map[string][]string{
"X-Username": {""},
"X-Name": {"name"},
"X-Email": {"email"},
"X-Login": {"login"},
"X-Role": {"Viewer"},
"X-Group": {"grp1,grp2"},
}},
},
proxyHeader: "X-Username",
proxyHeaders: map[string]string{
proxyFieldName: "X-Name",
proxyFieldEmail: "X-Email",
proxyFieldLogin: "X-Login",
proxyFieldRole: "X-Role",
proxyFieldGroups: "X-Group",
},
expectedErr: errEmptyProxyHeader,
},
{
desc: "should fail when caller ip is not in accept list",
req: &authn.Request{
HTTPRequest: &http.Request{
Header: map[string][]string{},
RemoteAddr: "127.0.0.2:333",
},
},
ips: "127.0.0.1",
expectedErr: errNotAcceptedIP,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.AuthProxy.HeaderName = "X-Username"
cfg.AuthProxy.Headers = tt.proxyHeaders
cfg.AuthProxy.Whitelist = tt.ips
calledUsername := ""
var calledAdditional map[string]string
proxyClient := authntest.MockProxyClient{AuthenticateProxyFunc: func(ctx context.Context, r *authn.Request, username string, additional map[string]string) (*authn.Identity, error) {
calledUsername = username
calledAdditional = additional
return nil, nil
}}
c, err := ProvideProxy(cfg, &fakeCache{expectedErr: errors.New("")}, proxyClient)
require.NoError(t, err)
_, err = c.Authenticate(context.Background(), tt.req)
assert.ErrorIs(t, err, tt.expectedErr)
assert.Equal(t, tt.expectedUsername, calledUsername)
assert.EqualValues(t, tt.expectedAdditional, calledAdditional)
})
}
}
func TestProxy_Test(t *testing.T) {
type testCase struct {
desc string
req *authn.Request
expectedOK bool
}
tests := []testCase{
{
desc: "should return true when proxy header exists",
req: &authn.Request{
HTTPRequest: &http.Request{
Header: map[string][]string{"Proxy-Header": {"some value"}},
},
},
expectedOK: true,
},
{
desc: "should return false when proxy header exists but has no value",
req: &authn.Request{
HTTPRequest: &http.Request{
Header: map[string][]string{"Proxy-Header": {""}},
},
},
expectedOK: false,
},
{
desc: "should return false when no proxy header is set on request",
req: &authn.Request{
HTTPRequest: &http.Request{Header: map[string][]string{}},
},
expectedOK: false,
},
{
desc: "should return false when no http request is present",
req: &authn.Request{},
expectedOK: false,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.AuthProxy.HeaderName = "Proxy-Header"
c, _ := ProvideProxy(cfg, nil, nil, nil)
assert.Equal(t, tt.expectedOK, c.Test(context.Background(), tt.req))
})
}
}
var _ proxyCache = new(fakeCache)
type fakeCache struct {
data map[string][]byte
expectedErr error
}
func (f *fakeCache) Get(ctx context.Context, key string) ([]byte, error) {
return f.data[key], f.expectedErr
}
func (f *fakeCache) Set(ctx context.Context, key string, value []byte, expire time.Duration) error {
f.data[key] = value
return f.expectedErr
}
func (f fakeCache) Delete(ctx context.Context, key string) error {
delete(f.data, key)
return f.expectedErr
}
func TestProxy_Hook(t *testing.T) {
cfg := setting.NewCfg()
cfg.AuthProxy.HeaderName = "X-Username"
cfg.AuthProxy.Headers = map[string]string{
proxyFieldRole: "X-Role",
}
cache := &fakeCache{data: make(map[string][]byte)}
userId := int64(1)
userID := identity.NewTypedID(identity.TypeUser, userId)
// withRole creates a test case for a user with a specific role.
withRole := func(role string) func(t *testing.T) {
cacheKey := fmt.Sprintf("users:johndoe-%s", role)
return func(t *testing.T) {
c, err := ProvideProxy(cfg, cache, authntest.MockProxyClient{})
require.NoError(t, err)
userIdentity := &authn.Identity{
ID: userID,
ClientParams: authn.ClientParams{
CacheAuthProxyKey: cacheKey,
},
}
userReq := &authn.Request{
HTTPRequest: &http.Request{
Header: map[string][]string{
"X-Username": {"johndoe"},
"X-Role": {role},
},
},
}
err = c.Hook(context.Background(), userIdentity, userReq)
assert.NoError(t, err)
expectedCache := map[string][]byte{
cacheKey: []byte(fmt.Sprintf("%d", userId)),
fmt.Sprintf("%s:%s", proxyCachePrefix, "johndoe"): []byte(fmt.Sprintf("users:johndoe-%s", role)),
}
assert.Equal(t, expectedCache, cache.data)
}
}
t.Run("step 1: new user with role Admin", withRole("Admin"))
t.Run("step 2: cached user with new Role Viewer", withRole("Viewer"))
t.Run("step 3: cached user get changed back to Admin", withRole("Admin"))
}