github.com/hairyhenderson/gomplate/v3@v3.11.7/internal/tests/integration/datasources_vault_test.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package integration
     5  
     6  import (
     7  	"os"
     8  	"os/user"
     9  	"path"
    10  	"testing"
    11  
    12  	vaultapi "github.com/hashicorp/vault/api"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"gotest.tools/v3/assert"
    16  	"gotest.tools/v3/fs"
    17  	"gotest.tools/v3/icmd"
    18  )
    19  
    20  const vaultRootToken = "00000000-1111-2222-3333-444455556666"
    21  
    22  func setupDatasourcesVaultTest(t *testing.T) *vaultClient {
    23  	_, vaultClient := startVault(t)
    24  
    25  	err := vaultClient.vc.Sys().PutPolicy("writepol", `path "*" {
    26    capabilities = ["create","update","delete"]
    27  }`)
    28  	require.NoError(t, err)
    29  	err = vaultClient.vc.Sys().PutPolicy("readpol", `path "*" {
    30    capabilities = ["read","delete"]
    31  }`)
    32  	require.NoError(t, err)
    33  	err = vaultClient.vc.Sys().PutPolicy("listPol", `path "*" {
    34    capabilities = ["read","list","delete"]
    35  }`)
    36  	require.NoError(t, err)
    37  
    38  	return vaultClient
    39  }
    40  
    41  func startVault(t *testing.T) (*fs.Dir, *vaultClient) {
    42  	pidDir := fs.NewDir(t, "gomplate-inttests-vaultpid")
    43  	t.Cleanup(pidDir.Remove)
    44  
    45  	tmpDir := fs.NewDir(t, "gomplate-inttests",
    46  		fs.WithFile("config.json", `{
    47  		"pid_file": "`+pidDir.Join("vault.pid")+`"
    48  		}`),
    49  	)
    50  	t.Cleanup(tmpDir.Remove)
    51  
    52  	// rename any existing token so it doesn't get overridden
    53  	u, _ := user.Current()
    54  	homeDir := u.HomeDir
    55  	tokenFile := path.Join(homeDir, ".vault-token")
    56  	info, err := os.Stat(tokenFile)
    57  	if err == nil && info.Mode().IsRegular() {
    58  		os.Rename(tokenFile, path.Join(homeDir, ".vault-token.bak"))
    59  	}
    60  
    61  	_, vaultAddr := freeport(t)
    62  	vault := icmd.Command("vault", "server",
    63  		"-dev",
    64  		"-dev-root-token-id="+vaultRootToken,
    65  		"-dev-leased-kv",
    66  		"-log-level=err",
    67  		"-dev-listen-address="+vaultAddr,
    68  		"-config="+tmpDir.Join("config.json"),
    69  	)
    70  	result := icmd.StartCmd(vault)
    71  
    72  	t.Logf("Fired up Vault: %v", vault)
    73  
    74  	err = waitForURL(t, "http://"+vaultAddr+"/v1/sys/health")
    75  	require.NoError(t, err)
    76  
    77  	vaultClient, err := createVaultClient(vaultAddr, vaultRootToken)
    78  	require.NoError(t, err)
    79  
    80  	t.Cleanup(func() {
    81  		err := result.Cmd.Process.Kill()
    82  		require.NoError(t, err)
    83  
    84  		result.Cmd.Wait()
    85  
    86  		result.Assert(t, icmd.Expected{ExitCode: 0})
    87  
    88  		// restore old token if it was backed up
    89  		u, _ := user.Current()
    90  		homeDir := u.HomeDir
    91  		tokenFile := path.Join(homeDir, ".vault-token.bak")
    92  		info, err := os.Stat(tokenFile)
    93  		if err == nil && info.Mode().IsRegular() {
    94  			os.Rename(tokenFile, path.Join(homeDir, ".vault-token"))
    95  		}
    96  	})
    97  
    98  	return tmpDir, vaultClient
    99  }
   100  
   101  func TestDatasources_Vault_TokenAuth(t *testing.T) {
   102  	v := setupDatasourcesVaultTest(t)
   103  
   104  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   105  	defer v.vc.Logical().Delete("secret/foo")
   106  	tok, err := v.tokenCreate("readpol", 5)
   107  	require.NoError(t, err)
   108  
   109  	o, e, err := cmd(t, "-d", "vault=vault:///secret",
   110  		"-i", `{{(ds "vault" "foo").value}}`).
   111  		withEnv("VAULT_ADDR", "http://"+v.addr).
   112  		withEnv("VAULT_TOKEN", tok).
   113  		run()
   114  	assertSuccess(t, o, e, err, "bar")
   115  
   116  	o, e, err = cmd(t, "-d", "vault=vault+http://"+v.addr+"/secret",
   117  		"-i", `{{(ds "vault" "foo").value}}`).
   118  		withEnv("VAULT_TOKEN", tok).
   119  		run()
   120  	assertSuccess(t, o, e, err, "bar")
   121  
   122  	_, _, err = cmd(t, "-d", "vault=vault:///secret",
   123  		"-i", `{{(ds "vault" "bar").value}}`).
   124  		withEnv("VAULT_ADDR", "http://"+v.addr).
   125  		withEnv("VAULT_TOKEN", tok).
   126  		run()
   127  	assert.ErrorContains(t, err, "error calling ds: Couldn't read datasource 'vault': no value found for path /secret/bar")
   128  
   129  	tokFile := fs.NewFile(t, "test-vault-token", fs.WithContent(tok))
   130  	defer tokFile.Remove()
   131  
   132  	o, e, err = cmd(t, "-d", "vault=vault:///secret",
   133  		"-i", `{{(ds "vault" "foo").value}}`).
   134  		withEnv("VAULT_ADDR", "http://"+v.addr).
   135  		withEnv("VAULT_TOKEN_FILE", tokFile.Path()).
   136  		run()
   137  	assertSuccess(t, o, e, err, "bar")
   138  }
   139  
   140  func TestDatasources_Vault_UserPassAuth(t *testing.T) {
   141  	v := setupDatasourcesVaultTest(t)
   142  
   143  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   144  	defer v.vc.Logical().Delete("secret/foo")
   145  	err := v.vc.Sys().EnableAuth("userpass", "userpass", "")
   146  	require.NoError(t, err)
   147  	err = v.vc.Sys().EnableAuth("userpass2", "userpass", "")
   148  	require.NoError(t, err)
   149  	defer v.vc.Sys().DisableAuth("userpass")
   150  	defer v.vc.Sys().DisableAuth("userpass2")
   151  	_, err = v.vc.Logical().Write("auth/userpass/users/dave", map[string]interface{}{
   152  		"password": "foo", "ttl": "10s", "policies": "readpol",
   153  	})
   154  	require.NoError(t, err)
   155  	_, err = v.vc.Logical().Write("auth/userpass2/users/dave", map[string]interface{}{
   156  		"password": "bar", "ttl": "10s", "policies": "readpol",
   157  	})
   158  	require.NoError(t, err)
   159  
   160  	o, e, err := cmd(t, "-d", "vault=vault:///secret",
   161  		"-i", `{{(ds "vault" "foo").value}}`).
   162  		withEnv("VAULT_ADDR", "http://"+v.addr).
   163  		withEnv("VAULT_AUTH_USERNAME", "dave").
   164  		withEnv("VAULT_AUTH_PASSWORD", "foo").
   165  		run()
   166  	assertSuccess(t, o, e, err, "bar")
   167  
   168  	userFile := fs.NewFile(t, "test-vault-user", fs.WithContent("dave"))
   169  	passFile := fs.NewFile(t, "test-vault-pass", fs.WithContent("foo"))
   170  	defer userFile.Remove()
   171  	defer passFile.Remove()
   172  	o, e, err = cmd(t,
   173  		"-d", "vault=vault:///secret",
   174  		"-i", `{{(ds "vault" "foo").value}}`).
   175  		withEnv("VAULT_ADDR", "http://"+v.addr).
   176  		withEnv("VAULT_AUTH_USERNAME_FILE", userFile.Path()).
   177  		withEnv("VAULT_AUTH_PASSWORD_FILE", passFile.Path()).
   178  		run()
   179  	assertSuccess(t, o, e, err, "bar")
   180  
   181  	o, e, err = cmd(t,
   182  		"-d", "vault=vault:///secret",
   183  		"-i", `{{(ds "vault" "foo").value}}`).
   184  		withEnv("VAULT_ADDR", "http://"+v.addr).
   185  		withEnv("VAULT_AUTH_USERNAME", "dave").
   186  		withEnv("VAULT_AUTH_PASSWORD", "bar").
   187  		withEnv("VAULT_AUTH_USERPASS_MOUNT", "userpass2").
   188  		run()
   189  	assertSuccess(t, o, e, err, "bar")
   190  }
   191  
   192  func TestDatasources_Vault_AppRoleAuth(t *testing.T) {
   193  	v := setupDatasourcesVaultTest(t)
   194  
   195  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   196  	defer v.vc.Logical().Delete("secret/foo")
   197  	err := v.vc.Sys().EnableAuth("approle", "approle", "")
   198  	require.NoError(t, err)
   199  	err = v.vc.Sys().EnableAuth("approle2", "approle", "")
   200  	require.NoError(t, err)
   201  	defer v.vc.Sys().DisableAuth("approle")
   202  	defer v.vc.Sys().DisableAuth("approle2")
   203  	_, err = v.vc.Logical().Write("auth/approle/role/testrole", map[string]interface{}{
   204  		"secret_id_ttl": "10s", "token_ttl": "20s",
   205  		"secret_id_num_uses": "1", "policies": "readpol",
   206  	})
   207  	require.NoError(t, err)
   208  	_, err = v.vc.Logical().Write("auth/approle2/role/testrole", map[string]interface{}{
   209  		"secret_id_ttl": "10s", "token_ttl": "20s",
   210  		"secret_id_num_uses": "1", "policies": "readpol",
   211  	})
   212  	require.NoError(t, err)
   213  
   214  	rid, _ := v.vc.Logical().Read("auth/approle/role/testrole/role-id")
   215  	roleID := rid.Data["role_id"].(string)
   216  	sid, _ := v.vc.Logical().Write("auth/approle/role/testrole/secret-id", nil)
   217  	secretID := sid.Data["secret_id"].(string)
   218  	o, e, err := cmd(t,
   219  		"-d", "vault=vault:///secret",
   220  		"-i", `{{(ds "vault" "foo").value}}`).
   221  		withEnv("VAULT_ADDR", "http://"+v.addr).
   222  		withEnv("VAULT_ROLE_ID", roleID).
   223  		withEnv("VAULT_SECRET_ID", secretID).
   224  		run()
   225  	assertSuccess(t, o, e, err, "bar")
   226  
   227  	rid, _ = v.vc.Logical().Read("auth/approle2/role/testrole/role-id")
   228  	roleID = rid.Data["role_id"].(string)
   229  	sid, _ = v.vc.Logical().Write("auth/approle2/role/testrole/secret-id", nil)
   230  	secretID = sid.Data["secret_id"].(string)
   231  	o, e, err = cmd(t,
   232  		"-d", "vault=vault:///secret",
   233  		"-i", `{{(ds "vault" "foo").value}}`).
   234  		withEnv("VAULT_ADDR", "http://"+v.addr).
   235  		withEnv("VAULT_ROLE_ID", roleID).
   236  		withEnv("VAULT_SECRET_ID", secretID).
   237  		withEnv("VAULT_AUTH_APPROLE_MOUNT", "approle2").
   238  		run()
   239  	assertSuccess(t, o, e, err, "bar")
   240  }
   241  
   242  func TestDatasources_Vault_AppIDAuth(t *testing.T) {
   243  	t.Skip("AppID auth is in 'Pending Removal' in Vault as of 1.12.0 - see https://support.hashicorp.com/hc/en-us/articles/13203258987027-Unable-to-Start-Vault-Due-to-Pending-Removal-Error")
   244  
   245  	v := setupDatasourcesVaultTest(t)
   246  
   247  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   248  	defer v.vc.Logical().Delete("secret/foo")
   249  	err := v.vc.Sys().EnableAuth("app-id", "app-id", "")
   250  	require.NoError(t, err)
   251  	err = v.vc.Sys().EnableAuth("app-id2", "app-id", "")
   252  	require.NoError(t, err)
   253  	defer v.vc.Sys().DisableAuth("app-id")
   254  	defer v.vc.Sys().DisableAuth("app-id2")
   255  	_, err = v.vc.Logical().Write("auth/app-id/map/app-id/testappid", map[string]interface{}{
   256  		"display_name": "test_app_id", "value": "readpol",
   257  	})
   258  	require.NoError(t, err)
   259  	_, err = v.vc.Logical().Write("auth/app-id/map/user-id/testuserid", map[string]interface{}{
   260  		"value": "testappid",
   261  	})
   262  	require.NoError(t, err)
   263  	_, err = v.vc.Logical().Write("auth/app-id2/map/app-id/testappid", map[string]interface{}{
   264  		"display_name": "test_app_id", "value": "readpol",
   265  	})
   266  	require.NoError(t, err)
   267  	_, err = v.vc.Logical().Write("auth/app-id2/map/user-id/testuserid", map[string]interface{}{
   268  		"value": "testappid",
   269  	})
   270  	require.NoError(t, err)
   271  
   272  	o, e, err := cmd(t,
   273  		"-d", "vault=vault:///secret",
   274  		"-i", `{{(ds "vault" "foo").value}}`).
   275  		withEnv("VAULT_ADDR", "http://"+v.addr).
   276  		withEnv("VAULT_APP_ID", "testappid").
   277  		withEnv("VAULT_USER_ID", "testuserid").
   278  		run()
   279  	assertSuccess(t, o, e, err, "bar")
   280  
   281  	o, e, err = cmd(t,
   282  		"-d", "vault=vault:///secret",
   283  		"-i", `{{(ds "vault" "foo").value}}`).
   284  		withEnv("VAULT_ADDR", "http://"+v.addr).
   285  		withEnv("VAULT_APP_ID", "testappid").
   286  		withEnv("VAULT_USER_ID", "testuserid").
   287  		withEnv("VAULT_AUTH_APP_ID_MOUNT", "app-id2").
   288  		run()
   289  	assertSuccess(t, o, e, err, "bar")
   290  }
   291  
   292  func TestDatasources_Vault_DynamicAuth(t *testing.T) {
   293  	v := setupDatasourcesVaultTest(t)
   294  
   295  	err := v.vc.Sys().Mount("ssh/", &vaultapi.MountInput{Type: "ssh"})
   296  	require.NoError(t, err)
   297  	defer v.vc.Sys().Unmount("ssh")
   298  
   299  	_, err = v.vc.Logical().Write("ssh/roles/test", map[string]interface{}{
   300  		"key_type": "otp", "default_user": "user", "cidr_list": "10.0.0.0/8",
   301  	})
   302  	require.NoError(t, err)
   303  	testCommands := []struct {
   304  		ds, in string
   305  	}{
   306  		{"vault=vault:///", `{{(ds "vault" "ssh/creds/test?ip=10.1.2.3&username=user").ip}}`},
   307  		{"vault=vault:///ssh/creds/test", `{{(ds "vault" "?ip=10.1.2.3&username=user").ip}}`},
   308  		{"vault=vault:///ssh/creds/test?ip=10.1.2.3&username=user", `{{(ds "vault").ip}}`},
   309  		{"vault=vault:///?ip=10.1.2.3&username=user", `{{(ds "vault" "ssh/creds/test").ip}}`},
   310  	}
   311  	tok, err := v.tokenCreate("writepol", len(testCommands)*2)
   312  	require.NoError(t, err)
   313  
   314  	for _, tc := range testCommands {
   315  		o, e, err := cmd(t, "-d", tc.ds, "-i", tc.in).
   316  			withEnv("VAULT_ADDR", "http://"+v.addr).
   317  			withEnv("VAULT_TOKEN", tok).
   318  			run()
   319  		assertSuccess(t, o, e, err, "10.1.2.3")
   320  	}
   321  }
   322  
   323  func TestDatasources_Vault_List(t *testing.T) {
   324  	v := setupDatasourcesVaultTest(t)
   325  
   326  	v.vc.Logical().Write("secret/dir/foo", map[string]interface{}{"value": "one"})
   327  	v.vc.Logical().Write("secret/dir/bar", map[string]interface{}{"value": "two"})
   328  	defer v.vc.Logical().Delete("secret/dir/foo")
   329  	defer v.vc.Logical().Delete("secret/dir/bar")
   330  	tok, err := v.tokenCreate("listpol", 5)
   331  	require.NoError(t, err)
   332  
   333  	o, e, err := cmd(t,
   334  		"-d", "vault=vault:///secret/dir/",
   335  		"-i", `{{ range (ds "vault" ) }}{{ . }}: {{ (ds "vault" .).value }} {{end}}`).
   336  		withEnv("VAULT_ADDR", "http://"+v.addr).
   337  		withEnv("VAULT_TOKEN", tok).
   338  		run()
   339  	assertSuccess(t, o, e, err, "bar: two foo: one ")
   340  
   341  	o, e, err = cmd(t,
   342  		"-d", "vault=vault+http://"+v.addr+"/secret",
   343  		"-i", `{{ range (ds "vault" "dir/" ) }}{{ . }} {{end}}`).
   344  		withEnv("VAULT_TOKEN", tok).
   345  		run()
   346  	assertSuccess(t, o, e, err, "bar foo ")
   347  }