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 }