github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/config/vault_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package config
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/mock"
    15  
    16  	"github.com/SAP/jenkins-library/pkg/config/mocks"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestVaultConfigLoad(t *testing.T) {
    21  	const secretName = "testSecret"
    22  	const secretNameOverrideKey = "mySecretVaultSecretName"
    23  	t.Parallel()
    24  	t.Run("Load secret from vault", func(t *testing.T) {
    25  		vaultMock := &mocks.VaultMock{}
    26  		stepConfig := StepConfig{Config: map[string]interface{}{
    27  			"vaultPath": "team1",
    28  		}}
    29  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    30  		vaultData := map[string]string{secretName: "value1"}
    31  
    32  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
    33  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    34  		assert.Equal(t, "value1", stepConfig.Config[secretName])
    35  	})
    36  
    37  	t.Run("Load secret from Vault with path override", func(t *testing.T) {
    38  		vaultMock := &mocks.VaultMock{}
    39  		stepConfig := StepConfig{Config: map[string]interface{}{
    40  			"vaultPath":           "team1",
    41  			secretNameOverrideKey: "overrideSecretName",
    42  		}}
    43  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    44  		vaultData := map[string]string{secretName: "value1"}
    45  
    46  		vaultMock.On("GetKvSecret", path.Join("team1", "overrideSecretName")).Return(vaultData, nil)
    47  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    48  		assert.Equal(t, "value1", stepConfig.Config[secretName])
    49  	})
    50  
    51  	t.Run("Secrets are not overwritten", func(t *testing.T) {
    52  		vaultMock := &mocks.VaultMock{}
    53  		stepConfig := StepConfig{Config: map[string]interface{}{
    54  			"vaultPath":             "team1",
    55  			secretName:              "preset value",
    56  			"vaultDisableOverwrite": true,
    57  		}}
    58  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    59  		vaultData := map[string]string{secretName: "value1"}
    60  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
    61  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    62  
    63  		assert.Equal(t, "preset value", stepConfig.Config[secretName])
    64  	})
    65  
    66  	t.Run("Secrets can be overwritten", func(t *testing.T) {
    67  		vaultMock := &mocks.VaultMock{}
    68  		stepConfig := StepConfig{Config: map[string]interface{}{
    69  			"vaultPath": "team1",
    70  			secretName:  "preset value",
    71  		}}
    72  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    73  		vaultData := map[string]string{secretName: "value1"}
    74  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
    75  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    76  
    77  		assert.Equal(t, "value1", stepConfig.Config[secretName])
    78  	})
    79  
    80  	t.Run("Error is passed through", func(t *testing.T) {
    81  		vaultMock := &mocks.VaultMock{}
    82  		stepConfig := StepConfig{Config: map[string]interface{}{
    83  			"vaultPath": "team1",
    84  		}}
    85  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    86  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(nil, fmt.Errorf("test"))
    87  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    88  		assert.Len(t, stepConfig.Config, 1)
    89  	})
    90  
    91  	t.Run("Secret doesn't exist", func(t *testing.T) {
    92  		vaultMock := &mocks.VaultMock{}
    93  		stepConfig := StepConfig{Config: map[string]interface{}{
    94  			"vaultPath": "team1",
    95  		}}
    96  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    97  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(nil, nil)
    98  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    99  		assert.Len(t, stepConfig.Config, 1)
   100  	})
   101  
   102  	t.Run("Alias names should be considered", func(t *testing.T) {
   103  		aliasName := "alias"
   104  		vaultMock := &mocks.VaultMock{}
   105  		stepConfig := StepConfig{Config: map[string]interface{}{
   106  			"vaultPath": "team1",
   107  		}}
   108  		param := stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)
   109  		addAlias(&param, aliasName)
   110  		stepParams := []StepParameters{param}
   111  		vaultData := map[string]string{aliasName: "value1"}
   112  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
   113  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   114  		assert.Equal(t, "value1", stepConfig.Config[secretName])
   115  	})
   116  
   117  	t.Run("Search over multiple paths", func(t *testing.T) {
   118  		vaultMock := &mocks.VaultMock{}
   119  		stepConfig := StepConfig{Config: map[string]interface{}{
   120  			"vaultBasePath": "team2",
   121  			"vaultPath":     "team1",
   122  		}}
   123  		stepParams := []StepParameters{
   124  			stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName),
   125  		}
   126  		vaultData := map[string]string{secretName: "value1"}
   127  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(nil, nil)
   128  		vaultMock.On("GetKvSecret", path.Join("team2/GROUP-SECRETS", secretName)).Return(vaultData, nil)
   129  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   130  		assert.Equal(t, "value1", stepConfig.Config[secretName])
   131  	})
   132  
   133  	t.Run("No BasePath is stepConfig.Configured", func(t *testing.T) {
   134  		vaultMock := &mocks.VaultMock{}
   135  		stepConfig := StepConfig{Config: map[string]interface{}{}}
   136  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
   137  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   138  		assert.Nil(t, stepConfig.Config[secretName])
   139  		vaultMock.AssertNotCalled(t, "GetKvSecret", mock.AnythingOfType("string"))
   140  	})
   141  }
   142  
   143  func TestVaultSecretFiles(t *testing.T) {
   144  	const secretName = "testSecret"
   145  	const secretNameOverrideKey = "mySecretVaultSecretName"
   146  	t.Run("Test Vault Secret File Reference", func(t *testing.T) {
   147  		vaultMock := &mocks.VaultMock{}
   148  		stepConfig := StepConfig{Config: map[string]interface{}{
   149  			"vaultPath": "team1",
   150  		}}
   151  		stepParams := []StepParameters{stepParam(secretName, "vaultSecretFile", secretNameOverrideKey, secretName)}
   152  		vaultData := map[string]string{secretName: "value1"}
   153  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
   154  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   155  		assert.NotNil(t, stepConfig.Config[secretName])
   156  		path := stepConfig.Config[secretName].(string)
   157  		contentByte, err := os.ReadFile(path)
   158  		assert.NoError(t, err)
   159  		content := string(contentByte)
   160  		assert.Equal(t, "value1", content)
   161  	})
   162  
   163  	os.RemoveAll(VaultSecretFileDirectory)
   164  	VaultSecretFileDirectory = ""
   165  
   166  	t.Run("Test temporary secret file cleanup", func(t *testing.T) {
   167  		vaultMock := &mocks.VaultMock{}
   168  		stepConfig := StepConfig{Config: map[string]interface{}{
   169  			"vaultPath": "team1",
   170  		}}
   171  		stepParams := []StepParameters{stepParam(secretName, "vaultSecretFile", secretNameOverrideKey, secretName)}
   172  		vaultData := map[string]string{secretName: "value1"}
   173  		assert.NoDirExists(t, VaultSecretFileDirectory)
   174  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
   175  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   176  		assert.NotNil(t, stepConfig.Config[secretName])
   177  		path := stepConfig.Config[secretName].(string)
   178  		assert.DirExists(t, VaultSecretFileDirectory)
   179  		assert.FileExists(t, path)
   180  		RemoveVaultSecretFiles()
   181  		assert.NoFileExists(t, path)
   182  		assert.NoDirExists(t, VaultSecretFileDirectory)
   183  	})
   184  }
   185  
   186  func TestMixinVault(t *testing.T) {
   187  	vaultServerUrl := "https://testServer"
   188  	vaultPath := "testPath"
   189  	config := StepConfig{
   190  		Config:     map[string]interface{}{},
   191  		HookConfig: nil,
   192  	}
   193  	general := map[string]interface{}{
   194  		"vaultPath": vaultPath,
   195  	}
   196  	steps := map[string]interface{}{
   197  		"vaultServerUrl": vaultServerUrl,
   198  		"unknownConfig":  "test",
   199  	}
   200  
   201  	config.mixinVaultConfig(nil, general, steps)
   202  
   203  	assert.Contains(t, config.Config, "vaultServerUrl")
   204  	assert.Equal(t, vaultServerUrl, config.Config["vaultServerUrl"])
   205  	assert.Contains(t, config.Config, "vaultPath")
   206  	assert.Equal(t, vaultPath, config.Config["vaultPath"])
   207  	assert.NotContains(t, config.Config, "unknownConfig")
   208  
   209  }
   210  
   211  func stepParam(name, refType, vaultSecretNameProperty, defaultSecretNameName string) StepParameters {
   212  	return StepParameters{
   213  		Name:    name,
   214  		Aliases: []Alias{},
   215  		ResourceRef: []ResourceReference{
   216  			{
   217  				Type:    refType,
   218  				Name:    vaultSecretNameProperty,
   219  				Default: defaultSecretNameName,
   220  			},
   221  		},
   222  	}
   223  }
   224  
   225  func addAlias(param *StepParameters, aliasName string) {
   226  	alias := Alias{Name: aliasName}
   227  	param.Aliases = append(param.Aliases, alias)
   228  }
   229  
   230  func TestResolveVaultTestCredentialsWrapper(t *testing.T) {
   231  	t.Parallel()
   232  	t.Run("Default test credential prefix", func(t *testing.T) {
   233  		t.Parallel()
   234  		// init
   235  		vaultMock := &mocks.VaultMock{}
   236  		envPrefix := "PIPER_TESTCREDENTIAL_"
   237  		stepConfig := StepConfig{Config: map[string]interface{}{
   238  			"vaultPath":               "team1",
   239  			"vaultTestCredentialPath": []interface{}{"appCredentials1", "appCredentials2"},
   240  			"vaultTestCredentialKeys": []interface{}{[]interface{}{"appUser1", "appUserPw1"}, []interface{}{"appUser2", "appUserPw2"}},
   241  		}}
   242  
   243  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER1")
   244  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW1")
   245  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER2")
   246  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW2")
   247  
   248  		// mock
   249  		vaultData1 := map[string]string{"appUser1": "test-user", "appUserPw1": "password1234"}
   250  		vaultMock.On("GetKvSecret", "team1/appCredentials1").Return(vaultData1, nil)
   251  		vaultData2 := map[string]string{"appUser2": "test-user", "appUserPw2": "password1234"}
   252  		vaultMock.On("GetKvSecret", "team1/appCredentials2").Return(vaultData2, nil)
   253  
   254  		// test
   255  		resolveVaultTestCredentialsWrapper(&stepConfig, vaultMock)
   256  
   257  		// assert
   258  		for k, expectedValue := range vaultData1 {
   259  			env := envPrefix + strings.ToUpper(k)
   260  			assert.NotEmpty(t, os.Getenv(env))
   261  			assert.Equal(t, expectedValue, os.Getenv(env))
   262  		}
   263  
   264  		// assert
   265  		for k, expectedValue := range vaultData2 {
   266  			env := envPrefix + strings.ToUpper(k)
   267  			assert.NotEmpty(t, os.Getenv(env))
   268  			assert.Equal(t, expectedValue, os.Getenv(env))
   269  		}
   270  	})
   271  
   272  	// Test empty and non-empty custom general purpose credential prefix
   273  	envPrefixes := []string{"CUSTOM_MYCRED1_", ""}
   274  	for idx, envPrefix := range envPrefixes {
   275  		tEnvPrefix := envPrefix
   276  		// this variable is used to avoid race condition, because tests are running in parallel
   277  		// env variable with default prefix is being created for each iteration and being set and unset asynchronously
   278  		// race condition may occur while one function sets and tries to assert if it exists but the other unsets it before it
   279  		stIdx := strconv.Itoa(idx)
   280  		t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
   281  			t.Parallel()
   282  			// init
   283  			vaultMock := &mocks.VaultMock{}
   284  			standardEnvPrefix := "PIPER_VAULTCREDENTIAL_"
   285  			stepConfig := StepConfig{Config: map[string]interface{}{
   286  				"vaultPath":                "team1",
   287  				"vaultCredentialPath":      "appCredentials3",
   288  				"vaultCredentialKeys":      []interface{}{"appUser3" + stIdx, "appUserPw3" + stIdx},
   289  				"vaultCredentialEnvPrefix": tEnvPrefix,
   290  			}}
   291  
   292  			defer os.Unsetenv(tEnvPrefix + "APPUSER3" + stIdx)
   293  			defer os.Unsetenv(tEnvPrefix + "APPUSERPW3" + stIdx)
   294  			defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSER3" + stIdx)
   295  			defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSERPW3" + stIdx)
   296  
   297  			// mock
   298  			vaultData := map[string]string{"appUser3" + stIdx: "test-user", "appUserPw3" + stIdx: "password1234"}
   299  			vaultMock.On("GetKvSecret", "team1/appCredentials3").Return(vaultData, nil)
   300  
   301  			// test
   302  			resolveVaultCredentialsWrapper(&stepConfig, vaultMock)
   303  
   304  			// assert
   305  			for k, expectedValue := range vaultData {
   306  				env := tEnvPrefix + strings.ToUpper(k)
   307  				assert.NotEmpty(t, os.Getenv(env))
   308  				assert.Equal(t, expectedValue, os.Getenv(env))
   309  				standardEnv := standardEnvPrefix + strings.ToUpper(k)
   310  				assert.NotEmpty(t, os.Getenv(standardEnv))
   311  				assert.Equal(t, expectedValue, os.Getenv(standardEnv))
   312  			}
   313  		})
   314  	}
   315  }
   316  
   317  func TestResolveVaultTestCredentials(t *testing.T) {
   318  	t.Parallel()
   319  	t.Run("Default test credential prefix", func(t *testing.T) {
   320  		t.Parallel()
   321  		// init
   322  		vaultMock := &mocks.VaultMock{}
   323  		envPrefix := "PIPER_TESTCREDENTIAL_"
   324  		stepConfig := StepConfig{Config: map[string]interface{}{
   325  			"vaultPath":               "team1",
   326  			"vaultTestCredentialPath": "appCredentials",
   327  			"vaultTestCredentialKeys": []interface{}{"appUser4", "appUserPw4"},
   328  		}}
   329  
   330  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER4")
   331  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW4")
   332  
   333  		// mock
   334  		vaultData := map[string]string{"appUser4": "test-user", "appUserPw4": "password1234"}
   335  		vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
   336  
   337  		// test
   338  		resolveVaultTestCredentials(&stepConfig, vaultMock)
   339  
   340  		// assert
   341  		for k, expectedValue := range vaultData {
   342  			env := envPrefix + strings.ToUpper(k)
   343  			assert.NotEmpty(t, os.Getenv(env))
   344  			assert.Equal(t, expectedValue, os.Getenv(env))
   345  		}
   346  	})
   347  
   348  	// Test empty and non-empty custom general purpose credential prefix
   349  	envPrefixes := []string{"CUSTOM_MYCRED_", ""}
   350  	for idx, envPrefix := range envPrefixes {
   351  		tEnvPrefix := envPrefix
   352  		// this variable is used to avoid race condition, because tests are running in parallel
   353  		// env variable with default prefix is being created for each iteration and being set and unset asynchronously
   354  		// race condition may occur while one function sets and tries to assert if it exists but the other unsets it before it
   355  		stIdx := strconv.Itoa(idx)
   356  		t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
   357  			t.Parallel()
   358  			// init
   359  			vaultMock := &mocks.VaultMock{}
   360  			standardEnvPrefix := "PIPER_VAULTCREDENTIAL_"
   361  			stepConfig := StepConfig{Config: map[string]interface{}{
   362  				"vaultPath":                "team1",
   363  				"vaultCredentialPath":      "appCredentials",
   364  				"vaultCredentialKeys":      []interface{}{"appUser5" + stIdx, "appUserPw5" + stIdx},
   365  				"vaultCredentialEnvPrefix": tEnvPrefix,
   366  			}}
   367  
   368  			defer os.Unsetenv(tEnvPrefix + "APPUSER5" + stIdx)
   369  			defer os.Unsetenv(tEnvPrefix + "APPUSERPW5" + stIdx)
   370  			defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSER5" + stIdx)
   371  			defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSERPW5" + stIdx)
   372  
   373  			// mock
   374  			vaultData := map[string]string{"appUser5" + stIdx: "test-user", "appUserPw5" + stIdx: "password1234"}
   375  			vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
   376  
   377  			// test
   378  			resolveVaultCredentials(&stepConfig, vaultMock)
   379  
   380  			// assert
   381  			for k, expectedValue := range vaultData {
   382  				env := tEnvPrefix + strings.ToUpper(k)
   383  				assert.NotEmpty(t, os.Getenv(env))
   384  				assert.Equal(t, expectedValue, os.Getenv(env))
   385  				standardEnv := standardEnvPrefix + strings.ToUpper(k)
   386  				assert.NotEmpty(t, os.Getenv(standardEnv))
   387  				assert.Equal(t, expectedValue, os.Getenv(standardEnv))
   388  			}
   389  		})
   390  	}
   391  
   392  	t.Run("Custom test credential prefix", func(t *testing.T) {
   393  		t.Parallel()
   394  		// init
   395  		vaultMock := &mocks.VaultMock{}
   396  		envPrefix := "CUSTOM_CREDENTIAL_"
   397  		stepConfig := StepConfig{Config: map[string]interface{}{
   398  			"vaultPath":                    "team1",
   399  			"vaultTestCredentialPath":      "appCredentials",
   400  			"vaultTestCredentialKeys":      []interface{}{"appUser6", "appUserPw6"},
   401  			"vaultTestCredentialEnvPrefix": envPrefix,
   402  		}}
   403  
   404  		defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSER6")
   405  		defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSERPW6")
   406  
   407  		// mock
   408  		vaultData := map[string]string{"appUser6": "test-user", "appUserPw6": "password1234"}
   409  		vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
   410  
   411  		// test
   412  		resolveVaultTestCredentials(&stepConfig, vaultMock)
   413  
   414  		// assert
   415  		for k, expectedValue := range vaultData {
   416  			env := envPrefix + strings.ToUpper(k)
   417  			assert.NotEmpty(t, os.Getenv(env))
   418  			assert.Equal(t, expectedValue, os.Getenv(env))
   419  		}
   420  	})
   421  }
   422  
   423  func Test_convertEnvVar(t *testing.T) {
   424  	type args struct {
   425  		s string
   426  	}
   427  	tests := []struct {
   428  		name string
   429  		args args
   430  		want string
   431  	}{
   432  		{
   433  			name: "empty string",
   434  			args: args{""},
   435  			want: "",
   436  		},
   437  		{
   438  			name: "alphanumerical string",
   439  			args: args{"myApp1"},
   440  			want: "MYAPP1",
   441  		},
   442  		{
   443  			name: "string with hyphen",
   444  			args: args{"my_App-1"},
   445  			want: "MY_APP_1",
   446  		},
   447  		{
   448  			name: "string with special characters",
   449  			args: args{"my_App?-(1]"},
   450  			want: "MY_APP_1",
   451  		},
   452  	}
   453  	for _, tt := range tests {
   454  		t.Run(tt.name, func(t *testing.T) {
   455  			if got := ConvertEnvVar(tt.args.s); got != tt.want {
   456  				t.Errorf("convertEnvironment() = %v, want %v", got, tt.want)
   457  			}
   458  		})
   459  	}
   460  }