github.com/argoproj/argo-cd@v1.8.7/server/account/account_test.go (about)

     1  package account
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/dgrijalva/jwt-go/v4"
     9  	"github.com/stretchr/testify/assert"
    10  	"google.golang.org/grpc/codes"
    11  	"google.golang.org/grpc/status"
    12  	v1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/client-go/kubernetes/fake"
    15  
    16  	"github.com/argoproj/argo-cd/common"
    17  	"github.com/argoproj/argo-cd/pkg/apiclient/account"
    18  	sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session"
    19  	"github.com/argoproj/argo-cd/server/session"
    20  	"github.com/argoproj/argo-cd/test"
    21  	"github.com/argoproj/argo-cd/util/errors"
    22  	"github.com/argoproj/argo-cd/util/password"
    23  	"github.com/argoproj/argo-cd/util/rbac"
    24  	sessionutil "github.com/argoproj/argo-cd/util/session"
    25  	"github.com/argoproj/argo-cd/util/settings"
    26  )
    27  
    28  const (
    29  	testNamespace = "default"
    30  )
    31  
    32  // return an AccountServer which returns fake data
    33  func newTestAccountServer(ctx context.Context, opts ...func(cm *v1.ConfigMap, secret *v1.Secret)) (*Server, *session.Server) {
    34  	return newTestAccountServerExt(ctx, func(claims jwt.Claims, rvals ...interface{}) bool {
    35  		return true
    36  	}, opts...)
    37  }
    38  
    39  func newTestAccountServerExt(ctx context.Context, enforceFn rbac.ClaimsEnforcerFunc, opts ...func(cm *v1.ConfigMap, secret *v1.Secret)) (*Server, *session.Server) {
    40  	bcrypt, err := password.HashPassword("oldpassword")
    41  	errors.CheckError(err)
    42  	cm := &v1.ConfigMap{
    43  		ObjectMeta: metav1.ObjectMeta{
    44  			Name:      "argocd-cm",
    45  			Namespace: testNamespace,
    46  			Labels: map[string]string{
    47  				"app.kubernetes.io/part-of": "argocd",
    48  			},
    49  		},
    50  		Data: map[string]string{},
    51  	}
    52  	secret := &v1.Secret{
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Name:      "argocd-secret",
    55  			Namespace: testNamespace,
    56  		},
    57  		Data: map[string][]byte{
    58  			"admin.password":   []byte(bcrypt),
    59  			"server.secretkey": []byte("test"),
    60  		},
    61  	}
    62  	for i := range opts {
    63  		opts[i](cm, secret)
    64  	}
    65  	kubeclientset := fake.NewSimpleClientset(cm, secret)
    66  	settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
    67  	sessionMgr := sessionutil.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", sessionutil.NewInMemoryUserStateStorage())
    68  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
    69  	enforcer.SetClaimsEnforcerFunc(enforceFn)
    70  
    71  	return NewServer(sessionMgr, settingsMgr, enforcer), session.NewServer(sessionMgr, nil, nil, nil)
    72  }
    73  
    74  func getAdminAccount(mgr *settings.SettingsManager) (*settings.Account, error) {
    75  	accounts, err := mgr.GetAccounts()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	adminAccount := accounts[common.ArgoCDAdminUsername]
    80  	return &adminAccount, nil
    81  }
    82  
    83  func adminContext(ctx context.Context) context.Context {
    84  	// nolint:staticcheck
    85  	return context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin", Issuer: sessionutil.SessionManagerClaimsIssuer})
    86  }
    87  
    88  func ssoAdminContext(ctx context.Context, iat time.Time) context.Context {
    89  	// nolint:staticcheck
    90  	return context.WithValue(ctx, "claims", &jwt.StandardClaims{
    91  		Subject:  "admin",
    92  		Issuer:   "https://myargocdhost.com/api/dex",
    93  		IssuedAt: jwt.At(iat),
    94  	})
    95  }
    96  
    97  func projTokenContext(ctx context.Context) context.Context {
    98  	// nolint:staticcheck
    99  	return context.WithValue(ctx, "claims", &jwt.StandardClaims{
   100  		Subject: "proj:demo:deployer",
   101  		Issuer:  sessionutil.SessionManagerClaimsIssuer,
   102  	})
   103  }
   104  
   105  func TestUpdatePassword(t *testing.T) {
   106  	accountServer, sessionServer := newTestAccountServer(context.Background())
   107  	ctx := adminContext(context.Background())
   108  	var err error
   109  
   110  	// ensure password is not allowed to be updated if given bad password
   111  	_, err = accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "badpassword", NewPassword: "newpassword"})
   112  	assert.Error(t, err)
   113  	assert.NoError(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "oldpassword"))
   114  	assert.Error(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "newpassword"))
   115  	// verify old password works
   116  	_, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "oldpassword"})
   117  	assert.NoError(t, err)
   118  	// verify new password doesn't
   119  	_, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "newpassword"})
   120  	assert.Error(t, err)
   121  
   122  	// ensure password can be updated with valid password and immediately be used
   123  	adminAccount, err := getAdminAccount(accountServer.settingsMgr)
   124  	assert.NoError(t, err)
   125  	prevHash := adminAccount.PasswordHash
   126  	_, err = accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword"})
   127  	assert.NoError(t, err)
   128  	adminAccount, err = getAdminAccount(accountServer.settingsMgr)
   129  	assert.NoError(t, err)
   130  	assert.NotEqual(t, prevHash, adminAccount.PasswordHash)
   131  	assert.NoError(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "newpassword"))
   132  	assert.Error(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "oldpassword"))
   133  	// verify old password is invalid
   134  	_, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "oldpassword"})
   135  	assert.Error(t, err)
   136  	// verify new password works
   137  	_, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "newpassword"})
   138  	assert.NoError(t, err)
   139  }
   140  
   141  func TestUpdatePassword_AdminUpdatesAnotherUser(t *testing.T) {
   142  	accountServer, sessionServer := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) {
   143  		cm.Data["accounts.anotherUser"] = "login"
   144  	})
   145  	ctx := adminContext(context.Background())
   146  
   147  	_, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"})
   148  	assert.NoError(t, err)
   149  
   150  	_, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "anotherUser", Password: "newpassword"})
   151  	assert.NoError(t, err)
   152  }
   153  
   154  func TestUpdatePassword_DoesNotHavePermissions(t *testing.T) {
   155  	enforcer := func(claims jwt.Claims, rvals ...interface{}) bool {
   156  		return false
   157  	}
   158  
   159  	t.Run("LocalAccountUpdatesAnotherAccount", func(t *testing.T) {
   160  		accountServer, _ := newTestAccountServerExt(context.Background(), enforcer, func(cm *v1.ConfigMap, secret *v1.Secret) {
   161  			cm.Data["accounts.anotherUser"] = "login"
   162  		})
   163  		ctx := adminContext(context.Background())
   164  		_, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"})
   165  		assert.Error(t, err)
   166  		assert.Contains(t, err.Error(), "permission denied")
   167  	})
   168  
   169  	t.Run("SSOAccountWithTheSameName", func(t *testing.T) {
   170  		accountServer, _ := newTestAccountServerExt(context.Background(), enforcer)
   171  		ctx := ssoAdminContext(context.Background(), time.Now())
   172  		_, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "admin"})
   173  		assert.Error(t, err)
   174  		assert.Contains(t, err.Error(), "permission denied")
   175  	})
   176  }
   177  
   178  func TestUpdatePassword_ProjectToken(t *testing.T) {
   179  	accountServer, _ := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) {
   180  		cm.Data["accounts.anotherUser"] = "login"
   181  	})
   182  	ctx := projTokenContext(context.Background())
   183  	_, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword"})
   184  	assert.Error(t, err)
   185  	assert.Contains(t, err.Error(), "password can only be changed for local users")
   186  }
   187  
   188  func TestUpdatePassword_OldSSOToken(t *testing.T) {
   189  	accountServer, _ := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) {
   190  		cm.Data["accounts.anotherUser"] = "login"
   191  	})
   192  	ctx := ssoAdminContext(context.Background(), time.Now().Add(-2*common.ChangePasswordSSOTokenMaxAge))
   193  
   194  	_, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"})
   195  	assert.Error(t, err)
   196  }
   197  
   198  func TestUpdatePassword_SSOUserUpdatesAnotherUser(t *testing.T) {
   199  	accountServer, sessionServer := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) {
   200  		cm.Data["accounts.anotherUser"] = "login"
   201  	})
   202  	ctx := ssoAdminContext(context.Background(), time.Now())
   203  
   204  	_, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"})
   205  	assert.NoError(t, err)
   206  
   207  	_, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "anotherUser", Password: "newpassword"})
   208  	assert.NoError(t, err)
   209  }
   210  
   211  func TestListAccounts_NoAccountsConfigured(t *testing.T) {
   212  	ctx := adminContext(context.Background())
   213  
   214  	accountServer, _ := newTestAccountServer(ctx)
   215  	resp, err := accountServer.ListAccounts(ctx, &account.ListAccountRequest{})
   216  	assert.NoError(t, err)
   217  	assert.Len(t, resp.Items, 1)
   218  }
   219  
   220  func TestListAccounts_AccountsAreConfigured(t *testing.T) {
   221  	ctx := adminContext(context.Background())
   222  	accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) {
   223  		cm.Data["accounts.account1"] = "apiKey"
   224  		cm.Data["accounts.account2"] = "login, apiKey"
   225  		cm.Data["accounts.account2.enabled"] = "false"
   226  	})
   227  
   228  	resp, err := accountServer.ListAccounts(ctx, &account.ListAccountRequest{})
   229  	assert.NoError(t, err)
   230  	assert.Len(t, resp.Items, 3)
   231  	assert.ElementsMatch(t, []*account.Account{
   232  		{Name: "admin", Capabilities: []string{"login"}, Enabled: true},
   233  		{Name: "account1", Capabilities: []string{"apiKey"}, Enabled: true},
   234  		{Name: "account2", Capabilities: []string{"login", "apiKey"}, Enabled: false},
   235  	}, resp.Items)
   236  }
   237  
   238  func TestGetAccount(t *testing.T) {
   239  	ctx := adminContext(context.Background())
   240  	accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) {
   241  		cm.Data["accounts.account1"] = "apiKey"
   242  	})
   243  
   244  	t.Run("ExistingAccount", func(t *testing.T) {
   245  		acc, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "account1"})
   246  		assert.NoError(t, err)
   247  
   248  		assert.Equal(t, acc.Name, "account1")
   249  	})
   250  
   251  	t.Run("NonExistingAccount", func(t *testing.T) {
   252  		_, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "bad-name"})
   253  		assert.Error(t, err)
   254  		assert.Equal(t, status.Code(err), codes.NotFound)
   255  	})
   256  }
   257  
   258  func TestCreateToken_SuccessfullyCreated(t *testing.T) {
   259  	ctx := adminContext(context.Background())
   260  	accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) {
   261  		cm.Data["accounts.account1"] = "apiKey"
   262  	})
   263  
   264  	_, err := accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1"})
   265  	assert.NoError(t, err)
   266  
   267  	acc, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "account1"})
   268  	assert.NoError(t, err)
   269  
   270  	assert.Len(t, acc.Tokens, 1)
   271  }
   272  
   273  func TestCreateToken_DoesNotHaveCapability(t *testing.T) {
   274  	ctx := adminContext(context.Background())
   275  	accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) {
   276  		cm.Data["accounts.account1"] = "login"
   277  	})
   278  
   279  	_, err := accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1"})
   280  	assert.Error(t, err)
   281  }
   282  
   283  func TestCreateToken_UserSpecifiedID(t *testing.T) {
   284  	ctx := adminContext(context.Background())
   285  	accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) {
   286  		cm.Data["accounts.account1"] = "apiKey"
   287  	})
   288  
   289  	_, err := accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1", Id: "test"})
   290  	assert.NoError(t, err)
   291  
   292  	_, err = accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1", Id: "test"})
   293  	if !assert.Error(t, err) {
   294  		return
   295  	}
   296  	assert.Contains(t, "account already has token with id 'test'", err.Error())
   297  }
   298  
   299  func TestDeleteToken_SuccessfullyRemoved(t *testing.T) {
   300  	ctx := adminContext(context.Background())
   301  	accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) {
   302  		cm.Data["accounts.account1"] = "apiKey"
   303  		secret.Data["accounts.account1.tokens"] = []byte(`[{"id":"123","iat":1583789194,"exp":1583789194}]`)
   304  	})
   305  
   306  	_, err := accountServer.DeleteToken(ctx, &account.DeleteTokenRequest{Name: "account1", Id: "123"})
   307  	assert.NoError(t, err)
   308  
   309  	acc, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "account1"})
   310  	assert.NoError(t, err)
   311  
   312  	assert.Len(t, acc.Tokens, 0)
   313  }