* Save and assign hclogger throughout the client cache daemon * Don't event for errors happening outside of the handlers or tickers. * Hide and ignore -store-debug and expose the -log-level flag
328 lines
8.8 KiB
Go
328 lines
8.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/boundary/api"
|
|
"github.com/hashicorp/boundary/api/authtokens"
|
|
"github.com/hashicorp/boundary/internal/clientcache/internal/cache"
|
|
cachedb "github.com/hashicorp/boundary/internal/clientcache/internal/db"
|
|
"github.com/hashicorp/boundary/internal/cmd/base"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// ringToken is a test struct used to group a keyring type and token name
|
|
// so it can be used in an authtoken lookup function.
|
|
type ringToken struct {
|
|
k string
|
|
t string
|
|
}
|
|
|
|
// mapBasedAuthTokenKeyringLookup provides a fake KeyringTokenLookupFn that uses
|
|
// the provided map to perform lookups for the tokens
|
|
func mapBasedAuthTokenKeyringLookup(m map[ringToken]*authtokens.AuthToken) cache.KeyringTokenLookupFn {
|
|
return func(k, t string) *authtokens.AuthToken {
|
|
return m[ringToken{k, t}]
|
|
}
|
|
}
|
|
|
|
// sliceBasedAuthTokenBoundaryReader provides a fake BoundaryTokenReaderFn that uses
|
|
// the provided map to lookup an auth tokens information.
|
|
func sliceBasedAuthTokenBoundaryReader(s []*authtokens.AuthToken) cache.BoundaryTokenReaderFn {
|
|
return func(ctx context.Context, addr, at string) (*authtokens.AuthToken, error) {
|
|
for _, v := range s {
|
|
if at == v.Token {
|
|
return v, nil
|
|
}
|
|
}
|
|
return nil, errors.New("not found")
|
|
}
|
|
}
|
|
|
|
type testRefresher struct {
|
|
called bool
|
|
}
|
|
|
|
func (r *testRefresher) refresh() {
|
|
r.called = true
|
|
}
|
|
|
|
func addToken(t *testing.T, h http.Handler, tokReq *UpsertTokenRequest) *api.Error {
|
|
t.Helper()
|
|
b, err := json.Marshal(tokReq)
|
|
require.NoError(t, err)
|
|
rawBody := bytes.NewBuffer(b)
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/tokens", rawBody)
|
|
h.ServeHTTP(rec, req)
|
|
if rec.Result().StatusCode == http.StatusNoContent {
|
|
return nil
|
|
}
|
|
apiErr := &api.Error{}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), apiErr))
|
|
return apiErr
|
|
}
|
|
|
|
func TestKeyringToken(t *testing.T) {
|
|
ctx := context.Background()
|
|
s, err := cachedb.Open(ctx)
|
|
require.NoError(t, err)
|
|
|
|
at := &authtokens.AuthToken{
|
|
Id: "at_1",
|
|
Token: "at_1_token",
|
|
UserId: "user",
|
|
}
|
|
boundaryAuthTokens := []*authtokens.AuthToken{at}
|
|
keyring := "k"
|
|
tokenName := "t"
|
|
atMap := map[ringToken]*authtokens.AuthToken{
|
|
{keyring, tokenName}: at,
|
|
}
|
|
r, err := cache.NewRepository(ctx, s, &sync.Map{}, mapBasedAuthTokenKeyringLookup(atMap), sliceBasedAuthTokenBoundaryReader(boundaryAuthTokens))
|
|
require.NoError(t, err)
|
|
|
|
tr := &testRefresher{}
|
|
ph, err := newTokenHandlerFunc(ctx, r, tr, hclog.NewNullLogger())
|
|
require.NoError(t, err)
|
|
|
|
t.Run("missing keyring", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: "",
|
|
TokenName: tokenName,
|
|
},
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: at.Id,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
require.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "keyring_type is a required field but was empty")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("none keyring", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: base.NoneKeyring,
|
|
TokenName: tokenName,
|
|
},
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: at.Id,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
require.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "keyring.keyring_type is set to none which is not supported")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("missing token name", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: keyring,
|
|
TokenName: "",
|
|
},
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: at.Id,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "keyring.token_name is a required field but was empty")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("missing boundary address", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: keyring,
|
|
TokenName: tokenName,
|
|
},
|
|
BoundaryAddr: "",
|
|
AuthTokenId: at.Id,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "boundary_addr is a required field but was empty")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("missing auth token id", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: keyring,
|
|
TokenName: tokenName,
|
|
},
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: "",
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "auth_token_id is a required field but was empty")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("mismatched auth token id", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: keyring,
|
|
TokenName: tokenName,
|
|
},
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: "at_doesntmatch",
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "Failed to add a keyring stored token")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
Keyring: &KeyringToken{
|
|
KeyringType: keyring,
|
|
TokenName: tokenName,
|
|
},
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: at.Id,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.Nil(t, apiErr)
|
|
assert.True(t, tr.called)
|
|
|
|
p, err := r.LookupToken(ctx, pa.AuthTokenId)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, p)
|
|
assert.Equal(t, at.Id, p.Id)
|
|
})
|
|
}
|
|
|
|
func TestKeyringlessToken(t *testing.T) {
|
|
ctx := context.Background()
|
|
s, err := cachedb.Open(ctx)
|
|
require.NoError(t, err)
|
|
|
|
at := &authtokens.AuthToken{
|
|
Id: "at_1",
|
|
Token: "at_1_token",
|
|
UserId: "user",
|
|
}
|
|
boundaryAuthTokens := []*authtokens.AuthToken{at}
|
|
atMap := map[ringToken]*authtokens.AuthToken{}
|
|
r, err := cache.NewRepository(ctx, s, &sync.Map{}, mapBasedAuthTokenKeyringLookup(atMap), sliceBasedAuthTokenBoundaryReader(boundaryAuthTokens))
|
|
require.NoError(t, err)
|
|
|
|
tr := &testRefresher{}
|
|
ph, err := newTokenHandlerFunc(ctx, r, tr, hclog.NewNullLogger())
|
|
require.NoError(t, err)
|
|
|
|
t.Run("missing boundary address", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
BoundaryAddr: "",
|
|
AuthTokenId: at.Id,
|
|
AuthToken: at.Token,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "boundary_addr is a required field but was empty")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("missing auth token id", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: "",
|
|
AuthToken: at.Token,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "auth_token_id is a required field but was empty")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("mismatched auth token id", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: "at_doesntmatch",
|
|
AuthToken: at.Token,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.NotNil(t, apiErr)
|
|
assert.Contains(t, apiErr.Message, "auth_token_id doesn't match the auth_token's prefix")
|
|
assert.False(t, tr.called)
|
|
})
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
pa := &UpsertTokenRequest{
|
|
BoundaryAddr: "http://127.0.0.1",
|
|
AuthTokenId: at.Id,
|
|
AuthToken: at.Token,
|
|
}
|
|
apiErr := addToken(t, ph, pa)
|
|
assert.Nil(t, apiErr)
|
|
assert.True(t, tr.called)
|
|
|
|
p, err := r.LookupToken(ctx, pa.AuthTokenId)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, p)
|
|
assert.Equal(t, at.Id, p.Id)
|
|
})
|
|
}
|
|
|
|
func TestUpsertTokenRequest_String(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
req *UpsertTokenRequest
|
|
want string
|
|
}{
|
|
{
|
|
name: "auth token fully redacted",
|
|
req: &UpsertTokenRequest{
|
|
BoundaryAddr: "test",
|
|
AuthToken: "at_wrong_SomethingElseHere",
|
|
AuthTokenId: "at_prefix",
|
|
},
|
|
want: "BoundaryAddr: \"test\", AuthTokenId: \"at_prefix\", AuthToken: \"/*redacted*/\"",
|
|
},
|
|
{
|
|
name: "auth token partially redacted",
|
|
req: &UpsertTokenRequest{
|
|
BoundaryAddr: "test",
|
|
AuthToken: "at_prefix_SomethingElseHere",
|
|
AuthTokenId: "at_prefix",
|
|
},
|
|
want: "BoundaryAddr: \"test\", AuthTokenId: \"at_prefix\", AuthToken: \"at_prefix_/*redacted*/\"",
|
|
},
|
|
{
|
|
name: "auth token partially redacted",
|
|
req: &UpsertTokenRequest{
|
|
BoundaryAddr: "test",
|
|
Keyring: &KeyringToken{
|
|
TokenName: "token",
|
|
KeyringType: "type",
|
|
},
|
|
AuthToken: "at_prefix_SomethingElseHere",
|
|
AuthTokenId: "at_prefix",
|
|
},
|
|
want: "BoundaryAddr: \"test\", AuthTokenId: \"at_prefix\", Keyring: {KeyringType:type TokenName:token}, AuthToken: \"at_prefix_/*redacted*/\"",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, tc.req.String())
|
|
})
|
|
}
|
|
}
|