github.com/hairyhenderson/gomplate/v4@v4.0.0-pre-2.0.20240520121557-362f058f0c93/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  	t.Helper()
    24  
    25  	_, vaultClient := startVault(t)
    26  
    27  	err := vaultClient.vc.Sys().PutPolicy("writepol", `path "*" {
    28    capabilities = ["create","update","delete"]
    29  }`)
    30  	require.NoError(t, err)
    31  	err = vaultClient.vc.Sys().PutPolicy("readpol", `path "*" {
    32    capabilities = ["read","delete"]
    33  }`)
    34  	require.NoError(t, err)
    35  	err = vaultClient.vc.Sys().PutPolicy("listpol", `path "*" {
    36    capabilities = ["read","list","delete"]
    37  }`)
    38  	require.NoError(t, err)
    39  
    40  	return vaultClient
    41  }
    42  
    43  func startVault(t *testing.T) (*fs.Dir, *vaultClient) {
    44  	t.Helper()
    45  
    46  	pidDir := fs.NewDir(t, "gomplate-inttests-vaultpid")
    47  	t.Cleanup(pidDir.Remove)
    48  
    49  	tmpDir := fs.NewDir(t, "gomplate-inttests",
    50  		fs.WithFile("config.json", `{
    51  		"pid_file": "`+pidDir.Join("vault.pid")+`"
    52  		}`),
    53  	)
    54  	t.Cleanup(tmpDir.Remove)
    55  
    56  	// rename any existing token so it doesn't get overridden
    57  	u, _ := user.Current()
    58  	homeDir := u.HomeDir
    59  	tokenFile := path.Join(homeDir, ".vault-token")
    60  	info, err := os.Stat(tokenFile)
    61  	if err == nil && info.Mode().IsRegular() {
    62  		os.Rename(tokenFile, path.Join(homeDir, ".vault-token.bak"))
    63  	}
    64  
    65  	_, vaultAddr := freeport(t)
    66  	vault := icmd.Command("vault", "server",
    67  		"-dev",
    68  		"-dev-root-token-id="+vaultRootToken,
    69  		"-dev-leased-kv",
    70  		"-log-level=err",
    71  		"-dev-listen-address="+vaultAddr,
    72  		"-config="+tmpDir.Join("config.json"),
    73  	)
    74  	result := icmd.StartCmd(vault)
    75  
    76  	t.Logf("Fired up Vault: %v", vault)
    77  
    78  	err = waitForURL(t, "http://"+vaultAddr+"/v1/sys/health")
    79  	require.NoError(t, err)
    80  
    81  	vaultClient, err := createVaultClient(vaultAddr, vaultRootToken)
    82  	require.NoError(t, err)
    83  
    84  	t.Cleanup(func() {
    85  		err := result.Cmd.Process.Kill()
    86  		require.NoError(t, err)
    87  
    88  		result.Cmd.Wait()
    89  
    90  		result.Assert(t, icmd.Expected{ExitCode: 0})
    91  
    92  		t.Log(result.Combined())
    93  
    94  		// restore old token if it was backed up
    95  		u, _ := user.Current()
    96  		homeDir := u.HomeDir
    97  		tokenFile := path.Join(homeDir, ".vault-token.bak")
    98  		info, err := os.Stat(tokenFile)
    99  		if err == nil && info.Mode().IsRegular() {
   100  			os.Rename(tokenFile, path.Join(homeDir, ".vault-token"))
   101  		}
   102  	})
   103  
   104  	return tmpDir, vaultClient
   105  }
   106  
   107  func TestDatasources_Vault_TokenAuth(t *testing.T) {
   108  	v := setupDatasourcesVaultTest(t)
   109  
   110  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   111  	defer v.vc.Logical().Delete("secret/foo")
   112  	tok, err := v.tokenCreate("readpol", 5)
   113  	require.NoError(t, err)
   114  
   115  	o, e, err := cmd(t, "-d", "vault=vault:///secret/",
   116  		"-i", `{{(ds "vault" "foo").value}}`).
   117  		withEnv("VAULT_ADDR", "http://"+v.addr).
   118  		withEnv("VAULT_TOKEN", tok).
   119  		run()
   120  	assertSuccess(t, o, e, err, "bar")
   121  
   122  	o, e, err = cmd(t, "-d", "vault=vault+http://"+v.addr+"/secret/",
   123  		"-i", `{{(ds "vault" "foo").value}}`).
   124  		withEnv("VAULT_TOKEN", tok).
   125  		run()
   126  	assertSuccess(t, o, e, err, "bar")
   127  
   128  	_, _, err = cmd(t, "-d", "vault=vault:///secret/",
   129  		"-i", `{{(ds "vault" "bar").value}}`).
   130  		withEnv("VAULT_ADDR", "http://"+v.addr).
   131  		withEnv("VAULT_TOKEN", tok).
   132  		run()
   133  	assert.ErrorContains(t, err, "error calling ds: couldn't read datasource 'vault':")
   134  	assert.ErrorContains(t, err, "stat secret/bar")
   135  	assert.ErrorContains(t, err, "file does not exist")
   136  
   137  	tokFile := fs.NewFile(t, "test-vault-token", fs.WithContent(tok))
   138  	defer tokFile.Remove()
   139  
   140  	o, e, err = cmd(t, "-d", "vault=vault:///secret/",
   141  		"-i", `{{(ds "vault" "foo").value}}`).
   142  		withEnv("VAULT_ADDR", "http://"+v.addr).
   143  		withEnv("VAULT_TOKEN_FILE", tokFile.Path()).
   144  		run()
   145  	assertSuccess(t, o, e, err, "bar")
   146  }
   147  
   148  func TestDatasources_Vault_UserPassAuth(t *testing.T) {
   149  	v := setupDatasourcesVaultTest(t)
   150  
   151  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   152  	defer v.vc.Logical().Delete("secret/foo")
   153  	err := v.vc.Sys().EnableAuth("userpass", "userpass", "")
   154  	require.NoError(t, err)
   155  	err = v.vc.Sys().EnableAuth("userpass2", "userpass", "")
   156  	require.NoError(t, err)
   157  	defer v.vc.Sys().DisableAuth("userpass")
   158  	defer v.vc.Sys().DisableAuth("userpass2")
   159  	_, err = v.vc.Logical().Write("auth/userpass/users/dave", map[string]interface{}{
   160  		"password": "foo", "ttl": "10s", "policies": "readpol",
   161  	})
   162  	require.NoError(t, err)
   163  	_, err = v.vc.Logical().Write("auth/userpass2/users/dave", map[string]interface{}{
   164  		"password": "bar", "ttl": "10s", "policies": "readpol",
   165  	})
   166  	require.NoError(t, err)
   167  
   168  	o, e, err := cmd(t, "-d", "vault=vault:///secret/",
   169  		"-i", `{{(ds "vault" "foo").value}}`).
   170  		withEnv("VAULT_ADDR", "http://"+v.addr).
   171  		withEnv("VAULT_AUTH_USERNAME", "dave").
   172  		withEnv("VAULT_AUTH_PASSWORD", "foo").
   173  		run()
   174  	assertSuccess(t, o, e, err, "bar")
   175  
   176  	userFile := fs.NewFile(t, "test-vault-user", fs.WithContent("dave"))
   177  	passFile := fs.NewFile(t, "test-vault-pass", fs.WithContent("foo"))
   178  	defer userFile.Remove()
   179  	defer passFile.Remove()
   180  	o, e, err = cmd(t,
   181  		"-d", "vault=vault:///secret/",
   182  		"-i", `{{(ds "vault" "foo").value}}`).
   183  		withEnv("VAULT_ADDR", "http://"+v.addr).
   184  		withEnv("VAULT_AUTH_USERNAME_FILE", userFile.Path()).
   185  		withEnv("VAULT_AUTH_PASSWORD_FILE", passFile.Path()).
   186  		run()
   187  	assertSuccess(t, o, e, err, "bar")
   188  
   189  	o, e, err = cmd(t,
   190  		"-d", "vault=vault:///secret/",
   191  		"-i", `{{(ds "vault" "foo").value}}`).
   192  		withEnv("VAULT_ADDR", "http://"+v.addr).
   193  		withEnv("VAULT_AUTH_USERNAME", "dave").
   194  		withEnv("VAULT_AUTH_PASSWORD", "bar").
   195  		withEnv("VAULT_AUTH_USERPASS_MOUNT", "userpass2").
   196  		run()
   197  	assertSuccess(t, o, e, err, "bar")
   198  }
   199  
   200  func TestDatasources_Vault_AppRoleAuth(t *testing.T) {
   201  	v := setupDatasourcesVaultTest(t)
   202  
   203  	v.vc.Logical().Write("secret/foo", map[string]interface{}{"value": "bar"})
   204  	defer v.vc.Logical().Delete("secret/foo")
   205  	err := v.vc.Sys().EnableAuth("approle", "approle", "")
   206  	require.NoError(t, err)
   207  	err = v.vc.Sys().EnableAuth("approle2", "approle", "")
   208  	require.NoError(t, err)
   209  	defer v.vc.Sys().DisableAuth("approle")
   210  	defer v.vc.Sys().DisableAuth("approle2")
   211  	_, err = v.vc.Logical().Write("auth/approle/role/testrole", map[string]interface{}{
   212  		"secret_id_ttl": "10s", "token_ttl": "20s",
   213  		"secret_id_num_uses": "1", "policies": "readpol",
   214  	})
   215  	require.NoError(t, err)
   216  	_, err = v.vc.Logical().Write("auth/approle2/role/testrole", map[string]interface{}{
   217  		"secret_id_ttl": "10s", "token_ttl": "20s",
   218  		"secret_id_num_uses": "1", "policies": "readpol",
   219  	})
   220  	require.NoError(t, err)
   221  
   222  	rid, _ := v.vc.Logical().Read("auth/approle/role/testrole/role-id")
   223  	roleID := rid.Data["role_id"].(string)
   224  	sid, _ := v.vc.Logical().Write("auth/approle/role/testrole/secret-id", nil)
   225  	secretID := sid.Data["secret_id"].(string)
   226  	o, e, err := cmd(t,
   227  		"-d", "vault=vault:///secret/",
   228  		"-i", `{{(ds "vault" "foo").value}}`).
   229  		withEnv("VAULT_ADDR", "http://"+v.addr).
   230  		withEnv("VAULT_ROLE_ID", roleID).
   231  		withEnv("VAULT_SECRET_ID", secretID).
   232  		run()
   233  	assertSuccess(t, o, e, err, "bar")
   234  
   235  	rid, _ = v.vc.Logical().Read("auth/approle2/role/testrole/role-id")
   236  	roleID = rid.Data["role_id"].(string)
   237  	sid, _ = v.vc.Logical().Write("auth/approle2/role/testrole/secret-id", nil)
   238  	secretID = sid.Data["secret_id"].(string)
   239  	o, e, err = cmd(t,
   240  		"-d", "vault=vault:///secret/",
   241  		"-i", `{{(ds "vault" "foo").value}}`).
   242  		withEnv("VAULT_ADDR", "http://"+v.addr).
   243  		withEnv("VAULT_ROLE_ID", roleID).
   244  		withEnv("VAULT_SECRET_ID", secretID).
   245  		withEnv("VAULT_AUTH_APPROLE_MOUNT", "approle2").
   246  		run()
   247  	assertSuccess(t, o, e, err, "bar")
   248  }
   249  
   250  func TestDatasources_Vault_DynamicAuth(t *testing.T) {
   251  	v := setupDatasourcesVaultTest(t)
   252  
   253  	err := v.vc.Sys().Mount("ssh/", &vaultapi.MountInput{Type: "ssh"})
   254  	require.NoError(t, err)
   255  	defer v.vc.Sys().Unmount("ssh")
   256  
   257  	_, err = v.vc.Logical().Write("ssh/roles/test", map[string]interface{}{
   258  		"key_type": "otp", "default_user": "user", "cidr_list": "10.0.0.0/8",
   259  	})
   260  	require.NoError(t, err)
   261  	testCommands := []struct {
   262  		ds, in string
   263  	}{
   264  		{"vault=vault:///", `{{(ds "vault" "ssh/creds/test?ip=10.1.2.3&username=user").ip}}`},
   265  		{"vault=vault:///ssh/creds/test", `{{(ds "vault" "?ip=10.1.2.3&username=user").ip}}`},
   266  		{"vault=vault:///ssh/creds/test?ip=10.1.2.3&username=user", `{{(ds "vault").ip}}`},
   267  		{"vault=vault:///?ip=10.1.2.3&username=user", `{{(ds "vault" "ssh/creds/test").ip}}`},
   268  	}
   269  
   270  	tok, err := v.tokenCreate("writepol", len(testCommands)*4)
   271  	require.NoError(t, err)
   272  
   273  	for _, tc := range testCommands {
   274  		o, e, err := cmd(t, "-d", tc.ds, "-i", tc.in).
   275  			withEnv("VAULT_ADDR", "http://"+v.addr).
   276  			withEnv("VAULT_TOKEN", tok).
   277  			run()
   278  		assertSuccess(t, o, e, err, "10.1.2.3")
   279  	}
   280  }
   281  
   282  func TestDatasources_Vault_List(t *testing.T) {
   283  	v := setupDatasourcesVaultTest(t)
   284  
   285  	v.vc.Logical().Write("secret/dir/foo", map[string]interface{}{"value": "one"})
   286  	v.vc.Logical().Write("secret/dir/bar", map[string]interface{}{"value": "two"})
   287  	defer v.vc.Logical().Delete("secret/dir/foo")
   288  	defer v.vc.Logical().Delete("secret/dir/bar")
   289  	tok, err := v.tokenCreate("listpol", 15)
   290  	require.NoError(t, err)
   291  
   292  	o, e, err := cmd(t,
   293  		"-d", "vault=vault:///secret/dir/",
   294  		"-i", `{{ range (ds "vault" ) }}{{ . }}: {{ (ds "vault" .).value }} {{end}}`).
   295  		withEnv("VAULT_ADDR", "http://"+v.addr).
   296  		withEnv("VAULT_TOKEN", tok).
   297  		run()
   298  	assertSuccess(t, o, e, err, "bar: two foo: one ")
   299  
   300  	o, e, err = cmd(t,
   301  		"-d", "vault=vault+http://"+v.addr+"/secret/",
   302  		"-i", `{{ range (ds "vault" "dir/" ) }}{{ . }} {{end}}`).
   303  		withEnv("VAULT_TOKEN", tok).
   304  		run()
   305  	assertSuccess(t, o, e, err, "bar foo ")
   306  }