Files
threshold/internal/tests/api/credentialstores/credentialstore_test.go
T
Louis Ruch d7c4c648ec bug(vault): Correctly handle credential stores with expired tokens (#2399)
* bug(vault): Correctly handle credential stores with expired tokens

Boundary makes use of a database view to perform CRUD operations on
Vault credential stores and libraries. This view did not include
credential stores with tokens that have expired in Vault, causing
errors when attempting to perform any action against them.
2022-09-06 07:50:15 -07:00

222 lines
8.2 KiB
Go

package credentialstores_test
import (
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/daemon/controller"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestList(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS))
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
storeClient := credentialstores.NewClient(client)
ul, err := storeClient.List(tc.Context(), proj.GetPublicId())
require.NoError(err)
assert.Empty(ul.Items)
var expected []*credentialstores.CredentialStore
for i := 0; i < 10; i++ {
_, tok := vaultServ.CreateToken(t)
expected = append(expected, &credentialstores.CredentialStore{Name: fmt.Sprint(i), Attributes: map[string]interface{}{
"address": vaultServ.Addr,
"token": tok,
}})
}
cr, err := storeClient.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName(expected[0].Name),
credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(expected[0].Attributes["token"].(string)))
require.NoError(err)
expected[0] = cr.Item
ul, err = storeClient.List(tc.Context(), proj.GetPublicId())
require.NoError(err)
assert.ElementsMatch(comparableCatalogSlice(expected[:1]), comparableCatalogSlice(ul.Items))
for i := 1; i < 5; i++ {
cr, err = storeClient.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName(expected[i].Name),
credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(expected[i].Attributes["token"].(string)))
require.NoError(err)
expected[i] = cr.Item
}
for i := 5; i < 10; i++ {
cr, err = storeClient.Create(tc.Context(), "static", proj.GetPublicId(), credentialstores.WithName(expected[i].Name))
require.NoError(err)
expected[i] = cr.Item
}
ul, err = storeClient.List(tc.Context(), proj.GetPublicId())
require.NoError(err)
assert.ElementsMatch(comparableCatalogSlice(expected), comparableCatalogSlice(ul.Items))
filterItem := ul.Items[3]
ul, err = storeClient.List(tc.Context(), proj.GetPublicId(),
credentialstores.WithFilter(fmt.Sprintf(`"/item/id"==%q`, filterItem.Id)))
require.NoError(err)
assert.Len(ul.Items, 1)
assert.Equal(filterItem.Id, ul.Items[0].Id)
}
func comparableCatalogSlice(in []*credentialstores.CredentialStore) []credentialstores.CredentialStore {
var filtered []credentialstores.CredentialStore
for _, i := range in {
p := credentialstores.CredentialStore{
Id: i.Id,
Name: i.Name,
Description: i.Description,
CreatedTime: i.CreatedTime,
UpdatedTime: i.UpdatedTime,
Attributes: i.Attributes,
}
filtered = append(filtered, p)
}
return filtered
}
func TestCrud(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS))
_, vaultTok := vaultServ.CreateToken(t)
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
checkResource := func(step string, cs *credentialstores.CredentialStore, err error, wantedName string, wantedTokenStatus vault.TokenStatus, wantVersion uint32) {
assert.NotNil(cs, "returned no resource", step)
gotName := ""
if cs.Name != "" {
gotName = cs.Name
}
assert.Equal(wantedName, gotName, step)
assert.Equal(wantVersion, cs.Version)
status, ok := cs.Attributes["token_status"].(string)
require.True(ok)
assert.Equal(string(wantedTokenStatus), status)
}
csClient := credentialstores.NewClient(client)
cs, err := csClient.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName("foo"),
credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok))
require.NoError(err)
require.NotNil(cs)
checkResource("create", cs.Item, err, "foo", vault.CurrentToken, 1)
cs, err = csClient.Read(tc.Context(), cs.Item.Id)
checkResource("read", cs.Item, err, "foo", vault.CurrentToken, 1)
cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.WithName("bar"))
checkResource("update", cs.Item, err, "bar", vault.CurrentToken, 2)
cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.DefaultName())
checkResource("update", cs.Item, err, "", vault.CurrentToken, 3)
// Set previous token to expired in the database and revoke in Vault to validate a
// credential store with an expired token is correctly returned over the API
rw := db.New(tc.DbConn())
num, err := rw.Exec(tc.Context(), "update credential_vault_token set status = ? where store_id = ?",
[]interface{}{vault.ExpiredToken, cs.GetItem().Id})
require.NoError(err)
assert.Equal(1, num)
vaultServ.RevokeToken(t, vaultTok)
cs, err = csClient.Read(tc.Context(), cs.Item.Id)
checkResource("read", cs.Item, err, "", vault.ExpiredToken, 3)
// Create a new token and update cred store
_, newVaultTok := vaultServ.CreateToken(t)
cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.WithName("bar"),
credentialstores.WithVaultCredentialStoreToken(newVaultTok))
checkResource("update", cs.Item, err, "bar", vault.CurrentToken, 4)
_, err = csClient.Delete(tc.Context(), cs.Item.Id)
assert.NoError(err)
_, err = csClient.Delete(tc.Context(), cs.Item.Id)
require.Error(err)
apiErr := api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
}
func TestErrors(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS))
_, vaultTok := vaultServ.CreateToken(t)
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
c := credentialstores.NewClient(client)
vs, err := c.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName("foo"),
credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok))
require.NoError(err)
require.NotNil(vs)
// A malformed id is processed as the id and not a different path to the api.
_, err = c.Read(tc.Context(), fmt.Sprintf("%s/../", vs.Item.Id))
require.Error(err)
apiErr := api.AsServerError(err)
require.NotNil(apiErr)
assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode())
require.Len(apiErr.Details.RequestFields, 1)
assert.Equal(apiErr.Details.RequestFields[0].Name, "id")
// Updating the wrong version should fail.
_, err = c.Update(tc.Context(), vs.Item.Id, 73, credentialstores.WithName("anything"))
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
// same token
_, err = c.Create(tc.Context(), "vault", proj.GetPublicId(),
credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok))
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
// Same name
_, err = c.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName("foo"))
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
_, err = c.Read(tc.Context(), vault.CredentialStorePrefix+"_doesntexis")
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
_, err = c.Read(tc.Context(), "invalid id")
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode())
}