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(¶m, 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 }