You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smallstep-certificates/authority/admin/db/nosql/provisioner_test.go

1398 lines
40 KiB
Go

package nosql
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/db"
"github.com/smallstep/nosql"
nosqldb "github.com/smallstep/nosql/database"
"go.step.sm/linkedca"
)
func TestDB_getDBProvisionerBytes(t *testing.T) {
provID := "provID"
type test struct {
db nosql.DB
err error
adminErr *admin.Error
}
var tests = map[string]func(t *testing.T) test{
"fail/not-found": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, nosqldb.ErrNotFound
},
},
adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, errors.New("force")
},
},
err: errors.New("error loading provisioner provID: force"),
}
},
"ok": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return []byte("foo"), nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db}
if b, err := d.getDBProvisionerBytes(context.Background(), provID); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) {
assert.Equals(t, string(b), "foo")
}
})
}
}
func TestDB_getDBProvisioner(t *testing.T) {
provID := "provID"
type test struct {
db nosql.DB
err error
adminErr *admin.Error
dbp *dbProvisioner
}
var tests = map[string]func(t *testing.T) test{
"fail/not-found": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, nosqldb.ErrNotFound
},
},
adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, errors.New("force")
},
},
err: errors.New("error loading provisioner provID: force"),
}
},
"fail/unmarshal-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return []byte("foo"), nil
},
},
err: errors.New("error unmarshaling provisioner provID into dbProvisioner"),
}
},
"fail/deleted": func(t *testing.T) test {
now := clock.Now()
dbp := &dbProvisioner{
ID: provID,
AuthorityID: admin.DefaultAuthorityID,
Type: linkedca.Provisioner_JWK,
Name: "provName",
CreatedAt: now,
DeletedAt: now,
}
b, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return b, nil
},
},
adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner provID is deleted"),
}
},
"ok": func(t *testing.T) test {
now := clock.Now()
dbp := &dbProvisioner{
ID: provID,
AuthorityID: admin.DefaultAuthorityID,
Type: linkedca.Provisioner_JWK,
Name: "provName",
CreatedAt: now,
}
b, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return b, nil
},
},
dbp: dbp,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID}
if dbp, err := d.getDBProvisioner(context.Background(), provID); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) {
assert.Equals(t, dbp.ID, provID)
assert.Equals(t, dbp.AuthorityID, tc.dbp.AuthorityID)
assert.Equals(t, dbp.Type, tc.dbp.Type)
assert.Equals(t, dbp.Name, tc.dbp.Name)
assert.Equals(t, dbp.CreatedAt, tc.dbp.CreatedAt)
assert.Fatal(t, dbp.DeletedAt.IsZero())
assert.Equals(t, dbp.Webhooks, tc.dbp.Webhooks)
}
})
}
}
func TestDB_unmarshalDBProvisioner(t *testing.T) {
provID := "provID"
type test struct {
in []byte
err error
adminErr *admin.Error
dbp *dbProvisioner
}
var tests = map[string]func(t *testing.T) test{
"fail/unmarshal-error": func(t *testing.T) test {
return test{
in: []byte("foo"),
err: errors.New("error unmarshaling provisioner provID into dbProvisioner"),
}
},
"fail/deleted-error": func(t *testing.T) test {
dbp := &dbProvisioner{
DeletedAt: clock.Now(),
}
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
in: data,
adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner %s is deleted", provID),
}
},
"fail/authority-mismatch-error": func(t *testing.T) test {
dbp := &dbProvisioner{
ID: provID,
AuthorityID: "foo",
}
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
in: data,
adminErr: admin.NewError(admin.ErrorAuthorityMismatchType,
"provisioner %s is not owned by authority %s", provID, admin.DefaultAuthorityID),
}
},
"ok": func(t *testing.T) test {
dbp := &dbProvisioner{
ID: provID,
AuthorityID: admin.DefaultAuthorityID,
Type: linkedca.Provisioner_JWK,
Name: "provName",
CreatedAt: clock.Now(),
}
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
in: data,
dbp: dbp,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{authorityID: admin.DefaultAuthorityID}
if dbp, err := d.unmarshalDBProvisioner(tc.in, provID); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) {
assert.Equals(t, dbp.ID, provID)
assert.Equals(t, dbp.AuthorityID, tc.dbp.AuthorityID)
assert.Equals(t, dbp.Type, tc.dbp.Type)
assert.Equals(t, dbp.Name, tc.dbp.Name)
assert.Equals(t, dbp.Details, tc.dbp.Details)
assert.Equals(t, dbp.Claims, tc.dbp.Claims)
assert.Equals(t, dbp.X509Template, tc.dbp.X509Template)
assert.Equals(t, dbp.SSHTemplate, tc.dbp.SSHTemplate)
assert.Equals(t, dbp.CreatedAt, tc.dbp.CreatedAt)
assert.Fatal(t, dbp.DeletedAt.IsZero())
assert.Equals(t, dbp.Webhooks, tc.dbp.Webhooks)
}
})
}
}
func defaultDBP(t *testing.T) *dbProvisioner {
details := &linkedca.ProvisionerDetails_ACME{
ACME: &linkedca.ACMEProvisioner{
ForceCn: true,
},
}
detailBytes, err := json.Marshal(details)
assert.FatalError(t, err)
return &dbProvisioner{
ID: "provID",
AuthorityID: admin.DefaultAuthorityID,
Type: linkedca.Provisioner_ACME,
Name: "provName",
Details: detailBytes,
Claims: &linkedca.Claims{
DisableRenewal: true,
X509: &linkedca.X509Claims{
Enabled: true,
Durations: &linkedca.Durations{
Min: "5m",
Max: "12h",
Default: "6h",
},
},
Ssh: &linkedca.SSHClaims{
Enabled: true,
UserDurations: &linkedca.Durations{
Min: "5m",
Max: "12h",
Default: "6h",
},
HostDurations: &linkedca.Durations{
Min: "5m",
Max: "12h",
Default: "6h",
},
},
},
X509Template: &linkedca.Template{
Template: []byte("foo"),
Data: []byte("bar"),
},
SSHTemplate: &linkedca.Template{
Template: []byte("baz"),
Data: []byte("zap"),
},
CreatedAt: clock.Now(),
Webhooks: []dbWebhook{
{
Name: "metadata",
URL: "https://inventory.smallstep.com",
Kind: linkedca.Webhook_ENRICHING.String(),
Secret: "secret",
BearerToken: "token",
},
},
}
}
func TestDB_unmarshalProvisioner(t *testing.T) {
provID := "provID"
type test struct {
in []byte
err error
adminErr *admin.Error
dbp *dbProvisioner
}
var tests = map[string]func(t *testing.T) test{
"fail/unmarshal-error": func(t *testing.T) test {
return test{
in: []byte("foo"),
err: errors.New("error unmarshaling provisioner provID into dbProvisioner"),
}
},
"fail/deleted-error": func(t *testing.T) test {
dbp := &dbProvisioner{
DeletedAt: time.Now(),
}
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
in: data,
adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner provID is deleted"),
}
},
"ok": func(t *testing.T) test {
dbp := defaultDBP(t)
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
in: data,
dbp: dbp,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{authorityID: admin.DefaultAuthorityID}
if prov, err := d.unmarshalProvisioner(tc.in, provID); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) {
assert.Equals(t, prov.Id, provID)
assert.Equals(t, prov.AuthorityId, tc.dbp.AuthorityID)
assert.Equals(t, prov.Type, tc.dbp.Type)
assert.Equals(t, prov.Name, tc.dbp.Name)
assert.Equals(t, prov.Claims, tc.dbp.Claims)
assert.Equals(t, prov.X509Template, tc.dbp.X509Template)
assert.Equals(t, prov.SshTemplate, tc.dbp.SSHTemplate)
assert.Equals(t, prov.Webhooks, dbWebhooksToLinkedca(tc.dbp.Webhooks))
retDetailsBytes, err := json.Marshal(prov.Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, tc.dbp.Details)
}
})
}
}
func TestDB_GetProvisioner(t *testing.T) {
provID := "provID"
type test struct {
db nosql.DB
err error
adminErr *admin.Error
dbp *dbProvisioner
}
var tests = map[string]func(t *testing.T) test{
"fail/not-found": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, nosqldb.ErrNotFound
},
},
adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, errors.New("force")
},
},
err: errors.New("error loading provisioner provID: force"),
}
},
"fail/unmarshal-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return []byte("foo"), nil
},
},
err: errors.New("error unmarshaling provisioner provID into dbProvisioner"),
}
},
"fail/deleted": func(t *testing.T) test {
dbp := defaultDBP(t)
dbp.DeletedAt = clock.Now()
b, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return b, nil
},
},
dbp: dbp,
adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner provID is deleted"),
}
},
"fail/authorityID-mismatch": func(t *testing.T) test {
dbp := defaultDBP(t)
dbp.AuthorityID = "foo"
b, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return b, nil
},
},
dbp: dbp,
adminErr: admin.NewError(admin.ErrorAuthorityMismatchType,
"provisioner %s is not owned by authority %s", dbp.ID, admin.DefaultAuthorityID),
}
},
"ok": func(t *testing.T) test {
dbp := defaultDBP(t)
b, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return b, nil
},
},
dbp: dbp,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID}
if prov, err := d.GetProvisioner(context.Background(), provID); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) {
assert.Equals(t, prov.Id, provID)
assert.Equals(t, prov.AuthorityId, tc.dbp.AuthorityID)
assert.Equals(t, prov.Type, tc.dbp.Type)
assert.Equals(t, prov.Name, tc.dbp.Name)
assert.Equals(t, prov.Claims, tc.dbp.Claims)
assert.Equals(t, prov.X509Template, tc.dbp.X509Template)
assert.Equals(t, prov.SshTemplate, tc.dbp.SSHTemplate)
assert.Equals(t, prov.Webhooks, dbWebhooksToLinkedca(tc.dbp.Webhooks))
retDetailsBytes, err := json.Marshal(prov.Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, tc.dbp.Details)
}
})
}
}
func TestDB_DeleteProvisioner(t *testing.T) {
provID := "provID"
type test struct {
db nosql.DB
err error
adminErr *admin.Error
}
var tests = map[string]func(t *testing.T) test{
"fail/not-found": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, nosqldb.ErrNotFound
},
},
adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, errors.New("force")
},
},
err: errors.New("error loading provisioner provID: force"),
}
},
"fail/save-error": func(t *testing.T) test {
dbp := defaultDBP(t)
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return data, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
assert.Equals(t, string(old), string(data))
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.Equals(t, _dbp.ID, provID)
assert.Equals(t, _dbp.AuthorityID, dbp.AuthorityID)
assert.Equals(t, _dbp.Type, dbp.Type)
assert.Equals(t, _dbp.Name, dbp.Name)
assert.Equals(t, _dbp.Claims, dbp.Claims)
assert.Equals(t, _dbp.X509Template, dbp.X509Template)
assert.Equals(t, _dbp.SSHTemplate, dbp.SSHTemplate)
assert.Equals(t, _dbp.CreatedAt, dbp.CreatedAt)
assert.Equals(t, _dbp.Details, dbp.Details)
assert.Equals(t, _dbp.Webhooks, dbp.Webhooks)
assert.True(t, _dbp.DeletedAt.Before(time.Now()))
assert.True(t, _dbp.DeletedAt.After(time.Now().Add(-time.Minute)))
return nil, false, errors.New("force")
},
},
err: errors.New("error saving authority provisioner: force"),
}
},
"ok": func(t *testing.T) test {
dbp := defaultDBP(t)
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return data, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
assert.Equals(t, string(old), string(data))
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.Equals(t, _dbp.ID, provID)
assert.Equals(t, _dbp.AuthorityID, dbp.AuthorityID)
assert.Equals(t, _dbp.Type, dbp.Type)
assert.Equals(t, _dbp.Name, dbp.Name)
assert.Equals(t, _dbp.Claims, dbp.Claims)
assert.Equals(t, _dbp.X509Template, dbp.X509Template)
assert.Equals(t, _dbp.SSHTemplate, dbp.SSHTemplate)
assert.Equals(t, _dbp.CreatedAt, dbp.CreatedAt)
assert.Equals(t, _dbp.Details, dbp.Details)
assert.Equals(t, _dbp.Webhooks, dbp.Webhooks)
assert.True(t, _dbp.DeletedAt.Before(time.Now()))
assert.True(t, _dbp.DeletedAt.After(time.Now().Add(-time.Minute)))
return nu, true, nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID}
if err := d.DeleteProvisioner(context.Background(), provID); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
}
})
}
}
func TestDB_GetProvisioners(t *testing.T) {
fooProv := defaultDBP(t)
fooProv.Name = "foo"
foob, err := json.Marshal(fooProv)
assert.FatalError(t, err)
barProv := defaultDBP(t)
barProv.Name = "bar"
barProv.DeletedAt = clock.Now()
barb, err := json.Marshal(barProv)
assert.FatalError(t, err)
bazProv := defaultDBP(t)
bazProv.Name = "baz"
bazProv.AuthorityID = "baz"
bazb, err := json.Marshal(bazProv)
assert.FatalError(t, err)
zapProv := defaultDBP(t)
zapProv.Name = "zap"
zapb, err := json.Marshal(zapProv)
assert.FatalError(t, err)
type test struct {
db nosql.DB
err error
adminErr *admin.Error
verify func(*testing.T, []*linkedca.Provisioner)
}
var tests = map[string]func(t *testing.T) test{
"fail/db.List-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, provisionersTable)
return nil, errors.New("force")
},
},
err: errors.New("error loading provisioners"),
}
},
"fail/unmarshal-error": func(t *testing.T) test {
ret := []*nosqldb.Entry{
{Bucket: provisionersTable, Key: []byte("foo"), Value: foob},
{Bucket: provisionersTable, Key: []byte("bar"), Value: barb},
{Bucket: provisionersTable, Key: []byte("zap"), Value: []byte("zap")},
}
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, provisionersTable)
return ret, nil
},
},
err: errors.New("error unmarshaling provisioner zap into dbProvisioner"),
}
},
"ok/none": func(t *testing.T) test {
ret := []*nosqldb.Entry{}
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, provisionersTable)
return ret, nil
},
},
verify: func(t *testing.T, provs []*linkedca.Provisioner) {
assert.Equals(t, len(provs), 0)
},
}
},
"ok/only-invalid": func(t *testing.T) test {
ret := []*nosqldb.Entry{
{Bucket: provisionersTable, Key: []byte("bar"), Value: barb},
{Bucket: provisionersTable, Key: []byte("baz"), Value: bazb},
}
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, provisionersTable)
return ret, nil
},
},
verify: func(t *testing.T, provs []*linkedca.Provisioner) {
assert.Equals(t, len(provs), 0)
},
}
},
"ok": func(t *testing.T) test {
ret := []*nosqldb.Entry{
{Bucket: provisionersTable, Key: []byte("foo"), Value: foob},
{Bucket: provisionersTable, Key: []byte("bar"), Value: barb},
{Bucket: provisionersTable, Key: []byte("baz"), Value: bazb},
{Bucket: provisionersTable, Key: []byte("zap"), Value: zapb},
}
return test{
db: &db.MockNoSQLDB{
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
assert.Equals(t, bucket, provisionersTable)
return ret, nil
},
},
verify: func(t *testing.T, provs []*linkedca.Provisioner) {
assert.Equals(t, len(provs), 2)
assert.Equals(t, provs[0].Id, fooProv.ID)
assert.Equals(t, provs[0].AuthorityId, fooProv.AuthorityID)
assert.Equals(t, provs[0].Type, fooProv.Type)
assert.Equals(t, provs[0].Name, fooProv.Name)
assert.Equals(t, provs[0].Claims, fooProv.Claims)
assert.Equals(t, provs[0].X509Template, fooProv.X509Template)
assert.Equals(t, provs[0].SshTemplate, fooProv.SSHTemplate)
assert.Equals(t, provs[0].Webhooks, dbWebhooksToLinkedca(fooProv.Webhooks))
retDetailsBytes, err := json.Marshal(provs[0].Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, fooProv.Details)
assert.Equals(t, provs[1].Id, zapProv.ID)
assert.Equals(t, provs[1].AuthorityId, zapProv.AuthorityID)
assert.Equals(t, provs[1].Type, zapProv.Type)
assert.Equals(t, provs[1].Name, zapProv.Name)
assert.Equals(t, provs[1].Claims, zapProv.Claims)
assert.Equals(t, provs[1].X509Template, zapProv.X509Template)
assert.Equals(t, provs[1].SshTemplate, zapProv.SSHTemplate)
assert.Equals(t, provs[1].Webhooks, dbWebhooksToLinkedca(zapProv.Webhooks))
retDetailsBytes, err = json.Marshal(provs[1].Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, zapProv.Details)
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID}
if provs, err := d.GetProvisioners(context.Background()); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else if assert.Nil(t, tc.err) && assert.Nil(t, tc.adminErr) {
tc.verify(t, provs)
}
})
}
}
func TestDB_CreateProvisioner(t *testing.T) {
type test struct {
db nosql.DB
err error
adminErr *admin.Error
prov *linkedca.Provisioner
}
var tests = map[string]func(t *testing.T) test{
"fail/save-error": func(t *testing.T) test {
dbp := defaultDBP(t)
prov, err := dbp.convert2linkedca()
assert.FatalError(t, err)
return test{
prov: prov,
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, old, nil)
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
assert.Equals(t, _dbp.Claims, prov.Claims)
assert.Equals(t, _dbp.X509Template, prov.X509Template)
assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate)
assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks))
retDetailsBytes, err := json.Marshal(prov.Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, _dbp.Details)
assert.True(t, _dbp.DeletedAt.IsZero())
assert.True(t, _dbp.CreatedAt.Before(time.Now()))
assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute)))
return nil, false, errors.New("force")
},
},
adminErr: admin.NewErrorISE("error creating provisioner provName: error saving authority provisioner: force"),
}
},
"ok": func(t *testing.T) test {
dbp := defaultDBP(t)
prov, err := dbp.convert2linkedca()
assert.FatalError(t, err)
return test{
prov: prov,
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, old, nil)
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
assert.Equals(t, _dbp.Claims, prov.Claims)
assert.Equals(t, _dbp.X509Template, prov.X509Template)
assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate)
assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks))
retDetailsBytes, err := json.Marshal(prov.Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, _dbp.Details)
assert.True(t, _dbp.DeletedAt.IsZero())
assert.True(t, _dbp.CreatedAt.Before(time.Now()))
assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute)))
return nu, true, nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID}
if err := d.CreateProvisioner(context.Background(), tc.prov); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
}
})
}
}
func TestDB_UpdateProvisioner(t *testing.T) {
provID := "provID"
type test struct {
db nosql.DB
err error
adminErr *admin.Error
prov *linkedca.Provisioner
}
var tests = map[string]func(t *testing.T) test{
"fail/not-found": func(t *testing.T) test {
return test{
prov: &linkedca.Provisioner{Id: provID},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, nosqldb.ErrNotFound
},
},
adminErr: admin.NewError(admin.ErrorNotFoundType, "provisioner provID not found"),
}
},
"fail/db.Get-error": func(t *testing.T) test {
return test{
prov: &linkedca.Provisioner{Id: provID},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return nil, errors.New("force")
},
},
err: errors.New("error loading provisioner provID: force"),
}
},
"fail/update-deleted": func(t *testing.T) test {
dbp := defaultDBP(t)
dbp.DeletedAt = clock.Now()
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
prov: &linkedca.Provisioner{Id: provID},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return data, nil
},
},
adminErr: admin.NewError(admin.ErrorDeletedType, "provisioner %s is deleted", provID),
}
},
"fail/update-type-error": func(t *testing.T) test {
dbp := defaultDBP(t)
upd, err := dbp.convert2linkedca()
assert.FatalError(t, err)
upd.Type = linkedca.Provisioner_JWK
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
prov: upd,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return data, nil
},
},
adminErr: admin.NewError(admin.ErrorBadRequestType, "cannot update provisioner type"),
}
},
"fail/save-error": func(t *testing.T) test {
dbp := defaultDBP(t)
prov, err := dbp.convert2linkedca()
assert.FatalError(t, err)
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
prov: prov,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return data, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
assert.Equals(t, string(old), string(data))
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
assert.Equals(t, _dbp.Claims, prov.Claims)
assert.Equals(t, _dbp.X509Template, prov.X509Template)
assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate)
assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks))
retDetailsBytes, err := json.Marshal(prov.Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, _dbp.Details)
assert.True(t, _dbp.DeletedAt.IsZero())
assert.True(t, _dbp.CreatedAt.Before(time.Now()))
assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute)))
return nil, false, errors.New("force")
},
},
err: errors.New("error saving authority provisioner: force"),
}
},
"ok": func(t *testing.T) test {
dbp := defaultDBP(t)
prov, err := dbp.convert2linkedca()
assert.FatalError(t, err)
prov.Name = "new-name"
prov.Claims = &linkedca.Claims{
DisableRenewal: true,
X509: &linkedca.X509Claims{
Enabled: true,
Durations: &linkedca.Durations{
Min: "10m",
Max: "8h",
Default: "4h",
},
},
Ssh: &linkedca.SSHClaims{
Enabled: true,
UserDurations: &linkedca.Durations{
Min: "7m",
Max: "11h",
Default: "5h",
},
HostDurations: &linkedca.Durations{
Min: "4m",
Max: "24h",
Default: "24h",
},
},
}
prov.X509Template = &linkedca.Template{
Template: []byte("x"),
Data: []byte("y"),
}
prov.SshTemplate = &linkedca.Template{
Template: []byte("z"),
Data: []byte("w"),
}
prov.Details = &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_ACME{
ACME: &linkedca.ACMEProvisioner{
ForceCn: false,
},
},
}
prov.Webhooks = []*linkedca.Webhook{
{
Name: "users",
Url: "https://example.com/users",
},
}
data, err := json.Marshal(dbp)
assert.FatalError(t, err)
return test{
prov: prov,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
return data, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, provisionersTable)
assert.Equals(t, string(key), provID)
assert.Equals(t, string(old), string(data))
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
assert.Equals(t, _dbp.Claims, prov.Claims)
assert.Equals(t, _dbp.X509Template, prov.X509Template)
assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate)
assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks))
retDetailsBytes, err := json.Marshal(prov.Details.GetData())
assert.FatalError(t, err)
assert.Equals(t, retDetailsBytes, _dbp.Details)
assert.True(t, _dbp.DeletedAt.IsZero())
assert.True(t, _dbp.CreatedAt.Before(time.Now()))
assert.True(t, _dbp.CreatedAt.After(time.Now().Add(-time.Minute)))
return nu, true, nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID}
if err := d.UpdateProvisioner(context.Background(), tc.prov); err != nil {
var ae *admin.Error
if errors.As(err, &ae) {
if assert.NotNil(t, tc.adminErr) {
assert.Equals(t, ae.Type, tc.adminErr.Type)
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
assert.Equals(t, ae.Status, tc.adminErr.Status)
assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error())
assert.Equals(t, ae.Detail, tc.adminErr.Detail)
}
} else {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
}
})
}
}
func Test_linkedcaWebhooksToDB(t *testing.T) {
type test struct {
in []*linkedca.Webhook
want []dbWebhook
}
var tests = map[string]test{
"nil": {
in: nil,
want: nil,
},
"zero": {
in: []*linkedca.Webhook{},
want: nil,
},
"bearer": {
in: []*linkedca.Webhook{
{
Name: "bearer",
Url: "https://example.com",
Kind: linkedca.Webhook_ENRICHING,
Secret: "secret",
Auth: &linkedca.Webhook_BearerToken{
BearerToken: &linkedca.BearerToken{
BearerToken: "token",
},
},
DisableTlsClientAuth: true,
CertType: linkedca.Webhook_X509,
},
},
want: []dbWebhook{
{
Name: "bearer",
URL: "https://example.com",
Kind: "ENRICHING",
Secret: "secret",
BearerToken: "token",
DisableTLSClientAuth: true,
CertType: linkedca.Webhook_X509.String(),
},
},
},
"basic": {
in: []*linkedca.Webhook{
{
Name: "basic",
Url: "https://example.com",
Kind: linkedca.Webhook_ENRICHING,
Secret: "secret",
Auth: &linkedca.Webhook_BasicAuth{
BasicAuth: &linkedca.BasicAuth{
Username: "user",
Password: "pass",
},
},
},
},
want: []dbWebhook{
{
Name: "basic",
URL: "https://example.com",
Kind: "ENRICHING",
Secret: "secret",
BasicAuth: &dbBasicAuth{
Username: "user",
Password: "pass",
},
CertType: linkedca.Webhook_ALL.String(),
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := linkedcaWebhooksToDB(tc.in)
assert.Equals(t, tc.want, got)
})
}
}
func Test_dbWebhooksToLinkedca(t *testing.T) {
type test struct {
in []dbWebhook
want []*linkedca.Webhook
}
var tests = map[string]test{
"nil": {
in: nil,
want: nil,
},
"zero": {
in: []dbWebhook{},
want: nil,
},
"bearer": {
in: []dbWebhook{
{
Name: "bearer",
ID: "69350cb6-6c31-4b5e-bf25-affd5053427d",
URL: "https://example.com",
Kind: "ENRICHING",
Secret: "secret",
BearerToken: "token",
DisableTLSClientAuth: true,
},
},
want: []*linkedca.Webhook{
{
Name: "bearer",
Id: "69350cb6-6c31-4b5e-bf25-affd5053427d",
Url: "https://example.com",
Kind: linkedca.Webhook_ENRICHING,
Secret: "secret",
Auth: &linkedca.Webhook_BearerToken{
BearerToken: &linkedca.BearerToken{
BearerToken: "token",
},
},
DisableTlsClientAuth: true,
},
},
},
"basic": {
in: []dbWebhook{
{
Name: "basic",
ID: "69350cb6-6c31-4b5e-bf25-affd5053427d",
URL: "https://example.com",
Kind: "ENRICHING",
Secret: "secret",
BasicAuth: &dbBasicAuth{
Username: "user",
Password: "pass",
},
},
},
want: []*linkedca.Webhook{
{
Name: "basic",
Id: "69350cb6-6c31-4b5e-bf25-affd5053427d",
Url: "https://example.com",
Kind: linkedca.Webhook_ENRICHING,
Secret: "secret",
Auth: &linkedca.Webhook_BasicAuth{
BasicAuth: &linkedca.BasicAuth{
Username: "user",
Password: "pass",
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := dbWebhooksToLinkedca(tc.in)
assert.Equals(t, tc.want, got)
})
}
}