internal/credential/vault: add pagination logic
This commit is contained in:
1 parent
fcb47f3772
commit
1337a57674
8 files changed
+743
-138
No files matched your search
@@ -1,14 +1,16 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// Copyright (c) HashiCorp Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault/store"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
@@ -132,3 +134,89 @@ func (l *CredentialLibrary) CredentialType() globals.CredentialType {
|
||||
}
|
||||
|
||||
var _ credential.Library = (*CredentialLibrary)(nil)
|
||||
|
||||
// listCredentialLibraryResult represents the result of the
|
||||
// list queries used to list all credential libraries.
|
||||
type listCredentialLibraryResult struct {
|
||||
PublicId string
|
||||
StoreId string
|
||||
ProjectId string
|
||||
Name string
|
||||
Description string
|
||||
VaultPath string
|
||||
CredentialType string
|
||||
HttpMethod string
|
||||
HttpRequestBody string
|
||||
Username string
|
||||
KeyType string
|
||||
Ttl string
|
||||
KeyId string
|
||||
CriticalOptions string
|
||||
Extensions string
|
||||
AdditionalValidPrincipals string
|
||||
CreateTime *timestamp.Timestamp
|
||||
UpdateTime *timestamp.Timestamp
|
||||
Version int
|
||||
KeyBits int
|
||||
Type string
|
||||
}
|
||||
|
||||
func (l *listCredentialLibraryResult) toLibrary(ctx context.Context) (credential.Library, error) {
|
||||
const op = "vault.(*listCredentialLibraryResult).toLibrary"
|
||||
switch l.Type {
|
||||
case "generic":
|
||||
cl := &CredentialLibrary{
|
||||
CredentialLibrary: &store.CredentialLibrary{
|
||||
PublicId: l.PublicId,
|
||||
StoreId: l.StoreId,
|
||||
Name: l.Name,
|
||||
Description: l.Description,
|
||||
CreateTime: l.CreateTime,
|
||||
UpdateTime: l.UpdateTime,
|
||||
Version: uint32(l.Version),
|
||||
VaultPath: l.VaultPath,
|
||||
CredentialType: l.CredentialType,
|
||||
HttpMethod: l.HttpMethod,
|
||||
},
|
||||
}
|
||||
// Assign byte slices only if the string isn't empty
|
||||
if l.HttpRequestBody != "" {
|
||||
cl.HttpRequestBody = []byte(l.HttpRequestBody)
|
||||
}
|
||||
return cl, nil
|
||||
case "ssh":
|
||||
return &SSHCertificateCredentialLibrary{
|
||||
SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{
|
||||
PublicId: l.PublicId,
|
||||
StoreId: l.StoreId,
|
||||
Name: l.Name,
|
||||
Description: l.Description,
|
||||
CreateTime: l.CreateTime,
|
||||
UpdateTime: l.UpdateTime,
|
||||
Version: uint32(l.Version),
|
||||
VaultPath: l.VaultPath,
|
||||
CredentialType: l.CredentialType,
|
||||
Username: l.Username,
|
||||
KeyType: l.KeyType,
|
||||
KeyBits: uint32(l.KeyBits),
|
||||
Ttl: l.Ttl,
|
||||
KeyId: l.KeyId,
|
||||
CriticalOptions: l.CriticalOptions,
|
||||
Extensions: l.Extensions,
|
||||
AdditionalValidPrincipals: l.AdditionalValidPrincipals,
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unexpected vault credential library type %s returned", l.Type))
|
||||
}
|
||||
}
|
||||
|
||||
type deletedCredentialLibrary struct {
|
||||
PublicId string `gorm:"primary_key"`
|
||||
DeleteTime *timestamp.Timestamp
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name
|
||||
func (s *deletedCredentialLibrary) TableName() string {
|
||||
return "credential_vault_library_deleted"
|
||||
}
|
||||
@@ -188,5 +188,298 @@ delete_time is not null
|
||||
delete from credential_vault_credential
|
||||
where session_id is null
|
||||
and status not in ('active', 'revoke')
|
||||
`
|
||||
|
||||
estimateCountCredentialLibraries = `
|
||||
select sum(reltuples::bigint) as estimate
|
||||
from pg_class
|
||||
where oid in (
|
||||
'credential_vault_library'::regclass,
|
||||
'credential_vault_ssh_cert_library'::regclass
|
||||
)
|
||||
`
|
||||
|
||||
listLibrariesTemplate = `
|
||||
with libraries as (
|
||||
select public_id
|
||||
from credential_library
|
||||
where store_id = @store_id
|
||||
order by create_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
generic_libs as (
|
||||
select *
|
||||
from credential_vault_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
ssh_cert_libs as (
|
||||
select *
|
||||
from credential_vault_ssh_cert_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
final as (
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
http_method,
|
||||
http_request_body,
|
||||
null as username, -- Add to make union uniform
|
||||
null as key_type, -- Add to make union uniform
|
||||
null as key_bits, -- Add to make union uniform
|
||||
null as ttl, -- Add to make union uniform
|
||||
null as key_id, -- Add to make union uniform
|
||||
null as critical_options, -- Add to make union uniform
|
||||
null as extensions, -- Add to make union uniform
|
||||
null as additional_valid_principals, -- Add to make union uniform
|
||||
'generic' as type
|
||||
from generic_libs
|
||||
union
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
null as http_method, -- Add to make union uniform
|
||||
null as http_request_body, -- Add to make union uniform
|
||||
username,
|
||||
key_type,
|
||||
key_bits,
|
||||
ttl,
|
||||
key_id,
|
||||
critical_options,
|
||||
extensions,
|
||||
additional_valid_principals,
|
||||
'ssh' as type
|
||||
from ssh_cert_libs
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by create_time desc, public_id asc;
|
||||
`
|
||||
|
||||
listLibrariesPageTemplate = `
|
||||
with libraries as (
|
||||
select public_id
|
||||
from credential_library
|
||||
where store_id = @store_id
|
||||
and (create_time, public_id) < (@last_item_create_time, @last_item_id)
|
||||
order by create_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
generic_libs as (
|
||||
select *
|
||||
from credential_vault_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
ssh_cert_libs as (
|
||||
select *
|
||||
from credential_vault_ssh_cert_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
final as (
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
http_method,
|
||||
http_request_body,
|
||||
null as username, -- Add to make union uniform
|
||||
null as key_type, -- Add to make union uniform
|
||||
null as key_bits, -- Add to make union uniform
|
||||
null as ttl, -- Add to make union uniform
|
||||
null as key_id, -- Add to make union uniform
|
||||
null as critical_options, -- Add to make union uniform
|
||||
null as extensions, -- Add to make union uniform
|
||||
null as additional_valid_principals, -- Add to make union uniform
|
||||
'generic' as type
|
||||
from generic_libs
|
||||
union
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
null as http_method, -- Add to make union uniform
|
||||
null as http_request_body, -- Add to make union uniform
|
||||
username,
|
||||
key_type,
|
||||
key_bits,
|
||||
ttl,
|
||||
key_id,
|
||||
critical_options,
|
||||
extensions,
|
||||
additional_valid_principals,
|
||||
'ssh' as type
|
||||
from ssh_cert_libs
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by create_time desc, public_id asc;
|
||||
`
|
||||
|
||||
listLibrariesRefreshTemplate = `
|
||||
with libraries as (
|
||||
select public_id
|
||||
from credential_library
|
||||
where store_id = @store_id
|
||||
and update_time > @updated_after_time
|
||||
order by update_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
generic_libs as (
|
||||
select *
|
||||
from credential_vault_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
ssh_cert_libs as (
|
||||
select *
|
||||
from credential_vault_ssh_cert_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
final as (
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
http_method,
|
||||
http_request_body,
|
||||
null as username, -- Add to make union uniform
|
||||
null as key_type, -- Add to make union uniform
|
||||
null as key_bits, -- Add to make union uniform
|
||||
null as ttl, -- Add to make union uniform
|
||||
null as key_id, -- Add to make union uniform
|
||||
null as critical_options, -- Add to make union uniform
|
||||
null as extensions, -- Add to make union uniform
|
||||
null as additional_valid_principals, -- Add to make union uniform
|
||||
'generic' as type
|
||||
from generic_libs
|
||||
union
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
null as http_method, -- Add to make union uniform
|
||||
null as http_request_body, -- Add to make union uniform
|
||||
username,
|
||||
key_type,
|
||||
key_bits,
|
||||
ttl,
|
||||
key_id,
|
||||
critical_options,
|
||||
extensions,
|
||||
additional_valid_principals,
|
||||
'ssh' as type
|
||||
from ssh_cert_libs
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by update_time desc, public_id asc;
|
||||
`
|
||||
|
||||
listLibrariesRefreshPageTemplate = `
|
||||
with libraries as (
|
||||
select public_id
|
||||
from credential_library
|
||||
where store_id = @store_id
|
||||
and update_time > @updated_after_time
|
||||
and (update_time, public_id) < (@last_item_update_time, @last_item_id)
|
||||
order by update_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
generic_libs as (
|
||||
select *
|
||||
from credential_vault_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
ssh_cert_libs as (
|
||||
select *
|
||||
from credential_vault_ssh_cert_library
|
||||
where public_id in (select public_id from libraries)
|
||||
),
|
||||
final as (
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
http_method,
|
||||
http_request_body,
|
||||
null as username, -- Add to make union uniform
|
||||
null as key_type, -- Add to make union uniform
|
||||
null as key_bits, -- Add to make union uniform
|
||||
null as ttl, -- Add to make union uniform
|
||||
null as key_id, -- Add to make union uniform
|
||||
null as critical_options, -- Add to make union uniform
|
||||
null as extensions, -- Add to make union uniform
|
||||
null as additional_valid_principals, -- Add to make union uniform
|
||||
'generic' as type
|
||||
from generic_libs
|
||||
union
|
||||
select public_id,
|
||||
store_id,
|
||||
project_id,
|
||||
name,
|
||||
description,
|
||||
create_time,
|
||||
update_time,
|
||||
version,
|
||||
vault_path,
|
||||
credential_type,
|
||||
null as http_method, -- Add to make union uniform
|
||||
null as http_request_body, -- Add to make union uniform
|
||||
username,
|
||||
key_type,
|
||||
key_bits,
|
||||
ttl,
|
||||
key_id,
|
||||
critical_options,
|
||||
extensions,
|
||||
additional_valid_principals,
|
||||
'ssh' as type
|
||||
from ssh_cert_libs
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by update_time desc, public_id asc;
|
||||
`
|
||||
)
|
||||
@@ -5,10 +5,14 @@ package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
@@ -452,23 +456,173 @@ func (r *Repository) DeleteCredentialLibrary(ctx context.Context, projectId stri
|
||||
return rowsDeleted, nil
|
||||
}
|
||||
|
||||
// ListCredentialLibraries returns a slice of CredentialLibraries for the
|
||||
// storeId. WithLimit is the only option supported.
|
||||
func (r *Repository) ListCredentialLibraries(ctx context.Context, storeId string, opt ...Option) ([]*CredentialLibrary, error) {
|
||||
const op = "vault.(Repository).ListCredentialLibraries"
|
||||
// ListLibraries returns a slice of CredentialLibraries for the
|
||||
// storeId. Supports the following options:
|
||||
// - credential.WithLimit
|
||||
// - credential.WithStartPageAfterItem
|
||||
func (r *Repository) ListLibraries(ctx context.Context, storeId string, opt ...credential.Option) ([]credential.Library, time.Time, error) {
|
||||
const op = "vault.(Repository).ListLibraries"
|
||||
if storeId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no storeId")
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing store id")
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
var libs []*CredentialLibrary
|
||||
err := r.reader.SearchWhere(ctx, &libs, "store_id = ?", []any{storeId}, db.WithLimit(limit))
|
||||
opts, err := credential.GetOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
return nil, time.Time{}, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return libs, nil
|
||||
limit := r.defaultLimit
|
||||
if opts.WithLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.WithLimit
|
||||
}
|
||||
query := fmt.Sprintf(listLibrariesTemplate, limit)
|
||||
args := []any{sql.Named("store_id", storeId)}
|
||||
if opts.WithStartPageAfterItem != nil {
|
||||
query = fmt.Sprintf(listLibrariesPageTemplate, limit)
|
||||
args = append(args,
|
||||
sql.Named("last_item_create_time", opts.WithStartPageAfterItem.GetCreateTime()),
|
||||
sql.Named("last_item_id", opts.WithStartPageAfterItem.GetPublicId()),
|
||||
)
|
||||
}
|
||||
|
||||
libs, transactionTimestamp, err := r.queryLibraries(ctx, query, args)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// Sort final slice to ensure correct ordering.
|
||||
// We sort by create time descending (most recently created first).
|
||||
slices.SortFunc(libs, func(i, j credential.Library) int {
|
||||
return j.GetCreateTime().AsTime().Compare(i.GetCreateTime().AsTime())
|
||||
})
|
||||
|
||||
return libs, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
// ListLibrariesRefresh returns a slice of credential libraries
|
||||
// for the store ID. Supports the following options:
|
||||
// - credential.WithLimit
|
||||
// - credential.WithStartPageAfterItem
|
||||
func (r *Repository) ListLibrariesRefresh(ctx context.Context, storeId string, updatedAfter time.Time, opt ...credential.Option) ([]credential.Library, time.Time, error) {
|
||||
const op = "vault.(Repository).ListLibrariesRefresh"
|
||||
switch {
|
||||
case storeId == "":
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing credential store ID")
|
||||
case updatedAfter.IsZero():
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing updated after time")
|
||||
}
|
||||
opts, err := credential.GetOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
limit := r.defaultLimit
|
||||
if opts.WithLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.WithLimit
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(listLibrariesRefreshTemplate, limit)
|
||||
args := []any{
|
||||
sql.Named("store_id", storeId),
|
||||
sql.Named("updated_after_time", timestamp.New(updatedAfter)),
|
||||
}
|
||||
if opts.WithStartPageAfterItem != nil {
|
||||
query = fmt.Sprintf(listLibrariesRefreshPageTemplate, limit)
|
||||
args = append(args,
|
||||
sql.Named("last_item_update_time", opts.WithStartPageAfterItem.GetUpdateTime()),
|
||||
sql.Named("last_item_id", opts.WithStartPageAfterItem.GetPublicId()),
|
||||
)
|
||||
}
|
||||
|
||||
libs, transactionTimestamp, err := r.queryLibraries(ctx, query, args)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// Sort final slice to ensure correct ordering.
|
||||
// We sort by update time descending (most recently updated first).
|
||||
slices.SortFunc(libs, func(i, j credential.Library) int {
|
||||
return j.GetUpdateTime().AsTime().Compare(i.GetUpdateTime().AsTime())
|
||||
})
|
||||
|
||||
return libs, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
func (r *Repository) queryLibraries(ctx context.Context, query string, args []any) ([]credential.Library, time.Time, error) {
|
||||
const op = "vault.(Repository).queryLibraries"
|
||||
|
||||
var libs []credential.Library
|
||||
var transactionTimestamp time.Time
|
||||
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(rd db.Reader, w db.Writer) error {
|
||||
rows, err := rd.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
var results []listCredentialLibraryResult
|
||||
for rows.Next() {
|
||||
if err := rd.ScanRows(ctx, rows, &results); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
for _, result := range results {
|
||||
lib, err := result.toLibrary(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
libs = append(libs, lib)
|
||||
}
|
||||
transactionTimestamp, err = rd.Now(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
|
||||
return libs, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
// EstimatedLibraryCount returns an estimate of the number of Vault credential libraries
|
||||
func (r *Repository) EstimatedLibraryCount(ctx context.Context) (int, error) {
|
||||
const op = "vault.(Repository).EstimatedLibraryCount"
|
||||
rows, err := r.reader.Query(ctx, estimateCountCredentialLibraries, nil)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total vault credential libraries"))
|
||||
}
|
||||
var count int
|
||||
for rows.Next() {
|
||||
if err := r.reader.ScanRows(ctx, rows, &count); err != nil {
|
||||
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total vault credential libraries"))
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ListDeletedLibraryIds lists the public IDs of any credential libraries deleted since the timestamp provided.
|
||||
func (r *Repository) ListDeletedLibraryIds(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
const op = "vault.(Repository).ListDeletedLibraryIds"
|
||||
var credentialLibraryIds []string
|
||||
var transactionTimestamp time.Time
|
||||
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
|
||||
var deletedCredentialLibraries []*deletedCredentialLibrary
|
||||
if err := r.SearchWhere(ctx, &deletedCredentialLibraries, "delete_time >= ?", []any{since}); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted credential libraries"))
|
||||
}
|
||||
for _, cl := range deletedCredentialLibraries {
|
||||
credentialLibraryIds = append(credentialLibraryIds, cl.PublicId)
|
||||
}
|
||||
var deletedSshCredentialLibraries []*deletedSSHCertificateCredentialLibrary
|
||||
if err := r.SearchWhere(ctx, &deletedSshCredentialLibraries, "delete_time >= ?", []any{since}); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted SSH certificate credential libraries"))
|
||||
}
|
||||
for _, cl := range deletedSshCredentialLibraries {
|
||||
credentialLibraryIds = append(credentialLibraryIds, cl.PublicId)
|
||||
}
|
||||
var err error
|
||||
transactionTimestamp, err = r.Now(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query transaction timestamp"))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
return credentialLibraryIds, transactionTimestamp, nil
|
||||
}
|
||||
@@ -8,10 +8,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbassert "github.com/hashicorp/boundary/internal/db/assert"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
@@ -19,6 +23,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestRepository_CreateCredentialLibrary(t *testing.T) {
|
||||
@@ -2079,17 +2084,26 @@ func TestRepository_ListCredentialLibraries(t *testing.T) {
|
||||
require.NotNil(orig)
|
||||
|
||||
// test
|
||||
got, err := repo.ListCredentialLibraries(ctx, cs.GetPublicId())
|
||||
got, ttime, err := repo.ListLibraries(ctx, cs.GetPublicId())
|
||||
assert.NoError(err)
|
||||
require.Len(got, 1)
|
||||
got1 := got[0]
|
||||
assert.Equal(orig.GetPublicId(), got1.GetPublicId())
|
||||
assert.Equal(orig.GetStoreId(), got1.GetStoreId())
|
||||
assert.Equal(orig.GetHttpMethod(), got1.GetHttpMethod())
|
||||
assert.Equal(orig.GetVaultPath(), got1.GetVaultPath())
|
||||
assert.Equal(orig.GetName(), got1.GetName())
|
||||
assert.Equal(orig.GetCredentialType(), got1.GetCredentialType())
|
||||
assert.Empty(got1.MappingOverride)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
require.Empty(cmp.Diff(
|
||||
orig,
|
||||
got[0],
|
||||
cmpopts.IgnoreUnexported(
|
||||
CredentialLibrary{},
|
||||
store.CredentialLibrary{},
|
||||
timestamp.Timestamp{},
|
||||
timestamppb.Timestamp{},
|
||||
),
|
||||
cmpopts.IgnoreFields(
|
||||
CredentialLibrary{},
|
||||
"MappingOverride",
|
||||
),
|
||||
))
|
||||
})
|
||||
|
||||
t.Run("with-no-credential-store-id", func(t *testing.T) {
|
||||
@@ -2102,10 +2116,9 @@ func TestRepository_ListCredentialLibraries(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
// test
|
||||
got, err := repo.ListCredentialLibraries(ctx, "")
|
||||
_, _, err = repo.ListLibraries(ctx, "")
|
||||
wantErr := errors.InvalidParameter
|
||||
assert.Truef(errors.Match(errors.T(wantErr), err), "want err: %q got: %q", wantErr, err)
|
||||
assert.Nil(got)
|
||||
})
|
||||
|
||||
t.Run("CredentialStore-with-no-libraries", func(t *testing.T) {
|
||||
@@ -2120,9 +2133,12 @@ func TestRepository_ListCredentialLibraries(t *testing.T) {
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
|
||||
// test
|
||||
got, err := repo.ListCredentialLibraries(ctx, cs.GetPublicId())
|
||||
got, ttime, err := repo.ListLibraries(ctx, cs.GetPublicId())
|
||||
assert.NoError(err)
|
||||
assert.Empty(got)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2141,7 +2157,7 @@ func TestRepository_ListCredentialLibraries_Limits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoOpts []Option
|
||||
listOpts []Option
|
||||
listOpts []credential.Option
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
@@ -2153,31 +2169,21 @@ func TestRepository_ListCredentialLibraries_Limits(t *testing.T) {
|
||||
repoOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "with-negative-repo-limit",
|
||||
repoOpts: []Option{WithLimit(-1)},
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "with-list-limit",
|
||||
listOpts: []Option{WithLimit(3)},
|
||||
listOpts: []credential.Option{credential.WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "with-negative-list-limit",
|
||||
listOpts: []Option{WithLimit(-1)},
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "with-repo-smaller-than-list-limit",
|
||||
repoOpts: []Option{WithLimit(2)},
|
||||
listOpts: []Option{WithLimit(6)},
|
||||
listOpts: []credential.Option{credential.WithLimit(6)},
|
||||
wantLen: 6,
|
||||
},
|
||||
{
|
||||
name: "with-repo-larger-than-list-limit",
|
||||
repoOpts: []Option{WithLimit(6)},
|
||||
listOpts: []Option{WithLimit(2)},
|
||||
listOpts: []credential.Option{credential.WithLimit(2)},
|
||||
wantLen: 2,
|
||||
},
|
||||
}
|
||||
@@ -2191,9 +2197,155 @@ func TestRepository_ListCredentialLibraries_Limits(t *testing.T) {
|
||||
repo, err := NewRepository(ctx, rw, rw, kms, sche, tt.repoOpts...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListCredentialLibraries(ctx, libs[0].StoreId, tt.listOpts...)
|
||||
got, ttime, err := repo.ListLibraries(ctx, libs[0].StoreId, tt.listOpts...)
|
||||
require.NoError(err)
|
||||
assert.Len(got, tt.wantLen)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListCredentialLibraries_Pagination(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 3)
|
||||
|
||||
for _, cs := range css[:2] { // Leave the third store empty
|
||||
TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 5)
|
||||
}
|
||||
repo, err := NewRepository(ctx, rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
for _, cs := range css[:2] {
|
||||
page1, ttime, err := repo.ListLibraries(ctx, cs.GetPublicId(), credential.WithLimit(2))
|
||||
require.NoError(err)
|
||||
require.Len(page1, 2)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
page2, ttime, err := repo.ListLibraries(ctx, cs.GetPublicId(), credential.WithLimit(2), credential.WithStartPageAfterItem(page1[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page2, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page1 {
|
||||
assert.NotEqual(item.GetPublicId(), page2[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page2[1].GetPublicId())
|
||||
}
|
||||
page3, ttime, err := repo.ListLibraries(ctx, cs.GetPublicId(), credential.WithLimit(2), credential.WithStartPageAfterItem(page2[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page3, 1)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range append(page1, page2...) {
|
||||
assert.NotEqual(item.GetPublicId(), page3[0].GetPublicId())
|
||||
}
|
||||
page4, ttime, err := repo.ListLibraries(ctx, cs.GetPublicId(), credential.WithLimit(2), credential.WithStartPageAfterItem(page3[0]))
|
||||
require.NoError(err)
|
||||
require.Empty(page4)
|
||||
}
|
||||
|
||||
emptyPage, ttime, err := repo.ListLibraries(ctx, css[2].GetPublicId(), credential.WithLimit(2))
|
||||
require.NoError(err)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
require.Empty(emptyPage)
|
||||
}
|
||||
|
||||
func TestRepository_ListDeletedLibraryIds(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
store := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
|
||||
libs := TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2)
|
||||
|
||||
repo, err := NewRepository(ctx, rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
// Expect no entries at the start
|
||||
deletedIds, ttime, err := repo.ListDeletedLibraryIds(ctx, time.Now().AddDate(-1, 0, 0))
|
||||
require.NoError(err)
|
||||
require.Empty(deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
|
||||
// Delete a vault library
|
||||
_, err = repo.DeleteCredentialLibrary(ctx, prj.GetPublicId(), libs[0].GetPublicId())
|
||||
require.NoError(err)
|
||||
|
||||
// Expect a single entry
|
||||
deletedIds, ttime, err = repo.ListDeletedLibraryIds(ctx, time.Now().AddDate(-1, 0, 0))
|
||||
require.NoError(err)
|
||||
require.Equal([]string{libs[0].GetPublicId()}, deletedIds)
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
|
||||
// Try again with the time set to now, expect no entries
|
||||
deletedIds, ttime, err = repo.ListDeletedLibraryIds(ctx, time.Now())
|
||||
require.NoError(err)
|
||||
require.Empty(deletedIds)
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
func TestRepository_EstimatedLibraryCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDb, err := conn.SqlDB(ctx)
|
||||
require.NoError(err)
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
store := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
|
||||
|
||||
repo, err := NewRepository(ctx, rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
// Check total entries at start, expect 0
|
||||
numItems, err := repo.EstimatedLibraryCount(ctx)
|
||||
require.NoError(err)
|
||||
assert.Equal(0, numItems)
|
||||
|
||||
// Create some libraries
|
||||
libs := TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2)
|
||||
// Run analyze to update postgres meta tables
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(err)
|
||||
|
||||
numItems, err = repo.EstimatedLibraryCount(ctx)
|
||||
require.NoError(err)
|
||||
assert.Equal(2, numItems)
|
||||
|
||||
// Delete a library
|
||||
_, err = repo.DeleteCredentialLibrary(ctx, prj.GetPublicId(), libs[0].GetPublicId())
|
||||
require.NoError(err)
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(err)
|
||||
|
||||
numItems, err = repo.EstimatedLibraryCount(ctx)
|
||||
require.NoError(err)
|
||||
assert.Equal(1, numItems)
|
||||
}
|
||||
@@ -1580,7 +1580,7 @@ group by store_id, status;
|
||||
}
|
||||
|
||||
{
|
||||
libs, err := repo.ListCredentialLibraries(ctx, storeId)
|
||||
libs, _, err := repo.ListLibraries(ctx, storeId)
|
||||
assert.NoError(err)
|
||||
assert.Len(libs, len(actualLibs))
|
||||
}
|
||||
@@ -1640,7 +1640,7 @@ group by store_id, status;
|
||||
|
||||
// libraries should be empty
|
||||
{
|
||||
libs, err := repo.ListCredentialLibraries(ctx, storeId)
|
||||
libs, _, err := repo.ListLibraries(ctx, storeId)
|
||||
assert.NoError(err)
|
||||
assert.Empty(libs)
|
||||
}
|
||||
|
||||
@@ -306,27 +306,6 @@ func (r *Repository) LookupSSHCertificateCredentialLibrary(ctx context.Context,
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// ListSSHCertificateCredentialLibraries returns a slice of SSHCertificateCredentialLibraries for the
|
||||
// storeId. WithLimit is the only option supported.
|
||||
func (r *Repository) ListSSHCertificateCredentialLibraries(ctx context.Context, storeId string, opt ...Option) ([]*SSHCertificateCredentialLibrary, error) {
|
||||
const op = "vault.(Repository).ListSSHCertificateCredentialLibraries"
|
||||
if storeId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no storeId")
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
var libs []*SSHCertificateCredentialLibrary
|
||||
err := r.reader.SearchWhere(ctx, &libs, "store_id = ?", []any{storeId}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return libs, nil
|
||||
}
|
||||
|
||||
// DeleteSSHCertificateCredentialLibrary deletes publicId from the repository and returns
|
||||
// the number of records deleted.
|
||||
func (r *Repository) DeleteSSHCertificateCredentialLibrary(ctx context.Context, projectId string, publicId string, _ ...Option) (int, error) {
|
||||
|
||||
@@ -811,78 +811,6 @@ func TestRepository_LookupSSHCertificateCredentialLibrary(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepository_ListSSHCertificateCredentialLibraries_Limits(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
|
||||
const count = 10
|
||||
libs := TestSSHCertificateCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), count)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
repoOpts []Option
|
||||
listOpts []Option
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "with-no-limits",
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "with-repo-limit",
|
||||
repoOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "with-negative-repo-limit",
|
||||
repoOpts: []Option{WithLimit(-1)},
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "with-list-limit",
|
||||
listOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "with-negative-list-limit",
|
||||
listOpts: []Option{WithLimit(-1)},
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "with-repo-smaller-than-list-limit",
|
||||
repoOpts: []Option{WithLimit(2)},
|
||||
listOpts: []Option{WithLimit(6)},
|
||||
wantLen: 6,
|
||||
},
|
||||
{
|
||||
name: "with-repo-larger-than-list-limit",
|
||||
repoOpts: []Option{WithLimit(6)},
|
||||
listOpts: []Option{WithLimit(2)},
|
||||
wantLen: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kms, sche, tt.repoOpts...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListSSHCertificateCredentialLibraries(ctx, libs[0].StoreId, tt.listOpts...)
|
||||
require.NoError(err)
|
||||
assert.Len(got, tt.wantLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_UpdateSSHCertificateCredentialLibrary(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault/store"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -131,3 +132,13 @@ func (l *SSHCertificateCredentialLibrary) CredentialType() globals.CredentialTyp
|
||||
}
|
||||
|
||||
var _ credential.Library = (*SSHCertificateCredentialLibrary)(nil)
|
||||
|
||||
type deletedSSHCertificateCredentialLibrary struct {
|
||||
PublicId string `gorm:"primary_key"`
|
||||
DeleteTime *timestamp.Timestamp
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name
|
||||
func (s *deletedSSHCertificateCredentialLibrary) TableName() string {
|
||||
return "credential_vault_ssh_cert_library_deleted"
|
||||
}
|
||||
Reference in new issue
Block a user