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 }