mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2415 lines
75 KiB
2415 lines
75 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package vault
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/authtoken"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/host/static"
|
|
"github.com/hashicorp/boundary/internal/iam"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/scheduler"
|
|
"github.com/hashicorp/boundary/internal/session"
|
|
"github.com/hashicorp/boundary/internal/target"
|
|
"github.com/hashicorp/boundary/internal/target/tcp"
|
|
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
|
"github.com/hashicorp/go-uuid"
|
|
vault "github.com/hashicorp/vault/api"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const testUpdateTokenStatusExpirationQuery = `
|
|
update credential_vault_token
|
|
set status = ?,
|
|
last_renewal_time = now(),
|
|
expiration_time = wt_add_seconds_to_now(?)
|
|
where token_hmac = ?;
|
|
`
|
|
|
|
func testVaultToken(t *testing.T,
|
|
conn *db.DB,
|
|
wrapper wrapping.Wrapper,
|
|
v *TestVaultServer,
|
|
cs *CredentialStore,
|
|
status TokenStatus,
|
|
expiration time.Duration,
|
|
) *Token {
|
|
t.Helper()
|
|
assert, require := assert.New(t), require.New(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
rw := db.New(conn)
|
|
|
|
secret, _ := v.CreateToken(t)
|
|
inToken, err := newToken(context.Background(), cs.PublicId, []byte(secret.Auth.ClientToken), []byte(secret.Auth.Accessor), expiration)
|
|
require.NoError(err)
|
|
inToken.Status = string(status)
|
|
|
|
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), cs.ProjectId, kms.KeyPurposeDatabase)
|
|
require.NoError(err)
|
|
|
|
require.NoError(inToken.encrypt(context.Background(), databaseWrapper))
|
|
|
|
query := insertTokenQuery
|
|
queryValues := []any{
|
|
sql.Named("1", inToken.TokenHmac),
|
|
sql.Named("2", inToken.CtToken),
|
|
sql.Named("3", inToken.StoreId),
|
|
sql.Named("4", inToken.KeyId),
|
|
sql.Named("5", inToken.Status),
|
|
}
|
|
expire := int(expiration.Seconds())
|
|
if expire < 0 {
|
|
// last_renewal_time must be before expiration_time, if we are testing a expiration
|
|
// in the past set last_renewal_time to 1 second before that
|
|
query = strings.Replace(query,
|
|
"@6, -- last_renewal_time",
|
|
"wt_add_seconds_to_now(@6), -- last_renewal_time",
|
|
-1)
|
|
queryValues = append(queryValues, expire-1, sql.Named("6", expire))
|
|
} else {
|
|
queryValues = append(queryValues, sql.Named("6", "now()"), sql.Named("7", expire))
|
|
}
|
|
|
|
numRows, err := rw.Exec(context.Background(), query, queryValues)
|
|
assert.Equal(1, numRows)
|
|
require.NoError(err)
|
|
|
|
outToken := allocToken()
|
|
require.NoError(rw.LookupWhere(context.Background(), &outToken, "token_hmac = ?", []any{inToken.TokenHmac}))
|
|
require.NoError(outToken.decrypt(context.Background(), databaseWrapper))
|
|
|
|
return outToken
|
|
}
|
|
|
|
func testVaultCred(t *testing.T,
|
|
conn *db.DB,
|
|
v *TestVaultServer,
|
|
cl *CredentialLibrary,
|
|
sess *session.Session,
|
|
token *Token,
|
|
status CredentialStatus,
|
|
expiration time.Duration,
|
|
) (*vault.Secret, *Credential) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
rw := db.New(conn)
|
|
|
|
client := v.client(t)
|
|
var secret *vault.Secret
|
|
var err error
|
|
switch Method(cl.HttpMethod) {
|
|
case MethodGet:
|
|
secret, err = client.get(ctx, cl.VaultPath)
|
|
case MethodPost:
|
|
secret, err = client.post(ctx, cl.VaultPath, cl.HttpRequestBody)
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(secret)
|
|
|
|
id, err := newCredentialId(ctx)
|
|
require.NoError(err)
|
|
|
|
query := insertCredentialWithExpirationQuery
|
|
queryValues := []any{
|
|
sql.Named("public_id", id),
|
|
sql.Named("library_id", cl.GetPublicId()),
|
|
sql.Named("session_id", sess.GetPublicId()),
|
|
sql.Named("token_hmac", token.GetTokenHmac()),
|
|
sql.Named("external_id", secret.LeaseID),
|
|
sql.Named("is_renewable", true),
|
|
sql.Named("status", status),
|
|
}
|
|
expire := int(expiration.Seconds())
|
|
if expire < 0 {
|
|
// last_renewal_time must be before expiration_time, if we are testing a expiration
|
|
// in the past set last_renewal_time to 1 second before that
|
|
query = strings.Replace(query,
|
|
"@last_renewal_time, -- last_renewal_time",
|
|
"wt_add_seconds_to_now(@last_renewal_time), -- last_renewal_time",
|
|
-1)
|
|
queryValues = append(queryValues, sql.Named("last_renewal_time", expire-1), sql.Named("expiration_time", expire))
|
|
} else {
|
|
queryValues = append(queryValues, sql.Named("last_renewal_time", "now()"), sql.Named("expiration_time", expire))
|
|
}
|
|
|
|
numRows, err := rw.Exec(context.Background(), query, queryValues)
|
|
assert.Equal(1, numRows)
|
|
assert.NoError(err)
|
|
|
|
outCred := allocCredential()
|
|
require.NoError(rw.LookupWhere(context.Background(), &outCred, "public_id = ?", []any{id}))
|
|
|
|
return secret, outCred
|
|
}
|
|
|
|
func TestNewTokenRenewalJob(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
type args struct {
|
|
r db.Reader
|
|
w db.Writer
|
|
kms *kms.Kms
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
options []Option
|
|
wantLimit int
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
}{
|
|
{
|
|
name: "nil reader",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil writer",
|
|
args: args{
|
|
r: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil kms",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "valid-no-options",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
wantLimit: db.DefaultLimit,
|
|
},
|
|
{
|
|
name: "valid-with-limit",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
options: []Option{WithLimit(100)},
|
|
wantLimit: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := newTokenRenewalJob(context.Background(), tt.args.r, tt.args.w, tt.args.kms, tt.options...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Equal(tt.args.r, got.reader)
|
|
assert.Equal(tt.args.w, got.writer)
|
|
assert.Equal(tt.args.kms, got.kms)
|
|
assert.Equal(tt.wantLimit, got.limit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenRenewalJob_RunLimits(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
v := NewTestVaultServer(t)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
|
|
count := 5
|
|
tests := []struct {
|
|
name string
|
|
opts []Option
|
|
wantLen int
|
|
}{
|
|
{
|
|
name: "with-no-limits",
|
|
wantLen: count + 1, // +1 for current token
|
|
},
|
|
{
|
|
name: "with-negative-limit",
|
|
opts: []Option{WithLimit(-1)},
|
|
wantLen: count + 1, // +1 for current token
|
|
},
|
|
{
|
|
name: "with-limit",
|
|
opts: []Option{WithLimit(2)},
|
|
wantLen: 2,
|
|
},
|
|
{
|
|
name: "with-limit-greater-than-count",
|
|
opts: []Option{WithLimit(count + 10)},
|
|
wantLen: count + 1, // +1 for current token
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
_, token := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
err = RegisterJobs(ctx, sche, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
// Create additional tokens and alternative between token statuses, revoked and
|
|
// expired tokens should have no impact on number tokens renewed
|
|
for i := 0; i < count*3; i++ {
|
|
var status TokenStatus
|
|
switch i % 3 {
|
|
case 0:
|
|
status = MaintainingToken
|
|
case 1:
|
|
status = RevokedToken
|
|
case 2:
|
|
status = ExpiredToken
|
|
}
|
|
testVaultToken(t, conn, wrapper, v, cs, status, 5*time.Minute)
|
|
}
|
|
|
|
// inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time
|
|
numRows, err := rw.Exec(ctx, testUpdateTokenStatusExpirationQuery, []any{CurrentToken, time.Minute.Seconds(), cs.outputToken.TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, numRows)
|
|
|
|
r, err := newTokenRenewalJob(ctx, rw, rw, kmsCache, tt.opts...)
|
|
require.NoError(err)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(tt.wantLen, r.numTokens)
|
|
|
|
// Set all tokens to revoked for next test
|
|
_, err = rw.Exec(ctx, "update credential_vault_token set status = 'revoked'", nil)
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenRenewalJob_Run(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
v := NewTestVaultServer(t)
|
|
|
|
// Create 24 hour token
|
|
_, token := v.CreateToken(t, WithTokenPeriod(24*time.Hour))
|
|
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
|
|
r, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, r)
|
|
require.NoError(err)
|
|
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// No tokens should have been renewed since token expiration is 24 hours by default
|
|
assert.Equal(0, r.numProcessed)
|
|
|
|
// Create maintaining, revoked and expired tokens
|
|
maintainToken := testVaultToken(t, conn, wrapper, v, cs, MaintainingToken, time.Minute)
|
|
revokedToken := testVaultToken(t, conn, wrapper, v, cs, RevokedToken, time.Minute)
|
|
expiredToken := testVaultToken(t, conn, wrapper, v, cs, ExpiredToken, time.Minute)
|
|
|
|
// inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time
|
|
count, err := rw.Exec(ctx, testUpdateTokenStatusExpirationQuery, []any{CurrentToken, time.Minute.Seconds(), cs.outputToken.TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
currentToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, ¤tToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
databaseWrapper, err := kmsCache.GetWrapper(ctx, cs.ProjectId, kms.KeyPurposeDatabase)
|
|
require.NoError(err)
|
|
require.NoError(currentToken.decrypt(ctx, databaseWrapper))
|
|
|
|
// Sleep to move clock
|
|
time.Sleep(time.Second * 2)
|
|
|
|
// Get ttls in vault to verify against before running renewal
|
|
lookupToken := v.LookupToken(t, string(currentToken.GetToken()))
|
|
oldCurrentTtl, err := lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
lookupToken = v.LookupToken(t, string(maintainToken.GetToken()))
|
|
oldMaintainTtl, err := lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
lookupToken = v.LookupToken(t, string(revokedToken.GetToken()))
|
|
oldRevokedTtl, err := lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
lookupToken = v.LookupToken(t, string(expiredToken.GetToken()))
|
|
oldExpiredTtl, err := lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
|
|
// Run token renewal again
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// Current and maintaining token should have been processed
|
|
assert.Equal(2, r.numProcessed)
|
|
|
|
// Verify current and maintaining token were renewed in vault
|
|
lookupToken = v.LookupToken(t, string(currentToken.GetToken()))
|
|
newTtl, err := lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
assert.True(oldCurrentTtl < newTtl)
|
|
lookupToken = v.LookupToken(t, string(maintainToken.GetToken()))
|
|
newTtl, err = lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
assert.True(oldMaintainTtl < newTtl)
|
|
|
|
// Verify expired and revoked tokens were not renewed in vault
|
|
lookupToken = v.LookupToken(t, string(revokedToken.GetToken()))
|
|
newTtl, err = lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
assert.True(oldRevokedTtl >= newTtl)
|
|
lookupToken = v.LookupToken(t, string(expiredToken.GetToken()))
|
|
newTtl, err = lookupToken.TokenTTL()
|
|
require.NoError(err)
|
|
assert.True(oldExpiredTtl >= newTtl)
|
|
|
|
// Verify current and maintaining tokens were renewed in repo
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{currentToken.TokenHmac}))
|
|
assert.True(currentToken.GetExpirationTime().AsTime().Before(repoToken.GetExpirationTime().AsTime()))
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{maintainToken.TokenHmac}))
|
|
assert.True(maintainToken.GetExpirationTime().AsTime().Before(repoToken.GetExpirationTime().AsTime()))
|
|
|
|
// Verify revoked and expired tokens were not renewed in the repo
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{revokedToken.TokenHmac}))
|
|
assert.Equal(revokedToken.GetExpirationTime().AsTime(), repoToken.GetExpirationTime().AsTime())
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{expiredToken.TokenHmac}))
|
|
assert.Equal(expiredToken.GetExpirationTime().AsTime(), repoToken.GetExpirationTime().AsTime())
|
|
}
|
|
|
|
func TestTokenRenewalJob_RunExpired(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper, scheduler.WithRunJobsInterval(time.Second))
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
v := NewTestVaultServer(t)
|
|
|
|
// Create 2s token so it expires in vault before we can renew it
|
|
_, ct := v.CreateToken(t, WithTokenPeriod(time.Second*2))
|
|
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
assert.NoError(err)
|
|
require.NotNil(in)
|
|
|
|
r, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
err = sche.RegisterJob(ctx, r)
|
|
require.NoError(err)
|
|
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
// Sleep to move clock and expire token
|
|
time.Sleep(time.Second * 2)
|
|
|
|
// Token should have expired in vault, run should now expire in repo
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(1, r.numTokens)
|
|
|
|
// Verify token was expired in repo
|
|
token := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &token, "store_id = ?", []any{cs.GetPublicId()}))
|
|
assert.Equal(string(ExpiredToken), token.Status)
|
|
|
|
// Updating the credential store with a token that will expire before the job scheduler can run should return an error
|
|
_, ct = v.CreateToken(t, WithTokenPeriod(time.Second))
|
|
in, err = NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
assert.NoError(err)
|
|
require.NotNil(in)
|
|
|
|
cs, _, err = repo.UpdateCredentialStore(ctx, in, cs.Version+1, []string{"Token"})
|
|
assert.Error(err)
|
|
assert.Nil(cs)
|
|
|
|
// Create 1s token so it expires in vault before the job scheduler can run
|
|
_, ct = v.CreateToken(t, WithTokenPeriod(time.Second))
|
|
in, err = NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
assert.NoError(err)
|
|
require.NotNil(in)
|
|
|
|
// Should return error because token ttl expires before the run job scheduler interval
|
|
cs, err = repo.CreateCredentialStore(ctx, in)
|
|
require.Error(err)
|
|
require.Nil(cs)
|
|
}
|
|
|
|
func TestTokenRenewalJob_NextRunIn(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
|
|
ctx := context.Background()
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
|
|
v := NewTestVaultServer(t)
|
|
|
|
type tokenArgs struct {
|
|
e time.Duration
|
|
s TokenStatus
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
currentTokenExp time.Duration
|
|
tokens []tokenArgs
|
|
want time.Duration
|
|
skipCredStore bool
|
|
}{
|
|
{
|
|
name: "default-duration",
|
|
skipCredStore: true,
|
|
want: defaultNextRunIn,
|
|
},
|
|
{
|
|
name: "1-hour-token",
|
|
currentTokenExp: time.Hour,
|
|
want: 30 * time.Minute,
|
|
},
|
|
{
|
|
name: "2-hour-token",
|
|
currentTokenExp: 2 * time.Hour,
|
|
want: time.Hour,
|
|
},
|
|
{
|
|
name: "multiple-maintaining",
|
|
currentTokenExp: 24 * time.Hour,
|
|
tokens: []tokenArgs{
|
|
{e: 12 * time.Hour, s: MaintainingToken},
|
|
{e: 6 * time.Hour, s: MaintainingToken},
|
|
{e: 8 * time.Hour, s: MaintainingToken},
|
|
},
|
|
// 6 hours is the soonest expiry
|
|
want: 3 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple-maintaining-with-single-overdue-renewal",
|
|
currentTokenExp: 24 * time.Hour,
|
|
tokens: []tokenArgs{
|
|
{e: 24 * time.Hour, s: MaintainingToken},
|
|
{e: 6 * time.Hour, s: MaintainingToken},
|
|
{e: -12 * time.Hour, s: MaintainingToken},
|
|
{e: 10 * time.Hour, s: MaintainingToken},
|
|
},
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "multiple-current-soonest",
|
|
currentTokenExp: 6 * time.Hour,
|
|
tokens: []tokenArgs{
|
|
{e: time.Minute, s: RevokedToken},
|
|
{e: time.Minute, s: ExpiredToken},
|
|
{e: 8 * time.Hour, s: MaintainingToken},
|
|
},
|
|
// 6 hours is the soonest expiry, revoked and expired tokens with sooner
|
|
// expirations should be ignored
|
|
want: 3 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple-maintaining-soonest",
|
|
currentTokenExp: 6 * time.Hour,
|
|
tokens: []tokenArgs{
|
|
{e: time.Minute, s: RevokedToken},
|
|
{e: time.Minute, s: ExpiredToken},
|
|
{e: 4 * time.Hour, s: MaintainingToken},
|
|
},
|
|
// 4 hour maintaining token is the soonest expiry
|
|
// revoked and expired tokens with sooner expirations should be ignored
|
|
want: 2 * time.Hour,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
r, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
assert.NoError(err)
|
|
require.NotNil(r)
|
|
|
|
if !tt.skipCredStore {
|
|
_, token := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
err = RegisterJobs(ctx, sche, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
for _, token := range tt.tokens {
|
|
testVaultToken(t, conn, wrapper, v, cs, token.s, token.e)
|
|
}
|
|
|
|
// inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time
|
|
count, err := rw.Exec(ctx, testUpdateTokenStatusExpirationQuery, []any{CurrentToken, tt.currentTokenExp.Seconds(), cs.outputToken.TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
}
|
|
|
|
got, err := r.NextRunIn(ctx)
|
|
require.NoError(err)
|
|
// Round to time.Minute to account for lost time between creating tokens and determining next run
|
|
assert.Equal(tt.want.Round(time.Minute), got.Round(time.Minute))
|
|
|
|
// Set all tokens to revoked for next test
|
|
_, err = rw.Exec(ctx, "update credential_vault_token set status = 'revoked'", nil)
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewTokenRevocationJob(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
type args struct {
|
|
r db.Reader
|
|
w db.Writer
|
|
kms *kms.Kms
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
options []Option
|
|
wantLimit int
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
}{
|
|
{
|
|
name: "nil reader",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil writer",
|
|
args: args{
|
|
r: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil kms",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "valid-no-options",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
wantLimit: db.DefaultLimit,
|
|
},
|
|
{
|
|
name: "valid-with-limit",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
options: []Option{WithLimit(100)},
|
|
wantLimit: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := newTokenRevocationJob(context.Background(), tt.args.r, tt.args.w, tt.args.kms, tt.options...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Equal(tt.args.r, got.reader)
|
|
assert.Equal(tt.args.w, got.writer)
|
|
assert.Equal(tt.args.kms, got.kms)
|
|
assert.Equal(tt.wantLimit, got.limit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenRevocationJob_RunLimits(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
|
|
ctx := context.Background()
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
|
|
v := NewTestVaultServer(t)
|
|
|
|
count := 5
|
|
tests := []struct {
|
|
name string
|
|
opts []Option
|
|
wantLen int
|
|
}{
|
|
{
|
|
name: "with-no-limits",
|
|
wantLen: count,
|
|
},
|
|
{
|
|
name: "with-negative-limit",
|
|
opts: []Option{WithLimit(-1)},
|
|
wantLen: count,
|
|
},
|
|
{
|
|
name: "with-limit",
|
|
opts: []Option{WithLimit(2)},
|
|
wantLen: 2,
|
|
},
|
|
{
|
|
name: "with-limit-greater-than-count",
|
|
opts: []Option{WithLimit(count + 5)},
|
|
wantLen: count,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
_, token := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
err = RegisterJobs(ctx, sche, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
for i := 0; i < count*3; i++ {
|
|
var status TokenStatus
|
|
// Alternative between token status, the current token as well as the
|
|
// revoked and expired tokens should have no impact on number tokens revoked
|
|
switch i % 3 {
|
|
case 0:
|
|
status = MaintainingToken
|
|
case 1:
|
|
status = RevokedToken
|
|
case 2:
|
|
status = ExpiredToken
|
|
}
|
|
testVaultToken(t, conn, wrapper, v, cs, status, 5*time.Minute)
|
|
}
|
|
|
|
// inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time
|
|
numRows, err := rw.Exec(ctx, testUpdateTokenStatusExpirationQuery, []any{CurrentToken, time.Minute.Seconds(), cs.outputToken.TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, numRows)
|
|
|
|
r, err := newTokenRevocationJob(ctx, rw, rw, kmsCache, tt.opts...)
|
|
require.NoError(err)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(tt.wantLen, r.numTokens)
|
|
|
|
// Set all tokens to revoked for next test
|
|
_, err = rw.Exec(ctx, "update credential_vault_token set status = 'revoked'", nil)
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenRevocationJob_Run(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
|
|
_, ct := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
require.NoError(err)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(err)
|
|
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
r, err := newTokenRevocationJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
err = sche.RegisterJob(ctx, r)
|
|
require.NoError(err)
|
|
|
|
// No tokens should have been revoked since only the current token exists
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(0, r.numProcessed)
|
|
|
|
// Create maintaining tokens with and without credentials
|
|
noCredsToken := testVaultToken(t, conn, wrapper, v, cs, MaintainingToken, 5*time.Minute)
|
|
credsToken := testVaultToken(t, conn, wrapper, v, cs, MaintainingToken, 5*time.Minute)
|
|
revokeToken := testVaultToken(t, conn, wrapper, v, cs, RevokeToken, 5*time.Minute)
|
|
|
|
// inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time
|
|
count, err := rw.Exec(ctx, testUpdateTokenStatusExpirationQuery, []any{CurrentToken, (5 * time.Minute).Seconds(), cs.outputToken.TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// Create cred lib and session for credential
|
|
libPath := path.Join("database", "creds", "opened")
|
|
cl, err := NewCredentialLibrary(cs.PublicId, libPath, WithMethod(MethodGet))
|
|
require.NoError(err)
|
|
cl.PublicId, err = newCredentialLibraryId(ctx)
|
|
require.NoError(err)
|
|
err = rw.Create(ctx, cl)
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId())
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
// Create credential attached to credsToken
|
|
_, cred := testVaultCred(t, conn, v, cl, sess, credsToken, ActiveCredential, 5*time.Minute)
|
|
|
|
// Create fake credential attached to revokeToken
|
|
_, revokeCred := testVaultCred(t, conn, v, cl, sess, revokeToken, ActiveCredential, 5*time.Minute)
|
|
|
|
// Running should revoke noCredsToken and the revokeToken even though it has active
|
|
// credentials it has been marked for revocation
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(2, r.numProcessed)
|
|
|
|
// Verify noCredsToken was revoked in vault
|
|
v.VerifyTokenInvalid(t, string(noCredsToken.GetToken()))
|
|
|
|
// Verify noCredsToken was set to revoked in repo
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{noCredsToken.TokenHmac}))
|
|
assert.Equal(string(RevokedToken), repoToken.Status)
|
|
|
|
// Verify revokeToken was revoked in vault
|
|
v.VerifyTokenInvalid(t, string(revokeToken.GetToken()))
|
|
|
|
// Verify revokeToken was set to revoked in repo
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{revokeToken.TokenHmac}))
|
|
assert.Equal(string(RevokedToken), repoToken.Status)
|
|
|
|
// Verify revokeCred attached to revokeToken were marked as revoked
|
|
lookupCred := allocCredential()
|
|
lookupCred.PublicId = revokeCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(RevokedCredential), lookupCred.Status)
|
|
|
|
// Verify credsToken was not revoked in vault
|
|
lookupToken := v.LookupToken(t, string(credsToken.GetToken()))
|
|
assert.NotNil(lookupToken)
|
|
|
|
// Revoke credential in repo
|
|
query, queryValues := cred.updateStatusQuery(RevokedCredential)
|
|
rows, err := rw.Exec(ctx, query, queryValues)
|
|
assert.Equal(1, rows)
|
|
assert.NoError(err)
|
|
|
|
// Running again should now revoke the credsToken
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(1, r.numProcessed)
|
|
|
|
// Verify credsToken was revoked in vault
|
|
v.VerifyTokenInvalid(t, string(credsToken.GetToken()))
|
|
|
|
// Verify credsToken was set to revoked in repo
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{credsToken.TokenHmac}))
|
|
assert.Equal(string(RevokedToken), repoToken.Status)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// With only the current token remaining no tokens should be revoked
|
|
assert.Equal(0, r.numProcessed)
|
|
}
|
|
|
|
func TestNewCredentialRenewalJob(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
type args struct {
|
|
r db.Reader
|
|
w db.Writer
|
|
kms *kms.Kms
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
options []Option
|
|
wantLimit int
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
}{
|
|
{
|
|
name: "nil reader",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil writer",
|
|
args: args{
|
|
r: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil kms",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "valid-no-options",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
wantLimit: db.DefaultLimit,
|
|
},
|
|
{
|
|
name: "valid-with-limit",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
options: []Option{WithLimit(100)},
|
|
wantLimit: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := newCredentialRenewalJob(context.Background(), tt.args.r, tt.args.w, tt.args.kms, tt.options...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Equal(tt.args.r, got.reader)
|
|
assert.Equal(tt.args.w, got.writer)
|
|
assert.Equal(tt.args.kms, got.kms)
|
|
assert.Equal(tt.wantLimit, got.limit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialRenewalJob_RunLimits(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
_, ct := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
require.NoError(t, err)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(t, err)
|
|
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(t, err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(t, err)
|
|
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(t, err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
cl, err := NewCredentialLibrary(cs.PublicId, libPath, WithMethod(MethodGet))
|
|
require.NoError(t, err)
|
|
cl.PublicId, err = newCredentialLibraryId(ctx)
|
|
require.NoError(t, err)
|
|
err = rw.Create(ctx, cl)
|
|
require.NoError(t, err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId())
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
credsToken := testVaultToken(t, conn, wrapper, v, cs, CurrentToken, 5*time.Minute)
|
|
|
|
count := 10
|
|
tests := []struct {
|
|
name string
|
|
opts []Option
|
|
wantLen int
|
|
}{
|
|
{
|
|
name: "with-no-limits",
|
|
wantLen: count,
|
|
},
|
|
{
|
|
name: "with-negative-limit",
|
|
opts: []Option{WithLimit(-1)},
|
|
wantLen: count,
|
|
},
|
|
{
|
|
name: "with-limit",
|
|
opts: []Option{WithLimit(2)},
|
|
wantLen: 2,
|
|
},
|
|
{
|
|
name: "with-limit-greater-than-count",
|
|
opts: []Option{WithLimit(count + 5)},
|
|
wantLen: count,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
for i := 0; i < count*4; i++ {
|
|
var status CredentialStatus
|
|
// Alternative between cred status, revoke, revoked and expired should have
|
|
// no impact on number creds renewed. Only active creds should be renewed
|
|
switch i % 4 {
|
|
case 0:
|
|
status = ActiveCredential
|
|
case 1:
|
|
status = RevokeCredential
|
|
case 2:
|
|
status = RevokedCredential
|
|
case 3:
|
|
status = ExpiredCredential
|
|
}
|
|
testVaultCred(t, conn, v, cl, sess, credsToken, status, 5*time.Minute)
|
|
}
|
|
|
|
r, err := newCredentialRenewalJob(ctx, rw, rw, kmsCache, tt.opts...)
|
|
require.NoError(err)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(tt.wantLen, r.numCreds)
|
|
|
|
// Set all credentials to revoked for next test
|
|
_, err = rw.Exec(ctx, "update credential_vault_credential set status = 'revoked'", nil)
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialRenewalJob_Run(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
|
|
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
|
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, credStoreIn)
|
|
require.NoError(err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath)
|
|
require.NoError(err)
|
|
cl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(context.Background(), t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
csToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &csToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
|
|
credRenewal, err := newCredentialRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
err = credRenewal.Run(ctx)
|
|
require.NoError(err)
|
|
// No credentials should have been renewed
|
|
assert.Equal(0, credRenewal.numCreds)
|
|
|
|
_, activeCred := testVaultCred(t, conn, v, cl, sess, csToken, ActiveCredential, 5*time.Minute)
|
|
_, revokeCred := testVaultCred(t, conn, v, cl, sess, csToken, RevokeCredential, 5*time.Minute)
|
|
_, revokedCred := testVaultCred(t, conn, v, cl, sess, csToken, RevokedCredential, 5*time.Minute)
|
|
_, expiredCred := testVaultCred(t, conn, v, cl, sess, csToken, ExpiredCredential, 5*time.Minute)
|
|
|
|
secret := v.LookupLease(t, activeCred.ExternalId)
|
|
// Secret should not have a last renewal time
|
|
assert.Nil(secret.Data["last_renewal"])
|
|
|
|
// Sleep to move clock
|
|
time.Sleep(2 * time.Second)
|
|
|
|
err = credRenewal.Run(ctx)
|
|
require.NoError(err)
|
|
// The active credential should have been renewed
|
|
assert.Equal(1, credRenewal.numCreds)
|
|
|
|
// Active credential expiration time should have been updated
|
|
lookupCred := allocCredential()
|
|
lookupCred.PublicId = activeCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Truef(lookupCred.ExpirationTime.AsTime().After(activeCred.ExpirationTime.AsTime()), "expected expiration time to be updated")
|
|
|
|
// Revoke, Revoked and Expired credentials expiration times should not have changed
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = revokeCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(lookupCred.ExpirationTime.AsTime(), revokeCred.ExpirationTime.AsTime())
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = revokedCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(lookupCred.ExpirationTime.AsTime(), revokedCred.ExpirationTime.AsTime())
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = expiredCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(lookupCred.ExpirationTime.AsTime(), expiredCred.ExpirationTime.AsTime())
|
|
|
|
// Active credential should have a last renewal time in Vault
|
|
secret = v.LookupLease(t, activeCred.ExternalId)
|
|
assert.NotNil(secret.Data["last_renewal"])
|
|
|
|
// Revoke, Revoked and Expired credentials should not have a last renewal time
|
|
secret = v.LookupLease(t, revokeCred.ExternalId)
|
|
assert.Nil(secret.Data["last_renewal"])
|
|
secret = v.LookupLease(t, revokedCred.ExternalId)
|
|
assert.Nil(secret.Data["last_renewal"])
|
|
secret = v.LookupLease(t, expiredCred.ExternalId)
|
|
assert.Nil(secret.Data["last_renewal"])
|
|
}
|
|
|
|
func TestCredentialRenewalJob_RunExpired(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
|
|
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
|
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, credStoreIn)
|
|
require.NoError(err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath)
|
|
require.NoError(err)
|
|
cl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
|
|
credRenewal, err := newCredentialRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
_, cred := testVaultCred(t, conn, v, cl, sess, repoToken, ActiveCredential, time.Minute)
|
|
|
|
vc := v.client(t).cl
|
|
// revoke the credential in Vault outside of the job
|
|
assert.NoError(vc.Sys().Revoke(cred.ExternalId))
|
|
|
|
// Credential status should still be active
|
|
lookupCred := allocCredential()
|
|
lookupCred.PublicId = cred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(ActiveCredential), lookupCred.Status)
|
|
|
|
err = credRenewal.Run(ctx)
|
|
require.NoError(err)
|
|
// The active credential should have been processed
|
|
assert.Equal(1, credRenewal.numCreds)
|
|
|
|
// Credential status should have been updated to expired
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = cred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(ExpiredCredential), lookupCred.Status)
|
|
}
|
|
|
|
func TestCredentialRenewalJob_NextRunIn(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
|
|
_, ct := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
require.NoError(t, err)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(t, err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(t, err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(t, err)
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(t, err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath)
|
|
require.NoError(t, err)
|
|
cl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
|
|
require.NoError(t, err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId())
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
type args struct {
|
|
e time.Duration
|
|
s CredentialStatus
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args []args
|
|
want time.Duration
|
|
}{
|
|
{
|
|
name: "default-duration",
|
|
want: defaultNextRunIn,
|
|
},
|
|
{
|
|
name: "1-hour-active-credential",
|
|
args: []args{
|
|
{s: ActiveCredential, e: time.Hour},
|
|
},
|
|
want: 30 * time.Minute,
|
|
},
|
|
{
|
|
name: "1-hour-revoke-credential",
|
|
args: []args{
|
|
{s: RevokeCredential, e: time.Hour},
|
|
},
|
|
want: defaultNextRunIn,
|
|
},
|
|
{
|
|
name: "1-hour-revoked-credential",
|
|
args: []args{
|
|
{s: RevokedCredential, e: time.Hour},
|
|
},
|
|
want: defaultNextRunIn,
|
|
},
|
|
{
|
|
name: "1-hour-expired-credential",
|
|
args: []args{
|
|
{s: ExpiredCredential, e: time.Hour},
|
|
},
|
|
want: defaultNextRunIn,
|
|
},
|
|
{
|
|
name: "multiple-all-active",
|
|
args: []args{
|
|
{s: ActiveCredential, e: 24 * time.Hour},
|
|
{s: ActiveCredential, e: 6 * time.Hour},
|
|
{s: ActiveCredential, e: 10 * time.Hour},
|
|
},
|
|
// 6 hours is the soonest expiration time
|
|
want: 3 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple-mixed",
|
|
args: []args{
|
|
{s: RevokeCredential, e: 2 * time.Hour},
|
|
{s: ActiveCredential, e: 48 * time.Hour},
|
|
{s: ActiveCredential, e: 24 * time.Hour},
|
|
{s: RevokedCredential, e: 6 * time.Hour},
|
|
{s: RevokeCredential, e: 8 * time.Hour},
|
|
{s: ExpiredCredential, e: 10 * time.Hour},
|
|
},
|
|
// 24 hours is the soonest active expiration time
|
|
want: 12 * time.Hour,
|
|
},
|
|
{
|
|
name: "overdue-active-renewal",
|
|
args: []args{
|
|
{s: ActiveCredential, e: -12 * time.Hour},
|
|
},
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "non-active-overdue",
|
|
args: []args{
|
|
{s: ActiveCredential, e: 24 * time.Hour},
|
|
{s: ExpiredCredential, e: -12 * time.Hour},
|
|
{s: RevokeCredential, e: -12 * time.Hour},
|
|
{s: RevokedCredential, e: -12 * time.Hour},
|
|
},
|
|
// The active credential is expiring in 24 hours the non active credentials
|
|
// that are overdue should not impact the query
|
|
want: 12 * time.Hour,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
r, err := newCredentialRenewalJob(ctx, rw, rw, kmsCache)
|
|
assert.NoError(err)
|
|
require.NotNil(r)
|
|
|
|
token := testVaultToken(t, conn, wrapper, v, cs, CurrentToken, 5*time.Minute)
|
|
for _, cred := range tt.args {
|
|
testVaultCred(t, conn, v, cl, sess, token, cred.s, cred.e)
|
|
}
|
|
|
|
got, err := r.NextRunIn(ctx)
|
|
require.NoError(err)
|
|
// Round to time.Minute to account for lost time between creating credentials and determining next run
|
|
assert.Equal(tt.want.Round(time.Minute), got.Round(time.Minute))
|
|
|
|
// Set all credentials to revoked for next test
|
|
_, err = rw.Exec(ctx, "update credential_vault_credential set status = 'revoked'", nil)
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewCredentialRevocationJob(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
type args struct {
|
|
r db.Reader
|
|
w db.Writer
|
|
kms *kms.Kms
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
options []Option
|
|
wantLimit int
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
}{
|
|
{
|
|
name: "nil reader",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil writer",
|
|
args: args{
|
|
r: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil kms",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "valid-no-options",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
wantLimit: db.DefaultLimit,
|
|
},
|
|
{
|
|
name: "valid-with-limit",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
options: []Option{WithLimit(100)},
|
|
wantLimit: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := newCredentialRevocationJob(context.Background(), tt.args.r, tt.args.w, tt.args.kms, tt.options...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Equal(tt.args.r, got.reader)
|
|
assert.Equal(tt.args.w, got.writer)
|
|
assert.Equal(tt.args.kms, got.kms)
|
|
assert.Equal(tt.wantLimit, got.limit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialRevocationJob_RunLimits(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
_, ct := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
require.NoError(t, err)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(t, err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(t, err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(t, err)
|
|
cs, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(t, err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
cl, err := NewCredentialLibrary(cs.PublicId, libPath, WithMethod(MethodGet))
|
|
require.NoError(t, err)
|
|
cl.PublicId, err = newCredentialLibraryId(ctx)
|
|
require.NoError(t, err)
|
|
err = rw.Create(ctx, cl)
|
|
require.NoError(t, err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId())
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
repoToken := allocToken()
|
|
require.NoError(t, rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
|
|
count := 10
|
|
tests := []struct {
|
|
name string
|
|
opts []Option
|
|
wantLen int
|
|
}{
|
|
{
|
|
name: "with-no-limits",
|
|
wantLen: count,
|
|
},
|
|
{
|
|
name: "with-negative-limit",
|
|
opts: []Option{WithLimit(-1)},
|
|
wantLen: count,
|
|
},
|
|
{
|
|
name: "with-limit",
|
|
opts: []Option{WithLimit(2)},
|
|
wantLen: 2,
|
|
},
|
|
{
|
|
name: "with-limit-greater-than-count",
|
|
opts: []Option{WithLimit(count + 5)},
|
|
wantLen: count,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
for i := 0; i < count*4; i++ {
|
|
var status CredentialStatus
|
|
// Alternative between cred status, active, revoked and expired should have
|
|
// no impact on number creds revoked. Only revoke creds should be revoked.
|
|
switch i % 4 {
|
|
case 0:
|
|
status = ActiveCredential
|
|
case 1:
|
|
status = RevokeCredential
|
|
case 2:
|
|
status = RevokedCredential
|
|
case 3:
|
|
status = ExpiredCredential
|
|
}
|
|
testVaultCred(t, conn, v, cl, sess, repoToken, status, 5*time.Minute)
|
|
}
|
|
|
|
r, err := newCredentialRevocationJob(ctx, rw, rw, kmsCache, tt.opts...)
|
|
require.NoError(err)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(tt.wantLen, r.numCreds)
|
|
|
|
// Set all credentials to revoked for next test
|
|
_, err = rw.Exec(ctx, "update credential_vault_credential set status = 'revoked'", nil)
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialRevocationJob_Run(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
testDb := v.MountDatabase(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
|
|
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
|
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, credStoreIn)
|
|
require.NoError(err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath)
|
|
require.NoError(err)
|
|
cl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
|
|
r, err := newCredentialRevocationJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// No credentials should have been revoked
|
|
assert.Equal(0, r.numCreds)
|
|
|
|
secret1, _ := testVaultCred(t, conn, v, cl, sess, repoToken, ActiveCredential, 5*time.Minute)
|
|
revokeSecret, revokeCred := testVaultCred(t, conn, v, cl, sess, repoToken, RevokeCredential, 5*time.Minute)
|
|
secret2, _ := testVaultCred(t, conn, v, cl, sess, repoToken, RevokedCredential, 5*time.Minute)
|
|
secret3, _ := testVaultCred(t, conn, v, cl, sess, repoToken, ExpiredCredential, 5*time.Minute)
|
|
|
|
// Verify the revokeCred has a status of revoke
|
|
lookupCred := allocCredential()
|
|
lookupCred.PublicId = revokeCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(RevokeCredential), lookupCred.Status)
|
|
|
|
// Verify revokeCred is valid in testDb
|
|
assert.NoError(testDb.ValidateCredential(t, revokeSecret))
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// The revoke credential should have been revoked
|
|
assert.Equal(1, r.numCreds)
|
|
|
|
// revokeCred should now have a status of revoked
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = revokeCred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(RevokedCredential), lookupCred.Status)
|
|
|
|
// revokeCred should no longer be valid in test database
|
|
assert.Error(testDb.ValidateCredential(t, revokeSecret))
|
|
|
|
// Other creds should still be valid in test database
|
|
assert.NoError(testDb.ValidateCredential(t, secret1))
|
|
assert.NoError(testDb.ValidateCredential(t, secret2))
|
|
assert.NoError(testDb.ValidateCredential(t, secret3))
|
|
}
|
|
|
|
func TestCredentialRevocationJob_RunDeleted(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
testDb := v.MountDatabase(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
|
|
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
|
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, credStoreIn)
|
|
require.NoError(err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath)
|
|
require.NoError(err)
|
|
cl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
|
|
r, err := newCredentialRevocationJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
secret, cred := testVaultCred(t, conn, v, cl, sess, repoToken, ActiveCredential, 5*time.Hour)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// No credentials should have been revoked as expiration is 5 hours from now
|
|
assert.Equal(0, r.numCreds)
|
|
|
|
// Deleting the library should set the cred library_id to null, but not revoke the cred
|
|
count, err := rw.Delete(ctx, cl)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// No credentials should have been revoked
|
|
assert.Equal(0, r.numCreds)
|
|
|
|
// Verify the cred has a status of active with an empty libraryId
|
|
lookupCred := allocCredential()
|
|
lookupCred.PublicId = cred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(ActiveCredential), lookupCred.Status)
|
|
assert.Empty(lookupCred.LibraryId)
|
|
|
|
// secret should still be valid in test database
|
|
assert.NoError(testDb.ValidateCredential(t, secret))
|
|
|
|
// Deleting the session should set the cred session_id to null and schedule cred for revocation
|
|
count, err = rw.Delete(ctx, sess)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// cred should now have a status of revoke and empty sessionId
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = cred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Empty(lookupCred.SessionId)
|
|
assert.Equal(string(RevokeCredential), lookupCred.Status)
|
|
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
// The revoke credential should have been revoked
|
|
assert.Equal(1, r.numCreds)
|
|
|
|
// cred should now have a status of revoked
|
|
lookupCred = allocCredential()
|
|
lookupCred.PublicId = cred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(RevokedCredential), lookupCred.Status)
|
|
|
|
// secret should no longer be valid in test database
|
|
assert.Error(testDb.ValidateCredential(t, secret))
|
|
}
|
|
|
|
func TestNewCredentialStoreCleanupJob(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
|
|
type args struct {
|
|
r db.Reader
|
|
w db.Writer
|
|
kms *kms.Kms
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
options []Option
|
|
wantLimit int
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
}{
|
|
{
|
|
name: "nil reader",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil writer",
|
|
args: args{
|
|
r: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "nil kms",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
},
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "valid-no-options",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
wantLimit: db.DefaultLimit,
|
|
},
|
|
{
|
|
name: "valid-with-limit",
|
|
args: args{
|
|
r: rw,
|
|
w: rw,
|
|
kms: kmsCache,
|
|
},
|
|
options: []Option{WithLimit(100)},
|
|
wantLimit: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := newCredentialStoreCleanupJob(context.Background(), tt.args.r, tt.args.w, tt.args.kms, tt.options...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Equal(tt.args.r, got.reader)
|
|
assert.Equal(tt.args.w, got.writer)
|
|
assert.Equal(tt.args.kms, got.kms)
|
|
assert.Equal(tt.wantLimit, got.limit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialStoreCleanupJob_Run(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
v := NewTestVaultServer(t)
|
|
|
|
_, ct := v.CreateToken(t)
|
|
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
require.NoError(err)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(context.Background(), j)
|
|
require.NoError(err)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
cs1, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
_, ct = v.CreateToken(t)
|
|
in, err = NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
|
require.NoError(err)
|
|
cs2, err := repo.CreateCredentialStore(ctx, in)
|
|
require.NoError(err)
|
|
|
|
// Get token hmac for verifications below
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "store_id = ?", []any{cs1.PublicId}))
|
|
cs1TokenHmac := repoToken.TokenHmac
|
|
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "store_id = ?", []any{cs2.PublicId}))
|
|
cs2TokenHmac := repoToken.TokenHmac
|
|
|
|
// create second token on cs2
|
|
secondToken := testVaultToken(t, conn, wrapper, v, cs2, MaintainingToken, time.Hour)
|
|
|
|
r, err := newCredentialStoreCleanupJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
|
|
err = sche.RegisterJob(ctx, r)
|
|
require.NoError(err)
|
|
|
|
// No credential stores should have been cleaned up
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(0, r.numStores)
|
|
|
|
// Register token revocation job needed for delete
|
|
j1, err := newTokenRevocationJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j1)
|
|
require.NoError(err)
|
|
|
|
// Soft delete both credential stores
|
|
count, err := repo.DeleteCredentialStore(ctx, cs1.PublicId)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
count, err = repo.DeleteCredentialStore(ctx, cs2.PublicId)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// Verify tokens have been set to revoke
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "store_id = ?", []any{cs1.PublicId}))
|
|
assert.Equal(string(RevokeToken), repoToken.Status)
|
|
|
|
repoToken = allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "store_id = ?", []any{cs2.PublicId}))
|
|
assert.Equal(string(RevokeToken), repoToken.Status)
|
|
|
|
// Both soft deleted credential stores should not be cleaned up yet
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(0, r.numStores)
|
|
|
|
// Update cs1 token to be marked as revoked
|
|
count, err = rw.Exec(ctx, updateTokenStatusQuery, []any{RevokedToken, cs1TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// cs1 should be deleted
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(1, r.numStores)
|
|
|
|
// Lookup of cs1 and its token should fail
|
|
agg := allocListLookupStore()
|
|
agg.PublicId = cs1.PublicId
|
|
err = rw.LookupByPublicId(ctx, agg)
|
|
require.Error(err)
|
|
assert.True(errors.IsNotFoundError(err))
|
|
repoToken = allocToken()
|
|
err = rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs1TokenHmac})
|
|
require.Error(err)
|
|
assert.True(errors.IsNotFoundError(err))
|
|
|
|
// Lookup of cs2 and its token should not error
|
|
_, err = repo.LookupCredentialStore(ctx, cs2.PublicId)
|
|
require.NoError(err)
|
|
repoToken = allocToken()
|
|
err = rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs2TokenHmac})
|
|
require.NoError(err)
|
|
|
|
// Update cs2 token expiration time
|
|
count, err = rw.Exec(ctx, "update credential_vault_token set expiration_time = now() where token_hmac = ?;", []any{cs2TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// cs2 still has a second token not yet revoked/expired
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(0, r.numStores)
|
|
|
|
// Lookup of cs2 and its token should not error
|
|
_, err = repo.LookupCredentialStore(ctx, cs2.PublicId)
|
|
require.NoError(err)
|
|
repoToken = allocToken()
|
|
err = rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs2TokenHmac})
|
|
require.NoError(err)
|
|
|
|
// set secondToken with an expired status
|
|
count, err = rw.Exec(ctx, updateTokenStatusQuery, []any{ExpiredToken, secondToken.TokenHmac})
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// With no un-expired or un-revoked tokens cs2 should now be deleted
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(1, r.numStores)
|
|
|
|
// Lookup of cs2 and its token should fail
|
|
agg = allocListLookupStore()
|
|
agg.PublicId = cs2.PublicId
|
|
err = rw.LookupByPublicId(ctx, agg)
|
|
require.Error(err)
|
|
assert.True(errors.IsNotFoundError(err))
|
|
repoToken = allocToken()
|
|
err = rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs2TokenHmac})
|
|
require.Error(err)
|
|
assert.True(errors.IsNotFoundError(err))
|
|
err = rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{secondToken.TokenHmac})
|
|
require.Error(err)
|
|
assert.True(errors.IsNotFoundError(err))
|
|
}
|
|
|
|
func TestNewCredentialCleanupJob(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
|
|
type args struct {
|
|
w db.Writer
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
options []Option
|
|
wantLimit int
|
|
wantErr bool
|
|
wantErrCode errors.Code
|
|
}{
|
|
{
|
|
name: "nil writer",
|
|
wantErr: true,
|
|
wantErrCode: errors.InvalidParameter,
|
|
},
|
|
{
|
|
name: "valid",
|
|
args: args{
|
|
w: rw,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
got, err := newCredentialCleanupJob(context.Background(), tt.args.w)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
require.NotNil(got)
|
|
assert.Equal(tt.args.w, got.writer)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVaultJobsCorrelationId(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrapper := db.TestWrapper(t)
|
|
rw := db.New(conn)
|
|
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId, "http://vault", "vault-token", "accessor")
|
|
lib := TestCredentialLibraries(t, conn, wrapper, cs.PublicId, 1)[0]
|
|
token := cs.Token()
|
|
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
|
|
// Create session with known correlationId
|
|
corId, err := uuid.GenerateUUID()
|
|
require.NoError(err)
|
|
|
|
composedOf := session.TestSessionParams(t, conn, wrapper, iamRepo)
|
|
composedOf.CorrelationId = corId
|
|
|
|
sess := session.TestSession(t, conn, wrapper, composedOf)
|
|
got, err := newCredential(ctx, lib.GetPublicId(), "some/vault/credential", token.GetTokenHmac(), time.Minute)
|
|
require.NoError(err)
|
|
id, err := newCredentialId(ctx)
|
|
assert.NoError(err)
|
|
got.PublicId = id
|
|
query, queryValues := insertQuery(got, sess.PublicId)
|
|
require.NoError(err)
|
|
rows, err := rw.Exec(ctx, query, queryValues)
|
|
assert.Equal(1, rows)
|
|
assert.NoError(err)
|
|
|
|
cred := &privateCredential{PublicId: id}
|
|
err = rw.LookupById(ctx, cred)
|
|
require.NoError(err)
|
|
require.NotEmpty(cred.SessionCorrelationId)
|
|
assert.Equal(corId, cred.SessionCorrelationId)
|
|
}
|
|
|
|
func TestCredentialCleanupJob_Run(t *testing.T) {
|
|
// t.Parallel() - this was causing test failures, investigate before un-commenting
|
|
ctx := context.Background()
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
v := NewTestVaultServer(t, WithDockerNetwork(true))
|
|
v.MountDatabase(t)
|
|
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
kmsCache := kms.TestKms(t, conn, wrapper)
|
|
sche := scheduler.TestScheduler(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kmsCache, sche)
|
|
require.NoError(err)
|
|
|
|
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
|
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
|
require.NoError(err)
|
|
j, err := newTokenRenewalJob(ctx, rw, rw, kmsCache)
|
|
require.NoError(err)
|
|
err = sche.RegisterJob(ctx, j)
|
|
require.NoError(err)
|
|
cs, err := repo.CreateCredentialStore(ctx, credStoreIn)
|
|
require.NoError(err)
|
|
|
|
libPath := path.Join("database", "creds", "opened")
|
|
libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath)
|
|
require.NoError(err)
|
|
cl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
|
|
require.NoError(err)
|
|
|
|
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
|
uId := at.GetIamUserId()
|
|
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
|
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
|
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
|
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
|
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
|
sess1 := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
sess2 := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
|
UserId: uId,
|
|
HostId: h.GetPublicId(),
|
|
TargetId: tar.GetPublicId(),
|
|
HostSetId: hs.GetPublicId(),
|
|
AuthTokenId: at.GetPublicId(),
|
|
ProjectId: prj.GetPublicId(),
|
|
Endpoint: "tcp://127.0.0.1:22",
|
|
})
|
|
|
|
repoToken := allocToken()
|
|
require.NoError(rw.LookupWhere(ctx, &repoToken, "token_hmac = ?", []any{cs.outputToken.TokenHmac}))
|
|
|
|
r, err := newCredentialCleanupJob(ctx, rw)
|
|
require.NoError(err)
|
|
|
|
_, sess1Cred1 := testVaultCred(t, conn, v, cl, sess1, repoToken, ActiveCredential, 5*time.Hour)
|
|
_, sess1Cred2 := testVaultCred(t, conn, v, cl, sess1, repoToken, ActiveCredential, 5*time.Hour)
|
|
_, sess1Cred3 := testVaultCred(t, conn, v, cl, sess1, repoToken, ActiveCredential, 5*time.Hour)
|
|
_, sess2Cred := testVaultCred(t, conn, v, cl, sess2, repoToken, ActiveCredential, 5*time.Hour)
|
|
|
|
// No credentials should be cleaned up
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(0, r.numCreds)
|
|
|
|
// Delete sess1
|
|
count, err := rw.Delete(ctx, sess1)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// Credentials are still in the revoke state so none should be deleted yet
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(0, r.numCreds)
|
|
|
|
query, queryArgs := sess1Cred1.updateStatusQuery(RevokedCredential)
|
|
count, err = rw.Exec(ctx, query, queryArgs)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
query, queryArgs = sess1Cred2.updateStatusQuery(ExpiredCredential)
|
|
count, err = rw.Exec(ctx, query, queryArgs)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
query, queryArgs = sess1Cred3.updateStatusQuery(UnknownCredentialStatus)
|
|
count, err = rw.Exec(ctx, query, queryArgs)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
query, queryArgs = sess2Cred.updateStatusQuery(RevokedCredential)
|
|
count, err = rw.Exec(ctx, query, queryArgs)
|
|
require.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
// Only the three credentials associated with the deleted session should be deleted
|
|
err = r.Run(ctx)
|
|
require.NoError(err)
|
|
assert.Equal(3, r.numCreds)
|
|
|
|
// Session 1 creds should no longer exist
|
|
lookupCred := allocCredential()
|
|
lookupCred.PublicId = sess1Cred1.PublicId
|
|
require.Error(rw.LookupById(ctx, lookupCred))
|
|
lookupCred.PublicId = sess1Cred2.PublicId
|
|
require.Error(rw.LookupById(ctx, lookupCred))
|
|
lookupCred.PublicId = sess1Cred3.PublicId
|
|
require.Error(rw.LookupById(ctx, lookupCred))
|
|
|
|
// Session 2 creds should still exist but be revoked
|
|
lookupCred.PublicId = sess2Cred.PublicId
|
|
require.NoError(rw.LookupById(ctx, lookupCred))
|
|
assert.Equal(string(RevokedCredential), lookupCred.Status)
|
|
}
|
|
|
|
func TestVaultJobsWorkerFilters(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
wrapper := db.TestWrapper(t)
|
|
rw := db.New(conn)
|
|
|
|
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
|
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId, "http://vault", "vault-token", "accessor", WithWorkerFilter("true == true"))
|
|
lib := TestCredentialLibraries(t, conn, wrapper, cs.PublicId, 1)[0]
|
|
token := cs.Token()
|
|
|
|
csNoFilter := TestCredentialStore(t, conn, wrapper, prj.PublicId, "http://vault", "vault-token-no-filter", "accessor")
|
|
libNoFilter := TestCredentialLibraries(t, conn, wrapper, csNoFilter.PublicId, 1)[0]
|
|
tokenNoFilter := csNoFilter.Token()
|
|
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
session := session.TestDefaultSession(t, conn, wrapper, iamRepo)
|
|
|
|
// Create credential with filter
|
|
got, err := newCredential(ctx, lib.GetPublicId(), "some/vault/credential", token.GetTokenHmac(), time.Minute)
|
|
require.NoError(err)
|
|
id, err := newCredentialId(ctx)
|
|
assert.NoError(err)
|
|
got.PublicId = id
|
|
query, queryValues := insertQuery(got, session.PublicId)
|
|
require.NoError(err)
|
|
rows, err := rw.Exec(ctx, query, queryValues)
|
|
assert.Equal(1, rows)
|
|
assert.NoError(err)
|
|
|
|
// Validate renew/revoke token query includes worker filter
|
|
ps := &renewRevokeStore{Store: allocClientStore()}
|
|
ps.Store.PublicId = cs.PublicId
|
|
err = rw.LookupById(ctx, ps)
|
|
require.NoError(err)
|
|
assert.Equal("true == true", ps.Store.WorkerFilter)
|
|
|
|
// Validate renew/revoke credential query includes worker filter
|
|
cred := &privateCredential{PublicId: id}
|
|
err = rw.LookupById(ctx, cred)
|
|
require.NoError(err)
|
|
assert.Equal("true == true", cred.WorkerFilter)
|
|
|
|
// Create credential without filter
|
|
got, err = newCredential(ctx, libNoFilter.GetPublicId(), "some/vault/credential", tokenNoFilter.GetTokenHmac(), time.Minute)
|
|
require.NoError(err)
|
|
idNoFilter, err := newCredentialId(ctx)
|
|
assert.NoError(err)
|
|
got.PublicId = idNoFilter
|
|
query, queryValues = insertQuery(got, session.PublicId)
|
|
require.NoError(err)
|
|
rows, err = rw.Exec(ctx, query, queryValues)
|
|
assert.Equal(1, rows)
|
|
assert.NoError(err)
|
|
|
|
// Validate renew/revoke token query does not include worker filter
|
|
ps = &renewRevokeStore{Store: allocClientStore()}
|
|
ps.Store.PublicId = csNoFilter.PublicId
|
|
err = rw.LookupById(ctx, ps)
|
|
require.NoError(err)
|
|
assert.Empty(ps.Store.WorkerFilter)
|
|
|
|
// Validate renew/revoke credential query does not include worker filter
|
|
cred = &privateCredential{PublicId: idNoFilter}
|
|
err = rw.LookupById(ctx, cred)
|
|
require.NoError(err)
|
|
assert.Empty(cred.WorkerFilter)
|
|
}
|