github.com/jaylevin/jenkins-library@v1.230.4/pkg/config/vault_test.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/mock"
    12  
    13  	"github.com/SAP/jenkins-library/pkg/config/mocks"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  func TestVaultConfigLoad(t *testing.T) {
    18  	const secretName = "testSecret"
    19  	const secretNameOverrideKey = "mySecretVaultSecretName"
    20  	t.Parallel()
    21  	t.Run("Load secret from vault", func(t *testing.T) {
    22  		vaultMock := &mocks.VaultMock{}
    23  		stepConfig := StepConfig{Config: map[string]interface{}{
    24  			"vaultPath": "team1",
    25  		}}
    26  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    27  		vaultData := map[string]string{secretName: "value1"}
    28  
    29  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
    30  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    31  		assert.Equal(t, "value1", stepConfig.Config[secretName])
    32  	})
    33  
    34  	t.Run("Load secret from Vault with path override", func(t *testing.T) {
    35  		vaultMock := &mocks.VaultMock{}
    36  		stepConfig := StepConfig{Config: map[string]interface{}{
    37  			"vaultPath":           "team1",
    38  			secretNameOverrideKey: "overrideSecretName",
    39  		}}
    40  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    41  		vaultData := map[string]string{secretName: "value1"}
    42  
    43  		vaultMock.On("GetKvSecret", path.Join("team1", "overrideSecretName")).Return(vaultData, nil)
    44  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    45  		assert.Equal(t, "value1", stepConfig.Config[secretName])
    46  	})
    47  
    48  	t.Run("Secrets are not overwritten", func(t *testing.T) {
    49  		vaultMock := &mocks.VaultMock{}
    50  		stepConfig := StepConfig{Config: map[string]interface{}{
    51  			"vaultPath":             "team1",
    52  			secretName:              "preset value",
    53  			"vaultDisableOverwrite": true,
    54  		}}
    55  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    56  		vaultData := map[string]string{secretName: "value1"}
    57  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
    58  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    59  
    60  		assert.Equal(t, "preset value", stepConfig.Config[secretName])
    61  	})
    62  
    63  	t.Run("Secrets can be overwritten", func(t *testing.T) {
    64  		vaultMock := &mocks.VaultMock{}
    65  		stepConfig := StepConfig{Config: map[string]interface{}{
    66  			"vaultPath": "team1",
    67  			secretName:  "preset value",
    68  		}}
    69  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    70  		vaultData := map[string]string{secretName: "value1"}
    71  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
    72  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    73  
    74  		assert.Equal(t, "value1", stepConfig.Config[secretName])
    75  	})
    76  
    77  	t.Run("Error is passed through", func(t *testing.T) {
    78  		vaultMock := &mocks.VaultMock{}
    79  		stepConfig := StepConfig{Config: map[string]interface{}{
    80  			"vaultPath": "team1",
    81  		}}
    82  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    83  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(nil, fmt.Errorf("test"))
    84  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    85  		assert.Len(t, stepConfig.Config, 1)
    86  	})
    87  
    88  	t.Run("Secret doesn't exist", func(t *testing.T) {
    89  		vaultMock := &mocks.VaultMock{}
    90  		stepConfig := StepConfig{Config: map[string]interface{}{
    91  			"vaultPath": "team1",
    92  		}}
    93  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
    94  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(nil, nil)
    95  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
    96  		assert.Len(t, stepConfig.Config, 1)
    97  	})
    98  
    99  	t.Run("Alias names should be considered", func(t *testing.T) {
   100  		aliasName := "alias"
   101  		vaultMock := &mocks.VaultMock{}
   102  		stepConfig := StepConfig{Config: map[string]interface{}{
   103  			"vaultPath": "team1",
   104  		}}
   105  		param := stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)
   106  		addAlias(&param, aliasName)
   107  		stepParams := []StepParameters{param}
   108  		vaultData := map[string]string{aliasName: "value1"}
   109  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
   110  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   111  		assert.Equal(t, "value1", stepConfig.Config[secretName])
   112  	})
   113  
   114  	t.Run("Search over multiple paths", func(t *testing.T) {
   115  		vaultMock := &mocks.VaultMock{}
   116  		stepConfig := StepConfig{Config: map[string]interface{}{
   117  			"vaultBasePath": "team2",
   118  			"vaultPath":     "team1",
   119  		}}
   120  		stepParams := []StepParameters{
   121  			stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName),
   122  		}
   123  		vaultData := map[string]string{secretName: "value1"}
   124  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(nil, nil)
   125  		vaultMock.On("GetKvSecret", path.Join("team2/GROUP-SECRETS", secretName)).Return(vaultData, nil)
   126  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   127  		assert.Equal(t, "value1", stepConfig.Config[secretName])
   128  	})
   129  
   130  	t.Run("No BasePath is stepConfig.Configured", func(t *testing.T) {
   131  		vaultMock := &mocks.VaultMock{}
   132  		stepConfig := StepConfig{Config: map[string]interface{}{}}
   133  		stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
   134  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   135  		assert.Equal(t, nil, stepConfig.Config[secretName])
   136  		vaultMock.AssertNotCalled(t, "GetKvSecret", mock.AnythingOfType("string"))
   137  	})
   138  }
   139  
   140  func TestVaultSecretFiles(t *testing.T) {
   141  	const secretName = "testSecret"
   142  	const secretNameOverrideKey = "mySecretVaultSecretName"
   143  	t.Run("Test Vault Secret File Reference", func(t *testing.T) {
   144  		vaultMock := &mocks.VaultMock{}
   145  		stepConfig := StepConfig{Config: map[string]interface{}{
   146  			"vaultPath": "team1",
   147  		}}
   148  		stepParams := []StepParameters{stepParam(secretName, "vaultSecretFile", secretNameOverrideKey, secretName)}
   149  		vaultData := map[string]string{secretName: "value1"}
   150  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
   151  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   152  		assert.NotNil(t, stepConfig.Config[secretName])
   153  		path := stepConfig.Config[secretName].(string)
   154  		contentByte, err := ioutil.ReadFile(path)
   155  		assert.NoError(t, err)
   156  		content := string(contentByte)
   157  		assert.Equal(t, content, "value1")
   158  	})
   159  
   160  	os.RemoveAll(VaultSecretFileDirectory)
   161  	VaultSecretFileDirectory = ""
   162  
   163  	t.Run("Test temporary secret file cleanup", func(t *testing.T) {
   164  		vaultMock := &mocks.VaultMock{}
   165  		stepConfig := StepConfig{Config: map[string]interface{}{
   166  			"vaultPath": "team1",
   167  		}}
   168  		stepParams := []StepParameters{stepParam(secretName, "vaultSecretFile", secretNameOverrideKey, secretName)}
   169  		vaultData := map[string]string{secretName: "value1"}
   170  		assert.NoDirExists(t, VaultSecretFileDirectory)
   171  		vaultMock.On("GetKvSecret", path.Join("team1", secretName)).Return(vaultData, nil)
   172  		resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
   173  		assert.NotNil(t, stepConfig.Config[secretName])
   174  		path := stepConfig.Config[secretName].(string)
   175  		assert.DirExists(t, VaultSecretFileDirectory)
   176  		assert.FileExists(t, path)
   177  		RemoveVaultSecretFiles()
   178  		assert.NoFileExists(t, path)
   179  		assert.NoDirExists(t, VaultSecretFileDirectory)
   180  	})
   181  }
   182  
   183  func TestMixinVault(t *testing.T) {
   184  	vaultServerUrl := "https://testServer"
   185  	vaultPath := "testPath"
   186  	config := StepConfig{
   187  		Config:     map[string]interface{}{},
   188  		HookConfig: nil,
   189  	}
   190  	general := map[string]interface{}{
   191  		"vaultPath": vaultPath,
   192  	}
   193  	steps := map[string]interface{}{
   194  		"vaultServerUrl": vaultServerUrl,
   195  		"unknownConfig":  "test",
   196  	}
   197  
   198  	config.mixinVaultConfig(nil, general, steps)
   199  
   200  	assert.Contains(t, config.Config, "vaultServerUrl")
   201  	assert.Equal(t, vaultServerUrl, config.Config["vaultServerUrl"])
   202  	assert.Contains(t, config.Config, "vaultPath")
   203  	assert.Equal(t, vaultPath, config.Config["vaultPath"])
   204  	assert.NotContains(t, config.Config, "unknownConfig")
   205  
   206  }
   207  
   208  func stepParam(name, refType, vaultSecretNameProperty, defaultSecretNameName string) StepParameters {
   209  	return StepParameters{
   210  		Name:    name,
   211  		Aliases: []Alias{},
   212  		ResourceRef: []ResourceReference{
   213  			{
   214  				Type:    refType,
   215  				Name:    vaultSecretNameProperty,
   216  				Default: defaultSecretNameName,
   217  			},
   218  		},
   219  	}
   220  }
   221  
   222  func addAlias(param *StepParameters, aliasName string) {
   223  	alias := Alias{Name: aliasName}
   224  	param.Aliases = append(param.Aliases, alias)
   225  }
   226  
   227  func TestResolveVaultTestCredentials(t *testing.T) {
   228  	t.Parallel()
   229  	t.Run("Default test credential prefix", func(t *testing.T) {
   230  		t.Parallel()
   231  		// init
   232  		vaultMock := &mocks.VaultMock{}
   233  		envPrefix := "PIPER_TESTCREDENTIAL_"
   234  		stepConfig := StepConfig{Config: map[string]interface{}{
   235  			"vaultPath":               "team1",
   236  			"vaultTestCredentialPath": "appCredentials",
   237  			"vaultTestCredentialKeys": []interface{}{"appUser", "appUserPw"},
   238  		}}
   239  
   240  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER")
   241  		defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW")
   242  
   243  		// mock
   244  		vaultData := map[string]string{"appUser": "test-user", "appUserPw": "password1234"}
   245  		vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
   246  
   247  		// test
   248  		resolveVaultTestCredentials(&stepConfig, vaultMock)
   249  
   250  		// assert
   251  		for k, v := range vaultData {
   252  			env := envPrefix + strings.ToUpper(k)
   253  			assert.NotEmpty(t, os.Getenv(env))
   254  			assert.Equal(t, os.Getenv(env), v)
   255  		}
   256  	})
   257  
   258  	t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
   259  		t.Parallel()
   260  		// init
   261  		vaultMock := &mocks.VaultMock{}
   262  		envPrefix := "CUSTOM_MYCRED_"
   263  		standardEnvPrefix := "PIPER_VAULTCREDENTIAL_"
   264  		stepConfig := StepConfig{Config: map[string]interface{}{
   265  			"vaultPath":                "team1",
   266  			"vaultCredentialPath":      "appCredentials",
   267  			"vaultCredentialKeys":      []interface{}{"appUser", "appUserPw"},
   268  			"vaultCredentialEnvPrefix": envPrefix,
   269  		}}
   270  
   271  		defer os.Unsetenv("CUSTOM_MYCRED_APPUSER")
   272  		defer os.Unsetenv("CUSTOM_MYCRED_APPUSERPW")
   273  		defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSER")
   274  		defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSERPW")
   275  
   276  		// mock
   277  		vaultData := map[string]string{"appUser": "test-user", "appUserPw": "password1234"}
   278  		vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
   279  
   280  		// test
   281  		resolveVaultCredentials(&stepConfig, vaultMock)
   282  
   283  		// assert
   284  		for k, v := range vaultData {
   285  			env := envPrefix + strings.ToUpper(k)
   286  			assert.NotEmpty(t, os.Getenv(env))
   287  			assert.Equal(t, os.Getenv(env), v)
   288  			standardEnv := standardEnvPrefix + strings.ToUpper(k)
   289  			assert.NotEmpty(t, os.Getenv(standardEnv))
   290  			assert.Equal(t, os.Getenv(standardEnv), v)
   291  		}
   292  	})
   293  
   294  	t.Run("Custom test credential prefix", func(t *testing.T) {
   295  		t.Parallel()
   296  		// init
   297  		vaultMock := &mocks.VaultMock{}
   298  		envPrefix := "CUSTOM_CREDENTIAL_"
   299  		stepConfig := StepConfig{Config: map[string]interface{}{
   300  			"vaultPath":                    "team1",
   301  			"vaultTestCredentialPath":      "appCredentials",
   302  			"vaultTestCredentialKeys":      []interface{}{"appUser", "appUserPw"},
   303  			"vaultTestCredentialEnvPrefix": envPrefix,
   304  		}}
   305  
   306  		defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSER")
   307  		defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSERPW")
   308  
   309  		// mock
   310  		vaultData := map[string]string{"appUser": "test-user", "appUserPw": "password1234"}
   311  		vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
   312  
   313  		// test
   314  		resolveVaultTestCredentials(&stepConfig, vaultMock)
   315  
   316  		// assert
   317  		for k, v := range vaultData {
   318  			env := envPrefix + strings.ToUpper(k)
   319  			assert.NotEmpty(t, os.Getenv(env))
   320  			assert.Equal(t, os.Getenv(env), v)
   321  		}
   322  	})
   323  }
   324  
   325  func Test_convertEnvVar(t *testing.T) {
   326  	type args struct {
   327  		s string
   328  	}
   329  	tests := []struct {
   330  		name string
   331  		args args
   332  		want string
   333  	}{
   334  		{
   335  			name: "empty string",
   336  			args: args{""},
   337  			want: "",
   338  		},
   339  		{
   340  			name: "alphanumerical string",
   341  			args: args{"myApp1"},
   342  			want: "MYAPP1",
   343  		},
   344  		{
   345  			name: "string with hyphen",
   346  			args: args{"my_App-1"},
   347  			want: "MY_APP_1",
   348  		},
   349  		{
   350  			name: "string with special characters",
   351  			args: args{"my_App?-(1]"},
   352  			want: "MY_APP_1",
   353  		},
   354  	}
   355  	for _, tt := range tests {
   356  		t.Run(tt.name, func(t *testing.T) {
   357  			if got := convertEnvVar(tt.args.s); got != tt.want {
   358  				t.Errorf("convertEnvironment() = %v, want %v", got, tt.want)
   359  			}
   360  		})
   361  	}
   362  }