github.com/argoproj/argo-cd/v3@v3.2.1/util/git/creds_test.go (about)

     1  package git
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"regexp"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/uuid"
    14  	gocache "github.com/patrickmn/go-cache"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	"golang.org/x/oauth2"
    18  	"golang.org/x/oauth2/google"
    19  
    20  	argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
    21  
    22  	"github.com/argoproj/argo-cd/v3/util/cert"
    23  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    24  	"github.com/argoproj/argo-cd/v3/util/workloadidentity"
    25  	"github.com/argoproj/argo-cd/v3/util/workloadidentity/mocks"
    26  )
    27  
    28  type cred struct {
    29  	username string
    30  	password string
    31  }
    32  
    33  type memoryCredsStore struct {
    34  	creds map[string]cred
    35  }
    36  
    37  func (s *memoryCredsStore) Add(username string, password string) string {
    38  	id := uuid.New().String()
    39  	s.creds[id] = cred{
    40  		username: username,
    41  		password: password,
    42  	}
    43  	return id
    44  }
    45  
    46  func (s *memoryCredsStore) Remove(id string) {
    47  	delete(s.creds, id)
    48  }
    49  
    50  func (s *memoryCredsStore) Environ(_ string) []string {
    51  	return nil
    52  }
    53  
    54  func TestHTTPSCreds_Environ_no_cert_cleanup(t *testing.T) {
    55  	store := &memoryCredsStore{creds: make(map[string]cred)}
    56  	creds := NewHTTPSCreds("", "", "", "", "", true, store, false)
    57  	closer, _, err := creds.Environ()
    58  	require.NoError(t, err)
    59  	credsLenBefore := len(store.creds)
    60  	utilio.Close(closer)
    61  	assert.Len(t, store.creds, credsLenBefore-1)
    62  }
    63  
    64  func TestHTTPSCreds_Environ_insecure_true(t *testing.T) {
    65  	creds := NewHTTPSCreds("", "", "", "", "", true, &NoopCredsStore{}, false)
    66  	closer, env, err := creds.Environ()
    67  	t.Cleanup(func() {
    68  		utilio.Close(closer)
    69  	})
    70  	require.NoError(t, err)
    71  	found := false
    72  	for _, envVar := range env {
    73  		if envVar == "GIT_SSL_NO_VERIFY=true" {
    74  			found = true
    75  			break
    76  		}
    77  	}
    78  	assert.True(t, found)
    79  }
    80  
    81  func TestHTTPSCreds_Environ_insecure_false(t *testing.T) {
    82  	creds := NewHTTPSCreds("", "", "", "", "", false, &NoopCredsStore{}, false)
    83  	closer, env, err := creds.Environ()
    84  	t.Cleanup(func() {
    85  		utilio.Close(closer)
    86  	})
    87  	require.NoError(t, err)
    88  	found := false
    89  	for _, envVar := range env {
    90  		if envVar == "GIT_SSL_NO_VERIFY=true" {
    91  			found = true
    92  			break
    93  		}
    94  	}
    95  	assert.False(t, found)
    96  }
    97  
    98  func TestHTTPSCreds_Environ_forceBasicAuth(t *testing.T) {
    99  	t.Run("Enabled and credentials set", func(t *testing.T) {
   100  		store := &memoryCredsStore{creds: make(map[string]cred)}
   101  		creds := NewHTTPSCreds("username", "password", "", "", "", false, store, true)
   102  		closer, env, err := creds.Environ()
   103  		require.NoError(t, err)
   104  		defer closer.Close()
   105  		var header string
   106  		for _, envVar := range env {
   107  			if strings.HasPrefix(envVar, forceBasicAuthHeaderEnv+"=") {
   108  				header = envVar[len(forceBasicAuthHeaderEnv)+1:]
   109  			}
   110  			if header != "" {
   111  				break
   112  			}
   113  		}
   114  		b64enc := base64.StdEncoding.EncodeToString([]byte("username:password"))
   115  		assert.Equal(t, "Authorization: Basic "+b64enc, header)
   116  	})
   117  	t.Run("Enabled but credentials not set", func(t *testing.T) {
   118  		store := &memoryCredsStore{creds: make(map[string]cred)}
   119  		creds := NewHTTPSCreds("", "", "", "", "", false, store, true)
   120  		closer, env, err := creds.Environ()
   121  		require.NoError(t, err)
   122  		defer closer.Close()
   123  		var header string
   124  		for _, envVar := range env {
   125  			if strings.HasPrefix(envVar, forceBasicAuthHeaderEnv+"=") {
   126  				header = envVar[len(forceBasicAuthHeaderEnv)+1:]
   127  			}
   128  			if header != "" {
   129  				break
   130  			}
   131  		}
   132  		assert.Empty(t, header)
   133  	})
   134  	t.Run("Disabled with credentials set", func(t *testing.T) {
   135  		store := &memoryCredsStore{creds: make(map[string]cred)}
   136  		creds := NewHTTPSCreds("username", "password", "", "", "", false, store, false)
   137  		closer, env, err := creds.Environ()
   138  		require.NoError(t, err)
   139  		defer closer.Close()
   140  		var header string
   141  		for _, envVar := range env {
   142  			if strings.HasPrefix(envVar, forceBasicAuthHeaderEnv+"=") {
   143  				header = envVar[len(forceBasicAuthHeaderEnv)+1:]
   144  			}
   145  			if header != "" {
   146  				break
   147  			}
   148  		}
   149  		assert.Empty(t, header)
   150  	})
   151  
   152  	t.Run("Disabled with credentials not set", func(t *testing.T) {
   153  		store := &memoryCredsStore{creds: make(map[string]cred)}
   154  		creds := NewHTTPSCreds("", "", "", "", "", false, store, false)
   155  		closer, env, err := creds.Environ()
   156  		require.NoError(t, err)
   157  		defer closer.Close()
   158  		var header string
   159  		for _, envVar := range env {
   160  			if strings.HasPrefix(envVar, forceBasicAuthHeaderEnv+"=") {
   161  				header = envVar[len(forceBasicAuthHeaderEnv)+1:]
   162  			}
   163  			if header != "" {
   164  				break
   165  			}
   166  		}
   167  		assert.Empty(t, header)
   168  	})
   169  }
   170  
   171  func TestHTTPSCreds_Environ_bearerTokenAuth(t *testing.T) {
   172  	t.Run("Enabled and credentials set", func(t *testing.T) {
   173  		store := &memoryCredsStore{creds: make(map[string]cred)}
   174  		creds := NewHTTPSCreds("", "", "token", "", "", false, store, false)
   175  		closer, env, err := creds.Environ()
   176  		require.NoError(t, err)
   177  		defer closer.Close()
   178  		var header string
   179  		for _, envVar := range env {
   180  			if strings.HasPrefix(envVar, bearerAuthHeaderEnv+"=") {
   181  				header = envVar[len(bearerAuthHeaderEnv)+1:]
   182  			}
   183  			if header != "" {
   184  				break
   185  			}
   186  		}
   187  		assert.Equal(t, "Authorization: Bearer token", header)
   188  	})
   189  }
   190  
   191  func TestHTTPSCreds_Environ_clientCert(t *testing.T) {
   192  	store := &memoryCredsStore{creds: make(map[string]cred)}
   193  	creds := NewHTTPSCreds("", "", "", "clientCertData", "clientCertKey", false, store, false)
   194  	closer, env, err := creds.Environ()
   195  	require.NoError(t, err)
   196  	var cert, key string
   197  	for _, envVar := range env {
   198  		if strings.HasPrefix(envVar, "GIT_SSL_CERT=") {
   199  			cert = envVar[13:]
   200  		} else if strings.HasPrefix(envVar, "GIT_SSL_KEY=") {
   201  			key = envVar[12:]
   202  		}
   203  		if cert != "" && key != "" {
   204  			break
   205  		}
   206  	}
   207  	assert.NotEmpty(t, cert)
   208  	assert.NotEmpty(t, key)
   209  
   210  	certBytes, err := os.ReadFile(cert)
   211  	require.NoError(t, err)
   212  	assert.Equal(t, "clientCertData", string(certBytes))
   213  	keyBytes, err := os.ReadFile(key)
   214  	assert.Equal(t, "clientCertKey", string(keyBytes))
   215  	require.NoError(t, err)
   216  
   217  	utilio.Close(closer)
   218  
   219  	_, err = os.Stat(cert)
   220  	require.ErrorIs(t, err, os.ErrNotExist)
   221  	_, err = os.Stat(key)
   222  	require.ErrorIs(t, err, os.ErrNotExist)
   223  }
   224  
   225  func Test_SSHCreds_Environ(t *testing.T) {
   226  	for _, insecureIgnoreHostKey := range []bool{false, true} {
   227  		tempDir := t.TempDir()
   228  		caFile := path.Join(tempDir, "caFile")
   229  		err := os.WriteFile(caFile, []byte(""), os.FileMode(0o600))
   230  		require.NoError(t, err)
   231  		creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, "")
   232  		closer, env, err := creds.Environ()
   233  		require.NoError(t, err)
   234  		require.Len(t, env, 2)
   235  
   236  		assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")
   237  
   238  		assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
   239  
   240  		if insecureIgnoreHostKey {
   241  			assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
   242  			assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
   243  		} else {
   244  			assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
   245  			hostsPath := cert.GetSSHKnownHostsDataPath()
   246  			assert.Contains(t, env[1], "-o UserKnownHostsFile="+hostsPath)
   247  		}
   248  
   249  		envRegex := regexp.MustCompile("-i ([^ ]+)")
   250  		assert.Regexp(t, envRegex, env[1])
   251  		privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
   252  		assert.FileExists(t, privateKeyFile)
   253  		utilio.Close(closer)
   254  		assert.NoFileExists(t, privateKeyFile)
   255  	}
   256  }
   257  
   258  func Test_SSHCreds_Environ_WithProxy(t *testing.T) {
   259  	for _, insecureIgnoreHostKey := range []bool{false, true} {
   260  		tempDir := t.TempDir()
   261  		caFile := path.Join(tempDir, "caFile")
   262  		err := os.WriteFile(caFile, []byte(""), os.FileMode(0o600))
   263  		require.NoError(t, err)
   264  		creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, "socks5://127.0.0.1:1080")
   265  		closer, env, err := creds.Environ()
   266  		require.NoError(t, err)
   267  		require.Len(t, env, 2)
   268  
   269  		assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")
   270  
   271  		assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
   272  
   273  		if insecureIgnoreHostKey {
   274  			assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
   275  			assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
   276  		} else {
   277  			assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
   278  			hostsPath := cert.GetSSHKnownHostsDataPath()
   279  			assert.Contains(t, env[1], "-o UserKnownHostsFile="+hostsPath)
   280  		}
   281  		assert.Contains(t, env[1], "-o ProxyCommand='connect-proxy -S 127.0.0.1:1080 -5 %h %p'")
   282  
   283  		envRegex := regexp.MustCompile("-i ([^ ]+)")
   284  		assert.Regexp(t, envRegex, env[1])
   285  		privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
   286  		assert.FileExists(t, privateKeyFile)
   287  		utilio.Close(closer)
   288  		assert.NoFileExists(t, privateKeyFile)
   289  	}
   290  }
   291  
   292  func Test_SSHCreds_Environ_WithProxyUserNamePassword(t *testing.T) {
   293  	for _, insecureIgnoreHostKey := range []bool{false, true} {
   294  		tempDir := t.TempDir()
   295  		caFile := path.Join(tempDir, "caFile")
   296  		err := os.WriteFile(caFile, []byte(""), os.FileMode(0o600))
   297  		require.NoError(t, err)
   298  		creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, "socks5://user:password@127.0.0.1:1080")
   299  		closer, env, err := creds.Environ()
   300  		require.NoError(t, err)
   301  		require.Len(t, env, 4)
   302  
   303  		assert.Equal(t, fmt.Sprintf("GIT_SSL_CAINFO=%s/caFile", tempDir), env[0], "CAINFO env var must be set")
   304  
   305  		assert.True(t, strings.HasPrefix(env[1], "GIT_SSH_COMMAND="))
   306  		assert.Equal(t, "SOCKS5_USER=user", env[2], "SOCKS5 user env var must be set")
   307  		assert.Equal(t, "SOCKS5_PASSWD=password", env[3], "SOCKS5 password env var must be set")
   308  
   309  		if insecureIgnoreHostKey {
   310  			assert.Contains(t, env[1], "-o StrictHostKeyChecking=no")
   311  			assert.Contains(t, env[1], "-o UserKnownHostsFile=/dev/null")
   312  		} else {
   313  			assert.Contains(t, env[1], "-o StrictHostKeyChecking=yes")
   314  			hostsPath := cert.GetSSHKnownHostsDataPath()
   315  			assert.Contains(t, env[1], "-o UserKnownHostsFile="+hostsPath)
   316  		}
   317  		assert.Contains(t, env[1], "-o ProxyCommand='connect-proxy -S 127.0.0.1:1080 -5 %h %p'")
   318  
   319  		envRegex := regexp.MustCompile("-i ([^ ]+)")
   320  		assert.Regexp(t, envRegex, env[1])
   321  		privateKeyFile := envRegex.FindStringSubmatch(env[1])[1]
   322  		assert.FileExists(t, privateKeyFile)
   323  		utilio.Close(closer)
   324  		assert.NoFileExists(t, privateKeyFile)
   325  	}
   326  }
   327  
   328  func Test_SSHCreds_Environ_TempFileCleanupOnInvalidProxyURL(t *testing.T) {
   329  	// Previously, if the proxy URL was invalid, a temporary file would be left in /dev/shm. This ensures the file is cleaned up in this case.
   330  
   331  	// argoio.TempDir will be /dev/shm or "" (on an OS without /dev/shm).
   332  	// In this case os.CreateTemp(), which is used by creds.Environ(),
   333  	// will use os.TempDir for the temporary directory.
   334  	// Reproducing this logic here:
   335  	argoioTempDir := argoio.TempDir
   336  	if argoioTempDir == "" {
   337  		argoioTempDir = os.TempDir()
   338  	}
   339  
   340  	// countDev returns the number of files in the temporary directory
   341  	countFilesInDevShm := func() int {
   342  		entries, err := os.ReadDir(argoioTempDir)
   343  		require.NoError(t, err)
   344  
   345  		return len(entries)
   346  	}
   347  
   348  	for _, insecureIgnoreHostKey := range []bool{false, true} {
   349  		tempDir := t.TempDir()
   350  		caFile := path.Join(tempDir, "caFile")
   351  		err := os.WriteFile(caFile, []byte(""), os.FileMode(0o600))
   352  		require.NoError(t, err)
   353  		creds := NewSSHCreds("sshPrivateKey", caFile, insecureIgnoreHostKey, ":invalid-proxy-url")
   354  
   355  		filesInDevShmBeforeInvocation := countFilesInDevShm()
   356  
   357  		_, _, err = creds.Environ()
   358  		require.Error(t, err)
   359  
   360  		filesInDevShmAfterInvocation := countFilesInDevShm()
   361  
   362  		assert.Equal(t, filesInDevShmBeforeInvocation, filesInDevShmAfterInvocation, "no temporary files should leak if the proxy url cannot be parsed")
   363  	}
   364  }
   365  
   366  const gcpServiceAccountKeyJSON = `{
   367    "type": "service_account",
   368    "project_id": "my-google-project",
   369    "private_key_id": "REDACTED",
   370    "private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
   371    "client_email": "argocd-service-account@my-google-project.iam.gserviceaccount.com",
   372    "client_id": "REDACTED",
   373    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
   374    "token_uri": "https://oauth2.googleapis.com/token",
   375    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
   376    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/argocd-service-account%40my-google-project.iam.gserviceaccount.com"
   377  }`
   378  
   379  const invalidJSON = `{
   380    "type": "service_account",
   381    "project_id": "my-google-project",
   382  `
   383  
   384  func TestNewGoogleCloudCreds(t *testing.T) {
   385  	store := &memoryCredsStore{creds: make(map[string]cred)}
   386  	googleCloudCreds := NewGoogleCloudCreds(gcpServiceAccountKeyJSON, store)
   387  	assert.NotNil(t, googleCloudCreds)
   388  }
   389  
   390  func TestNewGoogleCloudCreds_invalidJSON(t *testing.T) {
   391  	store := &memoryCredsStore{creds: make(map[string]cred)}
   392  	googleCloudCreds := NewGoogleCloudCreds(invalidJSON, store)
   393  	assert.Nil(t, googleCloudCreds.creds)
   394  
   395  	token, err := googleCloudCreds.getAccessToken()
   396  	assert.Empty(t, token)
   397  	require.Error(t, err)
   398  
   399  	username, err := googleCloudCreds.getUsername()
   400  	assert.Empty(t, username)
   401  	require.Error(t, err)
   402  
   403  	closer, envStringSlice, err := googleCloudCreds.Environ()
   404  	assert.Equal(t, NopCloser{}, closer)
   405  	assert.Equal(t, []string(nil), envStringSlice)
   406  	require.Error(t, err)
   407  }
   408  
   409  func TestGoogleCloudCreds_Environ_cleanup(t *testing.T) {
   410  	store := &memoryCredsStore{creds: make(map[string]cred)}
   411  	staticToken := &oauth2.Token{AccessToken: "token"}
   412  	googleCloudCreds := GoogleCloudCreds{&google.Credentials{
   413  		ProjectID:   "my-google-project",
   414  		TokenSource: oauth2.StaticTokenSource(staticToken),
   415  		JSON:        []byte(gcpServiceAccountKeyJSON),
   416  	}, store}
   417  
   418  	closer, _, err := googleCloudCreds.Environ()
   419  	require.NoError(t, err)
   420  	credsLenBefore := len(store.creds)
   421  	utilio.Close(closer)
   422  	assert.Len(t, store.creds, credsLenBefore-1)
   423  }
   424  
   425  func TestAzureWorkloadIdentityCreds_Environ(t *testing.T) {
   426  	resetAzureTokenCache()
   427  	store := &memoryCredsStore{creds: make(map[string]cred)}
   428  	workloadIdentityMock := new(mocks.TokenProvider)
   429  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).Return(&workloadidentity.Token{AccessToken: "accessToken", ExpiresOn: time.Now().Add(time.Minute)}, nil)
   430  	creds := AzureWorkloadIdentityCreds{store, workloadIdentityMock}
   431  	_, env, err := creds.Environ()
   432  	require.NoError(t, err)
   433  	assert.Len(t, store.creds, 1)
   434  
   435  	for _, value := range store.creds {
   436  		assert.Empty(t, value.username)
   437  		assert.Equal(t, "accessToken", value.password)
   438  	}
   439  
   440  	require.Len(t, env, 1)
   441  	assert.Equal(t, "ARGOCD_GIT_BEARER_AUTH_HEADER=Authorization: Bearer accessToken", env[0], "ARGOCD_GIT_BEARER_AUTH_HEADER env var must be set")
   442  }
   443  
   444  func TestAzureWorkloadIdentityCreds_Environ_cleanup(t *testing.T) {
   445  	resetAzureTokenCache()
   446  	store := &memoryCredsStore{creds: make(map[string]cred)}
   447  	workloadIdentityMock := new(mocks.TokenProvider)
   448  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).Return(&workloadidentity.Token{AccessToken: "accessToken", ExpiresOn: time.Now().Add(time.Minute)}, nil)
   449  	creds := AzureWorkloadIdentityCreds{store, workloadIdentityMock}
   450  	closer, _, err := creds.Environ()
   451  	require.NoError(t, err)
   452  	credsLenBefore := len(store.creds)
   453  	utilio.Close(closer)
   454  	assert.Len(t, store.creds, credsLenBefore-1)
   455  }
   456  
   457  func TestAzureWorkloadIdentityCreds_GetUserInfo(t *testing.T) {
   458  	resetAzureTokenCache()
   459  	store := &memoryCredsStore{creds: make(map[string]cred)}
   460  	workloadIdentityMock := new(mocks.TokenProvider)
   461  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).Return(&workloadidentity.Token{AccessToken: "accessToken", ExpiresOn: time.Now().Add(time.Minute)}, nil)
   462  	creds := AzureWorkloadIdentityCreds{store, workloadIdentityMock}
   463  
   464  	user, email, err := creds.GetUserInfo(t.Context())
   465  	require.NoError(t, err)
   466  	assert.Equal(t, workloadidentity.EmptyGuid, user)
   467  	assert.Empty(t, email)
   468  }
   469  
   470  func TestGetHelmCredsShouldReturnHelmCredsIfAzureWorkloadIdentityNotSpecified(t *testing.T) {
   471  	var creds Creds = NewAzureWorkloadIdentityCreds(NoopCredsStore{}, new(mocks.TokenProvider))
   472  
   473  	_, ok := creds.(AzureWorkloadIdentityCreds)
   474  	require.Truef(t, ok, "expected HelmCreds but got %T", creds)
   475  }
   476  
   477  func TestAzureWorkloadIdentityCreds_FetchNewTokenIfExistingIsExpired(t *testing.T) {
   478  	resetAzureTokenCache()
   479  	store := &memoryCredsStore{creds: make(map[string]cred)}
   480  	workloadIdentityMock := new(mocks.TokenProvider)
   481  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).
   482  		Return(&workloadidentity.Token{AccessToken: "firstToken", ExpiresOn: time.Now().Add(time.Minute)}, nil).Once()
   483  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).
   484  		Return(&workloadidentity.Token{AccessToken: "secondToken"}, nil).Once()
   485  	creds := AzureWorkloadIdentityCreds{store, workloadIdentityMock}
   486  	token, err := creds.GetAzureDevOpsAccessToken()
   487  	require.NoError(t, err)
   488  
   489  	assert.Equal(t, "firstToken", token)
   490  	time.Sleep(5 * time.Second)
   491  	token, err = creds.GetAzureDevOpsAccessToken()
   492  	require.NoError(t, err)
   493  	assert.Equal(t, "secondToken", token)
   494  }
   495  
   496  func TestAzureWorkloadIdentityCreds_ReuseTokenIfExistingIsNotExpired(t *testing.T) {
   497  	resetAzureTokenCache()
   498  	store := &memoryCredsStore{creds: make(map[string]cred)}
   499  	workloadIdentityMock := new(mocks.TokenProvider)
   500  	firstToken := &workloadidentity.Token{AccessToken: "firstToken", ExpiresOn: time.Now().Add(6 * time.Minute)}
   501  	secondToken := &workloadidentity.Token{AccessToken: "secondToken"}
   502  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).Return(firstToken, nil).Once()
   503  	workloadIdentityMock.On("GetToken", azureDevopsEntraResourceId).Return(secondToken, nil).Once()
   504  	creds := AzureWorkloadIdentityCreds{store, workloadIdentityMock}
   505  	token, err := creds.GetAzureDevOpsAccessToken()
   506  	require.NoError(t, err)
   507  
   508  	assert.Equal(t, "firstToken", token)
   509  	time.Sleep(5 * time.Second)
   510  	token, err = creds.GetAzureDevOpsAccessToken()
   511  	require.NoError(t, err)
   512  	assert.Equal(t, "firstToken", token)
   513  }
   514  
   515  func resetAzureTokenCache() {
   516  	azureTokenCache = gocache.New(gocache.NoExpiration, 0)
   517  }