mirror of
https://github.com/argoproj/argo-cd
synced 2026-05-24 09:50:08 +00:00
341 lines
9.7 KiB
Go
341 lines
9.7 KiB
Go
package session
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
|
"github.com/dgrijalva/jwt-go"
|
|
"github.com/stretchr/testify/assert"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
|
|
"github.com/argoproj/argo-cd/common"
|
|
"github.com/argoproj/argo-cd/util/password"
|
|
"github.com/argoproj/argo-cd/util/settings"
|
|
)
|
|
|
|
func getKubeClient(pass string, enabled bool) *fake.Clientset {
|
|
const defaultSecretKey = "Hello, world!"
|
|
|
|
bcrypt, err := password.HashPassword(pass)
|
|
errors.CheckError(err)
|
|
|
|
return fake.NewSimpleClientset(&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-cm",
|
|
Namespace: "argocd",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"admin.enabled": strconv.FormatBool(enabled),
|
|
},
|
|
}, &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-secret",
|
|
Namespace: "argocd",
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": []byte(bcrypt),
|
|
"server.secretkey": []byte(defaultSecretKey),
|
|
},
|
|
})
|
|
}
|
|
|
|
func newSessionManager(settingsMgr *settings.SettingsManager, storage UserStateStorage) *SessionManager {
|
|
mgr := NewSessionManager(settingsMgr, "", storage)
|
|
mgr.verificationDelayNoiseEnabled = false
|
|
return mgr
|
|
}
|
|
|
|
func TestSessionManager(t *testing.T) {
|
|
const (
|
|
defaultSubject = "admin"
|
|
)
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("pass", true), "argocd")
|
|
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
|
|
|
|
token, err := mgr.Create(defaultSubject, 0, "")
|
|
if err != nil {
|
|
t.Errorf("Could not create token: %v", err)
|
|
}
|
|
|
|
claims, err := mgr.Parse(token)
|
|
if err != nil {
|
|
t.Errorf("Could not parse token: %v", err)
|
|
}
|
|
|
|
mapClaims := *(claims.(*jwt.MapClaims))
|
|
subject := mapClaims["sub"].(string)
|
|
if subject != "admin" {
|
|
t.Errorf("Token claim subject \"%s\" does not match expected subject \"%s\".", subject, defaultSubject)
|
|
}
|
|
}
|
|
|
|
var loggedOutContext = context.Background()
|
|
|
|
// nolint:staticcheck
|
|
var loggedInContext = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"iss": "qux", "sub": "foo", "email": "bar", "groups": []string{"baz"}})
|
|
|
|
func TestIss(t *testing.T) {
|
|
assert.Empty(t, Iss(loggedOutContext))
|
|
assert.Equal(t, "qux", Iss(loggedInContext))
|
|
}
|
|
func TestLoggedIn(t *testing.T) {
|
|
assert.False(t, LoggedIn(loggedOutContext))
|
|
assert.True(t, LoggedIn(loggedInContext))
|
|
}
|
|
|
|
func TestUsername(t *testing.T) {
|
|
assert.Empty(t, Username(loggedOutContext))
|
|
assert.Equal(t, "bar", Username(loggedInContext))
|
|
}
|
|
|
|
func TestSub(t *testing.T) {
|
|
assert.Empty(t, Sub(loggedOutContext))
|
|
assert.Equal(t, "foo", Sub(loggedInContext))
|
|
}
|
|
|
|
func TestGroups(t *testing.T) {
|
|
assert.Empty(t, Groups(loggedOutContext, []string{"groups"}))
|
|
assert.Equal(t, []string{"baz"}, Groups(loggedInContext, []string{"groups"}))
|
|
}
|
|
|
|
func TestVerifyUsernamePassword(t *testing.T) {
|
|
const password = "password"
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
disabled bool
|
|
userName string
|
|
password string
|
|
expected error
|
|
}{
|
|
{
|
|
name: "Success if userName and password is correct",
|
|
disabled: false,
|
|
userName: common.ArgoCDAdminUsername,
|
|
password: password,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "Return error if password is empty",
|
|
disabled: false,
|
|
userName: common.ArgoCDAdminUsername,
|
|
password: "",
|
|
expected: status.Errorf(codes.Unauthenticated, blankPasswordError),
|
|
},
|
|
{
|
|
name: "Return error if password is not correct",
|
|
disabled: false,
|
|
userName: common.ArgoCDAdminUsername,
|
|
password: "foo",
|
|
expected: status.Errorf(codes.Unauthenticated, invalidLoginError),
|
|
},
|
|
{
|
|
name: "Return error if disableAdmin is true",
|
|
disabled: true,
|
|
userName: common.ArgoCDAdminUsername,
|
|
password: password,
|
|
expected: status.Errorf(codes.Unauthenticated, accountDisabled, "admin"),
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient(password, !tc.disabled), "argocd")
|
|
|
|
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
|
|
|
|
err := mgr.VerifyUsernamePassword(tc.userName, tc.password)
|
|
|
|
if tc.expected == nil {
|
|
assert.Nil(t, err)
|
|
} else {
|
|
assert.EqualError(t, err, tc.expected.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCacheValueGetters(t *testing.T) {
|
|
t.Run("Default values", func(t *testing.T) {
|
|
mlf := getMaxLoginFailures()
|
|
assert.Equal(t, defaultMaxLoginFailures, mlf)
|
|
|
|
mcs := getMaximumCacheSize()
|
|
assert.Equal(t, defaultMaxCacheSize, mcs)
|
|
})
|
|
|
|
t.Run("Valid environment overrides", func(t *testing.T) {
|
|
os.Setenv(envLoginMaxFailCount, "5")
|
|
os.Setenv(envLoginMaxCacheSize, "5")
|
|
|
|
mlf := getMaxLoginFailures()
|
|
assert.Equal(t, 5, mlf)
|
|
|
|
mcs := getMaximumCacheSize()
|
|
assert.Equal(t, 5, mcs)
|
|
|
|
os.Setenv(envLoginMaxFailCount, "")
|
|
os.Setenv(envLoginMaxCacheSize, "")
|
|
})
|
|
|
|
t.Run("Invalid environment overrides", func(t *testing.T) {
|
|
os.Setenv(envLoginMaxFailCount, "invalid")
|
|
os.Setenv(envLoginMaxCacheSize, "invalid")
|
|
|
|
mlf := getMaxLoginFailures()
|
|
assert.Equal(t, defaultMaxLoginFailures, mlf)
|
|
|
|
mcs := getMaximumCacheSize()
|
|
assert.Equal(t, defaultMaxCacheSize, mcs)
|
|
|
|
os.Setenv(envLoginMaxFailCount, "")
|
|
os.Setenv(envLoginMaxCacheSize, "")
|
|
})
|
|
|
|
t.Run("Less than allowed in environment overrides", func(t *testing.T) {
|
|
os.Setenv(envLoginMaxFailCount, "-1")
|
|
os.Setenv(envLoginMaxCacheSize, "-1")
|
|
|
|
mlf := getMaxLoginFailures()
|
|
assert.Equal(t, defaultMaxLoginFailures, mlf)
|
|
|
|
mcs := getMaximumCacheSize()
|
|
assert.Equal(t, defaultMaxCacheSize, mcs)
|
|
|
|
os.Setenv(envLoginMaxFailCount, "")
|
|
os.Setenv(envLoginMaxCacheSize, "")
|
|
})
|
|
|
|
t.Run("Greater than allowed in environment overrides", func(t *testing.T) {
|
|
os.Setenv(envLoginMaxFailCount, fmt.Sprintf("%d", math.MaxInt32+1))
|
|
os.Setenv(envLoginMaxCacheSize, fmt.Sprintf("%d", math.MaxInt32+1))
|
|
|
|
mlf := getMaxLoginFailures()
|
|
assert.Equal(t, defaultMaxLoginFailures, mlf)
|
|
|
|
mcs := getMaximumCacheSize()
|
|
assert.Equal(t, defaultMaxCacheSize, mcs)
|
|
|
|
os.Setenv(envLoginMaxFailCount, "")
|
|
os.Setenv(envLoginMaxCacheSize, "")
|
|
})
|
|
|
|
}
|
|
|
|
func TestRandomPasswordVerificationDelay(t *testing.T) {
|
|
var sleptFor time.Duration
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
|
|
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
|
|
mgr.verificationDelayNoiseEnabled = true
|
|
mgr.sleep = func(d time.Duration) {
|
|
sleptFor = d
|
|
}
|
|
for i := 0; i < 10; i++ {
|
|
sleptFor = 0
|
|
start := time.Now()
|
|
if !assert.NoError(t, mgr.VerifyUsernamePassword("admin", "password")) {
|
|
return
|
|
}
|
|
totalDuration := time.Since(start) + sleptFor
|
|
assert.GreaterOrEqual(t, totalDuration.Nanoseconds(), verificationDelayNoiseMin.Nanoseconds())
|
|
assert.LessOrEqual(t, totalDuration.Nanoseconds(), verificationDelayNoiseMax.Nanoseconds())
|
|
}
|
|
}
|
|
|
|
func TestLoginRateLimiter(t *testing.T) {
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
|
|
storage := NewInMemoryUserStateStorage()
|
|
|
|
mgr := newSessionManager(settingsMgr, storage)
|
|
|
|
t.Run("Test login delay valid user", func(t *testing.T) {
|
|
for i := 0; i < getMaxLoginFailures(); i++ {
|
|
err := mgr.VerifyUsernamePassword("admin", "wrong")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
// The 11th time should fail even if password is right
|
|
{
|
|
err := mgr.VerifyUsernamePassword("admin", "password")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
storage.attempts = map[string]LoginAttempts{}
|
|
// Failed counter should have been reset, should validate immediately
|
|
{
|
|
err := mgr.VerifyUsernamePassword("admin", "password")
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
|
|
t.Run("Test login delay invalid user", func(t *testing.T) {
|
|
for i := 0; i < getMaxLoginFailures(); i++ {
|
|
err := mgr.VerifyUsernamePassword("invalid", "wrong")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
err := mgr.VerifyUsernamePassword("invalid", "wrong")
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestMaxUsernameLength(t *testing.T) {
|
|
username := ""
|
|
for i := 0; i < maxUsernameLength+1; i++ {
|
|
username += "a"
|
|
}
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
|
|
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
|
|
err := mgr.VerifyUsernamePassword(username, "password")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), fmt.Sprintf(usernameTooLongError, maxUsernameLength))
|
|
}
|
|
|
|
func TestMaxCacheSize(t *testing.T) {
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
|
|
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
|
|
|
|
invalidUsers := []string{"invalid1", "invalid2", "invalid3", "invalid4", "invalid5", "invalid6", "invalid7"}
|
|
// Temporarily decrease max cache size
|
|
os.Setenv(envLoginMaxCacheSize, "5")
|
|
|
|
for _, user := range invalidUsers {
|
|
err := mgr.VerifyUsernamePassword(user, "password")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
assert.Len(t, mgr.GetLoginFailures(), 5)
|
|
}
|
|
|
|
func TestFailedAttemptsExpiry(t *testing.T) {
|
|
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
|
|
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
|
|
|
|
invalidUsers := []string{"invalid1", "invalid2", "invalid3", "invalid4", "invalid5", "invalid6", "invalid7"}
|
|
|
|
os.Setenv(envLoginFailureWindowSeconds, "1")
|
|
|
|
for _, user := range invalidUsers {
|
|
err := mgr.VerifyUsernamePassword(user, "password")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
err := mgr.VerifyUsernamePassword("invalid8", "password")
|
|
assert.Error(t, err)
|
|
assert.Len(t, mgr.GetLoginFailures(), 1)
|
|
|
|
os.Setenv(envLoginFailureWindowSeconds, "")
|
|
}
|