[acme db interface] nosql authz unit tests

This commit is contained in:
max furman 2021-03-19 14:37:45 -07:00
parent 206909b12e
commit f72b2ff2c2
9 changed files with 667 additions and 34 deletions

View file

@ -121,9 +121,9 @@ func TestHandler_GetAuthorization(t *testing.T) {
Type: "dns", Type: "dns",
Value: "example.com", Value: "example.com",
}, },
Status: "pending", Status: "pending",
Expires: expiry, ExpiresAt: expiry,
Wildcard: false, Wildcard: false,
Challenges: []*acme.Challenge{ Challenges: []*acme.Challenge{
{ {
Type: "http-01", Type: "http-01",
@ -220,7 +220,7 @@ func TestHandler_GetAuthorization(t *testing.T) {
return &acme.Authorization{ return &acme.Authorization{
AccountID: "accID", AccountID: "accID",
Status: acme.StatusPending, Status: acme.StatusPending,
Expires: time.Now().Add(-1 * time.Hour), ExpiresAt: time.Now().Add(-1 * time.Hour),
}, nil }, nil
}, },
MockUpdateAuthorization: func(ctx context.Context, az *acme.Authorization) error { MockUpdateAuthorization: func(ctx context.Context, az *acme.Authorization) error {

View file

@ -89,14 +89,24 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
return return
} }
now := clock.Now()
expiry := now.Add(defaultOrderExpiry)
// New order. // New order.
o := &acme.Order{Identifiers: nor.Identifiers} o := &acme.Order{
AccountID: acc.ID,
ProvisionerID: prov.GetID(),
Status: acme.StatusPending,
ExpiresAt: expiry,
Identifiers: nor.Identifiers,
}
o.AuthorizationIDs = make([]string, len(o.Identifiers)) o.AuthorizationIDs = make([]string, len(o.Identifiers))
for i, identifier := range o.Identifiers { for i, identifier := range o.Identifiers {
az := &acme.Authorization{ az := &acme.Authorization{
AccountID: acc.ID, AccountID: acc.ID,
Identifier: identifier, Identifier: identifier,
ExpiresAt: expiry,
Status: acme.StatusPending,
} }
if err := h.newAuthorization(ctx, az); err != nil { if err := h.newAuthorization(ctx, az); err != nil {
api.WriteError(w, err) api.WriteError(w, err)
@ -105,14 +115,12 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
o.AuthorizationIDs[i] = az.ID o.AuthorizationIDs[i] = az.ID
} }
now := clock.Now()
if o.NotBefore.IsZero() { if o.NotBefore.IsZero() {
o.NotBefore = now o.NotBefore = now
} }
if o.NotAfter.IsZero() { if o.NotAfter.IsZero() {
o.NotAfter = o.NotBefore.Add(prov.DefaultTLSCertDuration()) o.NotAfter = o.NotBefore.Add(prov.DefaultTLSCertDuration())
} }
o.Expires = now.Add(defaultOrderExpiry)
if err := h.db.CreateOrder(ctx, o); err != nil { if err := h.db.CreateOrder(ctx, o); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error creating order")) api.WriteError(w, acme.WrapErrorISE(err, "error creating order"))
@ -156,6 +164,7 @@ func (h *Handler) newAuthorization(ctx context.Context, az *acme.Authorization)
Value: az.Identifier.Value, Value: az.Identifier.Value,
Type: typ, Type: typ,
Token: az.Token, Token: az.Token,
Status: acme.StatusPending,
} }
if err := h.db.CreateChallenge(ctx, ch); err != nil { if err := h.db.CreateChallenge(ctx, ch); err != nil {
return err return err

View file

@ -166,9 +166,9 @@ func TestHandler_GetOrder(t *testing.T) {
Value: "*.smallstep.com", Value: "*.smallstep.com",
}, },
}, },
Expires: expiry, ExpiresAt: expiry,
Status: acme.StatusInvalid, Status: acme.StatusInvalid,
Error: acme.NewError(acme.ErrorMalformedType, "order has expired"), Error: acme.NewError(acme.ErrorMalformedType, "order has expired"),
AuthorizationURLs: []string{ AuthorizationURLs: []string{
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo", "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo",
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar", "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar",
@ -285,7 +285,7 @@ func TestHandler_GetOrder(t *testing.T) {
return &acme.Order{ return &acme.Order{
AccountID: "accountID", AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com", ProvisionerID: "acme/test@acme-provisioner.com",
Expires: clock.Now().Add(-time.Hour), ExpiresAt: clock.Now().Add(-time.Hour),
Status: acme.StatusReady, Status: acme.StatusReady,
}, nil }, nil
}, },
@ -311,7 +311,7 @@ func TestHandler_GetOrder(t *testing.T) {
ID: "orderID", ID: "orderID",
AccountID: "accountID", AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com", ProvisionerID: "acme/test@acme-provisioner.com",
Expires: expiry, ExpiresAt: expiry,
Status: acme.StatusReady, Status: acme.StatusReady,
AuthorizationIDs: []string{"foo", "bar", "baz"}, AuthorizationIDs: []string{"foo", "bar", "baz"},
NotBefore: nbf, NotBefore: nbf,
@ -380,7 +380,7 @@ func TestHandler_NewOrder(t *testing.T) {
naf := nbf.Add(17 * time.Hour) naf := nbf.Add(17 * time.Hour)
o := acme.Order{ o := acme.Order{
ID: "orderID", ID: "orderID",
Expires: expiry, ExpiresAt: expiry,
NotBefore: nbf, NotBefore: nbf,
NotAfter: naf, NotAfter: naf,
Identifiers: []acme.Identifier{ Identifiers: []acme.Identifier{
@ -607,8 +607,8 @@ func TestHandler_FinalizeOrder(t *testing.T) {
Value: "*.smallstep.com", Value: "*.smallstep.com",
}, },
}, },
Expires: naf, ExpiresAt: naf,
Status: acme.StatusValid, Status: acme.StatusValid,
AuthorizationURLs: []string{ AuthorizationURLs: []string{
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo", "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo",
"https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar", "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar",
@ -788,7 +788,7 @@ func TestHandler_FinalizeOrder(t *testing.T) {
return &acme.Order{ return &acme.Order{
AccountID: "accountID", AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com", ProvisionerID: "acme/test@acme-provisioner.com",
Expires: clock.Now().Add(-time.Hour), ExpiresAt: clock.Now().Add(-time.Hour),
Status: acme.StatusReady, Status: acme.StatusReady,
}, nil }, nil
}, },
@ -815,7 +815,7 @@ func TestHandler_FinalizeOrder(t *testing.T) {
ID: "orderID", ID: "orderID",
AccountID: "accountID", AccountID: "accountID",
ProvisionerID: "acme/test@acme-provisioner.com", ProvisionerID: "acme/test@acme-provisioner.com",
Expires: naf, ExpiresAt: naf,
Status: acme.StatusValid, Status: acme.StatusValid,
AuthorizationIDs: []string{"foo", "bar", "baz"}, AuthorizationIDs: []string{"foo", "bar", "baz"},
NotBefore: nbf, NotBefore: nbf,

View file

@ -13,6 +13,7 @@ type Authorization struct {
ExpiresAt time.Time `json:"expires"` ExpiresAt time.Time `json:"expires"`
Challenges []*Challenge `json:"challenges"` Challenges []*Challenge `json:"challenges"`
Wildcard bool `json:"wildcard"` Wildcard bool `json:"wildcard"`
Error *Error `json:"error,omitempty"`
ID string `json:"-"` ID string `json:"-"`
AccountID string `json:"-"` AccountID string `json:"-"`
Token string `json:"-"` Token string `json:"-"`

View file

@ -23,6 +23,7 @@ type dbAuthz struct {
Wildcard bool `json:"wildcard"` Wildcard bool `json:"wildcard"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Error *acme.Error `json:"error"` Error *acme.Error `json:"error"`
Token string `json:"token"`
} }
func (ba *dbAuthz) clone() *dbAuthz { func (ba *dbAuthz) clone() *dbAuthz {
@ -35,14 +36,14 @@ func (ba *dbAuthz) clone() *dbAuthz {
func (db *DB) getDBAuthz(ctx context.Context, id string) (*dbAuthz, error) { func (db *DB) getDBAuthz(ctx context.Context, id string) (*dbAuthz, error) {
data, err := db.db.Get(authzTable, []byte(id)) data, err := db.db.Get(authzTable, []byte(id))
if nosql.IsErrNotFound(err) { if nosql.IsErrNotFound(err) {
return nil, errors.Wrapf(err, "authz %s not found", id) return nil, acme.NewError(acme.ErrorMalformedType, "authz %s not found", id)
} else if err != nil { } else if err != nil {
return nil, errors.Wrapf(err, "error loading authz %s", id) return nil, errors.Wrapf(err, "error loading authz %s", id)
} }
var dbaz dbAuthz var dbaz dbAuthz
if err = json.Unmarshal(data, &dbaz); err != nil { if err = json.Unmarshal(data, &dbaz); err != nil {
return nil, errors.Wrap(err, "error unmarshaling authz type into dbAuthz") return nil, errors.Wrapf(err, "error unmarshaling authz %s into dbAuthz", id)
} }
return &dbaz, nil return &dbaz, nil
} }
@ -62,12 +63,15 @@ func (db *DB) GetAuthorization(ctx context.Context, id string) (*acme.Authorizat
} }
} }
return &acme.Authorization{ return &acme.Authorization{
ID: dbaz.ID,
AccountID: dbaz.AccountID,
Identifier: dbaz.Identifier, Identifier: dbaz.Identifier,
Status: dbaz.Status, Status: dbaz.Status,
Challenges: chs, Challenges: chs,
Wildcard: dbaz.Wildcard, Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt, ExpiresAt: dbaz.ExpiresAt,
ID: dbaz.ID, Token: dbaz.Token,
Error: dbaz.Error,
}, nil }, nil
} }
@ -89,11 +93,12 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e
dbaz := &dbAuthz{ dbaz := &dbAuthz{
ID: az.ID, ID: az.ID,
AccountID: az.AccountID, AccountID: az.AccountID,
Status: acme.StatusPending, Status: az.Status,
CreatedAt: now, CreatedAt: now,
ExpiresAt: now.Add(defaultExpiryDuration), ExpiresAt: az.ExpiresAt,
Identifier: az.Identifier, Identifier: az.Identifier,
Challenges: chIDs, Challenges: chIDs,
Token: az.Token,
Wildcard: az.Wildcard, Wildcard: az.Wildcard,
} }
@ -102,9 +107,6 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e
// UpdateAuthorization saves an updated ACME Authorization to the database. // UpdateAuthorization saves an updated ACME Authorization to the database.
func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) error { func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) error {
if len(az.ID) == 0 {
return errors.New("id cannot be empty")
}
old, err := db.getDBAuthz(ctx, az.ID) old, err := db.getDBAuthz(ctx, az.ID)
if err != nil { if err != nil {
return err return err
@ -113,5 +115,6 @@ func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) e
nu := old.clone() nu := old.clone()
nu.Status = az.Status nu.Status = az.Status
nu.Error = az.Error
return db.save(ctx, old.ID, nu, old, "authz", authzTable) return db.save(ctx, old.ID, nu, old, "authz", authzTable)
} }

620
acme/db/nosql/authz_test.go Normal file
View file

@ -0,0 +1,620 @@
package nosql
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/db"
"github.com/smallstep/nosql"
nosqldb "github.com/smallstep/nosql/database"
)
func TestDB_getDBAuthz(t *testing.T) {
azID := "azID"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
dbaz *dbAuthz
}
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, authzTable)
assert.Equals(t, string(key), azID)
return nil, nosqldb.ErrNotFound
},
},
acmeErr: acme.NewError(acme.ErrorMalformedType, "authz azID 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, authzTable)
assert.Equals(t, string(key), azID)
return nil, errors.New("force")
},
},
err: errors.New("error loading authz azID: 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, authzTable)
assert.Equals(t, string(key), azID)
return []byte("foo"), nil
},
},
err: errors.New("error unmarshaling authz azID into dbAuthz"),
}
},
"ok": func(t *testing.T) test {
now := clock.Now()
dbaz := &dbAuthz{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute),
Error: acme.NewErrorISE("force"),
Challenges: []string{"foo", "bar"},
Wildcard: true,
}
b, err := json.Marshal(dbaz)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), azID)
return b, nil
},
},
dbaz: dbaz,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
db := DB{db: tc.db}
if dbaz, err := db.getDBAuthz(context.Background(), azID); err != nil {
switch k := err.(type) {
case *acme.Error:
if assert.NotNil(t, tc.acmeErr) {
assert.Equals(t, k.Type, tc.acmeErr.Type)
assert.Equals(t, k.Detail, tc.acmeErr.Detail)
assert.Equals(t, k.Status, tc.acmeErr.Status)
assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error())
assert.Equals(t, k.Detail, tc.acmeErr.Detail)
}
default:
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, dbaz.ID, tc.dbaz.ID)
assert.Equals(t, dbaz.AccountID, tc.dbaz.AccountID)
assert.Equals(t, dbaz.Identifier, tc.dbaz.Identifier)
assert.Equals(t, dbaz.Status, tc.dbaz.Status)
assert.Equals(t, dbaz.Token, tc.dbaz.Token)
assert.Equals(t, dbaz.CreatedAt, tc.dbaz.CreatedAt)
assert.Equals(t, dbaz.ExpiresAt, tc.dbaz.ExpiresAt)
assert.Equals(t, dbaz.Error.Error(), tc.dbaz.Error.Error())
assert.Equals(t, dbaz.Wildcard, tc.dbaz.Wildcard)
}
}
})
}
}
func TestDB_GetAuthorization(t *testing.T) {
azID := "azID"
type test struct {
db nosql.DB
err error
acmeErr *acme.Error
dbaz *dbAuthz
}
var tests = map[string]func(t *testing.T) test{
"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, authzTable)
assert.Equals(t, string(key), azID)
return nil, errors.New("force")
},
},
err: errors.New("error loading authz azID: force"),
}
},
"fail/forward-acme-error": func(t *testing.T) test {
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), azID)
return nil, nosqldb.ErrNotFound
},
},
acmeErr: acme.NewError(acme.ErrorMalformedType, "authz azID not found"),
}
},
"fail/db.GetChallenge-error": func(t *testing.T) test {
now := clock.Now()
dbaz := &dbAuthz{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute),
Error: acme.NewErrorISE("force"),
Challenges: []string{"foo", "bar"},
Wildcard: true,
}
b, err := json.Marshal(dbaz)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(authzTable):
assert.Equals(t, string(key), azID)
return b, nil
case string(challengeTable):
assert.Equals(t, string(key), "foo")
return nil, errors.New("force")
default:
assert.FatalError(t, errors.Errorf("unexpected bucket '%s'", string(bucket)))
return nil, errors.New("force")
}
},
},
err: errors.New("error loading acme challenge foo: force"),
}
},
"fail/db.GetChallenge-not-found": func(t *testing.T) test {
now := clock.Now()
dbaz := &dbAuthz{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute),
Error: acme.NewErrorISE("force"),
Challenges: []string{"foo", "bar"},
Wildcard: true,
}
b, err := json.Marshal(dbaz)
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(authzTable):
assert.Equals(t, string(key), azID)
return b, nil
case string(challengeTable):
assert.Equals(t, string(key), "foo")
return nil, nosqldb.ErrNotFound
default:
assert.FatalError(t, errors.Errorf("unexpected bucket '%s'", string(bucket)))
return nil, errors.New("force")
}
},
},
acmeErr: acme.NewError(acme.ErrorMalformedType, "challenge foo not found"),
}
},
"ok": func(t *testing.T) test {
now := clock.Now()
dbaz := &dbAuthz{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute),
Error: acme.NewErrorISE("force"),
Challenges: []string{"foo", "bar"},
Wildcard: true,
}
b, err := json.Marshal(dbaz)
assert.FatalError(t, err)
chCount := 0
fooChb, err := json.Marshal(&dbChallenge{ID: "foo"})
assert.FatalError(t, err)
barChb, err := json.Marshal(&dbChallenge{ID: "bar"})
assert.FatalError(t, err)
return test{
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(authzTable):
assert.Equals(t, string(key), azID)
return b, nil
case string(challengeTable):
if chCount == 0 {
chCount++
assert.Equals(t, string(key), "foo")
return fooChb, nil
}
assert.Equals(t, string(key), "bar")
return barChb, nil
default:
assert.FatalError(t, errors.Errorf("unexpected bucket '%s'", string(bucket)))
return nil, errors.New("force")
}
},
},
dbaz: dbaz,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
db := DB{db: tc.db}
if az, err := db.GetAuthorization(context.Background(), azID); err != nil {
switch k := err.(type) {
case *acme.Error:
if assert.NotNil(t, tc.acmeErr) {
assert.Equals(t, k.Type, tc.acmeErr.Type)
assert.Equals(t, k.Detail, tc.acmeErr.Detail)
assert.Equals(t, k.Status, tc.acmeErr.Status)
assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error())
assert.Equals(t, k.Detail, tc.acmeErr.Detail)
}
default:
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, az.ID, tc.dbaz.ID)
assert.Equals(t, az.AccountID, tc.dbaz.AccountID)
assert.Equals(t, az.Identifier, tc.dbaz.Identifier)
assert.Equals(t, az.Status, tc.dbaz.Status)
assert.Equals(t, az.Token, tc.dbaz.Token)
assert.Equals(t, az.Wildcard, tc.dbaz.Wildcard)
assert.Equals(t, az.ExpiresAt, tc.dbaz.ExpiresAt)
assert.Equals(t, az.Challenges, []*acme.Challenge{
{ID: "foo"},
{ID: "bar"},
})
assert.Equals(t, az.Error.Error(), tc.dbaz.Error.Error())
}
}
})
}
}
func TestDB_CreateAuthorization(t *testing.T) {
azID := "azID"
type test struct {
db nosql.DB
az *acme.Authorization
err error
_id *string
}
var tests = map[string]func(t *testing.T) test{
"fail/cmpAndSwap-error": func(t *testing.T) test {
now := clock.Now()
az := &acme.Authorization{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
ExpiresAt: now.Add(5 * time.Minute),
Challenges: []*acme.Challenge{
{ID: "foo"},
{ID: "bar"},
},
Wildcard: true,
Error: acme.NewErrorISE("force"),
}
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), az.ID)
assert.Equals(t, old, nil)
dbaz := new(dbAuthz)
assert.FatalError(t, json.Unmarshal(nu, dbaz))
assert.Equals(t, dbaz.ID, string(key))
assert.Equals(t, dbaz.AccountID, az.AccountID)
assert.Equals(t, dbaz.Identifier, acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
})
assert.Equals(t, dbaz.Status, az.Status)
assert.Equals(t, dbaz.Token, az.Token)
assert.Equals(t, dbaz.Challenges, []string{"foo", "bar"})
assert.Equals(t, dbaz.Wildcard, az.Wildcard)
assert.Equals(t, dbaz.ExpiresAt, az.ExpiresAt)
assert.Nil(t, dbaz.Error)
assert.True(t, clock.Now().Add(-time.Minute).Before(dbaz.CreatedAt))
assert.True(t, clock.Now().Add(time.Minute).After(dbaz.CreatedAt))
return nil, false, errors.New("force")
},
},
az: az,
err: errors.New("error saving acme authz: force"),
}
},
"ok": func(t *testing.T) test {
var (
id string
idPtr = &id
now = clock.Now()
az = &acme.Authorization{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
ExpiresAt: now.Add(5 * time.Minute),
Challenges: []*acme.Challenge{
{ID: "foo"},
{ID: "bar"},
},
Wildcard: true,
Error: acme.NewErrorISE("force"),
}
)
return test{
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
*idPtr = string(key)
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), az.ID)
assert.Equals(t, old, nil)
dbaz := new(dbAuthz)
assert.FatalError(t, json.Unmarshal(nu, dbaz))
assert.Equals(t, dbaz.ID, string(key))
assert.Equals(t, dbaz.AccountID, az.AccountID)
assert.Equals(t, dbaz.Identifier, acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
})
assert.Equals(t, dbaz.Status, az.Status)
assert.Equals(t, dbaz.Token, az.Token)
assert.Equals(t, dbaz.Challenges, []string{"foo", "bar"})
assert.Equals(t, dbaz.Wildcard, az.Wildcard)
assert.Equals(t, dbaz.ExpiresAt, az.ExpiresAt)
assert.Nil(t, dbaz.Error)
assert.True(t, clock.Now().Add(-time.Minute).Before(dbaz.CreatedAt))
assert.True(t, clock.Now().Add(time.Minute).After(dbaz.CreatedAt))
return nu, true, nil
},
},
az: az,
_id: idPtr,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
db := DB{db: tc.db}
if err := db.CreateAuthorization(context.Background(), tc.az); err != nil {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, tc.az.ID, *tc._id)
}
}
})
}
}
func TestDB_UpdateAuthorization(t *testing.T) {
azID := "azID"
now := clock.Now()
dbaz := &dbAuthz{
ID: azID,
AccountID: "accountID",
Identifier: acme.Identifier{
Type: "dns",
Value: "test.ca.smallstep.com",
},
Status: acme.StatusPending,
Token: "token",
CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute),
Challenges: []string{"foo", "bar"},
Wildcard: true,
}
b, err := json.Marshal(dbaz)
assert.FatalError(t, err)
type test struct {
db nosql.DB
az *acme.Authorization
err error
}
var tests = map[string]func(t *testing.T) test{
"fail/db.Get-error": func(t *testing.T) test {
return test{
az: &acme.Authorization{
ID: azID,
},
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), azID)
return nil, errors.New("force")
},
},
err: errors.New("error loading authz azID: force"),
}
},
"fail/db.CmpAndSwap-error": func(t *testing.T) test {
updAz := &acme.Authorization{
ID: azID,
Status: acme.StatusValid,
Error: acme.NewError(acme.ErrorMalformedType, "malformed"),
}
return test{
az: updAz,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), azID)
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, old, b)
dbOld := new(dbAuthz)
assert.FatalError(t, json.Unmarshal(old, dbOld))
assert.Equals(t, dbaz, dbOld)
dbNew := new(dbAuthz)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbaz.ID)
assert.Equals(t, dbNew.AccountID, dbaz.AccountID)
assert.Equals(t, dbNew.Identifier, dbaz.Identifier)
assert.Equals(t, dbNew.Status, acme.StatusValid)
assert.Equals(t, dbNew.Token, dbaz.Token)
assert.Equals(t, dbNew.Challenges, dbaz.Challenges)
assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard)
assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt)
assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt)
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error())
return nil, false, errors.New("force")
},
},
err: errors.New("error saving acme authz: force"),
}
},
"ok": func(t *testing.T) test {
updAz := &acme.Authorization{
ID: azID,
AccountID: dbaz.AccountID,
Status: acme.StatusValid,
Identifier: dbaz.Identifier,
Challenges: []*acme.Challenge{
{ID: "foo"},
{ID: "bar"},
},
Token: dbaz.Token,
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Error: acme.NewError(acme.ErrorMalformedType, "malformed"),
}
return test{
az: updAz,
db: &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, string(key), azID)
return b, nil
},
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, old, b)
dbOld := new(dbAuthz)
assert.FatalError(t, json.Unmarshal(old, dbOld))
assert.Equals(t, dbaz, dbOld)
dbNew := new(dbAuthz)
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbaz.ID)
assert.Equals(t, dbNew.AccountID, dbaz.AccountID)
assert.Equals(t, dbNew.Identifier, dbaz.Identifier)
assert.Equals(t, dbNew.Status, acme.StatusValid)
assert.Equals(t, dbNew.Token, dbaz.Token)
assert.Equals(t, dbNew.Challenges, dbaz.Challenges)
assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard)
assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt)
assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt)
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error())
return nu, true, nil
},
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
db := DB{db: tc.db}
if err := db.UpdateAuthorization(context.Background(), tc.az); err != nil {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, tc.az.ID, dbaz.ID)
assert.Equals(t, tc.az.AccountID, dbaz.AccountID)
assert.Equals(t, tc.az.Identifier, dbaz.Identifier)
assert.Equals(t, tc.az.Status, acme.StatusValid)
assert.Equals(t, tc.az.Wildcard, dbaz.Wildcard)
assert.Equals(t, tc.az.Token, dbaz.Token)
assert.Equals(t, tc.az.ExpiresAt, dbaz.ExpiresAt)
assert.Equals(t, tc.az.Challenges, []*acme.Challenge{
{ID: "foo"},
{ID: "bar"},
})
assert.Equals(t, tc.az.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error())
}
}
})
}
}

View file

@ -58,7 +58,7 @@ func (db *DB) GetOrder(ctx context.Context, id string) (*acme.Order, error) {
o := &acme.Order{ o := &acme.Order{
Status: dbo.Status, Status: dbo.Status,
Expires: dbo.Expires, ExpiresAt: dbo.Expires,
Identifiers: dbo.Identifiers, Identifiers: dbo.Identifiers,
NotBefore: dbo.NotBefore, NotBefore: dbo.NotBefore,
NotAfter: dbo.NotAfter, NotAfter: dbo.NotAfter,
@ -86,7 +86,7 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
ProvisionerID: o.ProvisionerID, ProvisionerID: o.ProvisionerID,
Created: now, Created: now,
Status: acme.StatusPending, Status: acme.StatusPending,
Expires: o.Expires, Expires: o.ExpiresAt,
Identifiers: o.Identifiers, Identifiers: o.Identifiers,
NotBefore: o.NotBefore, NotBefore: o.NotBefore,
NotAfter: o.NotBefore, NotAfter: o.NotBefore,

View file

@ -22,7 +22,7 @@ type Identifier struct {
type Order struct { type Order struct {
ID string `json:"id"` ID string `json:"id"`
Status Status `json:"status"` Status Status `json:"status"`
Expires time.Time `json:"expires,omitempty"` ExpiresAt time.Time `json:"expires,omitempty"`
Identifiers []Identifier `json:"identifiers"` Identifiers []Identifier `json:"identifiers"`
NotBefore time.Time `json:"notBefore,omitempty"` NotBefore time.Time `json:"notBefore,omitempty"`
NotAfter time.Time `json:"notAfter,omitempty"` NotAfter time.Time `json:"notAfter,omitempty"`
@ -59,7 +59,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
return nil return nil
case StatusReady: case StatusReady:
// Check expiry // Check expiry
if now.After(o.Expires) { if now.After(o.ExpiresAt) {
o.Status = StatusInvalid o.Status = StatusInvalid
o.Error = NewError(ErrorMalformedType, "order has expired") o.Error = NewError(ErrorMalformedType, "order has expired")
break break
@ -67,7 +67,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
return nil return nil
case StatusPending: case StatusPending:
// Check expiry // Check expiry
if now.After(o.Expires) { if now.After(o.ExpiresAt) {
o.Status = StatusInvalid o.Status = StatusInvalid
o.Error = NewError(ErrorMalformedType, "order has expired") o.Error = NewError(ErrorMalformedType, "order has expired")
break break

View file

@ -388,7 +388,7 @@ func TestACMEClient_NewOrder(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
ord := acme.Order{ ord := acme.Order{
Status: "valid", Status: "valid",
Expires: time.Now(), // "soon" ExpiresAt: time.Now(), // "soon"
FinalizeURL: "finalize-url", FinalizeURL: "finalize-url",
} }
ac := &ACMEClient{ ac := &ACMEClient{
@ -510,7 +510,7 @@ func TestACMEClient_GetOrder(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
ord := acme.Order{ ord := acme.Order{
Status: "valid", Status: "valid",
Expires: time.Now(), // "soon" ExpiresAt: time.Now(), // "soon"
FinalizeURL: "finalize-url", FinalizeURL: "finalize-url",
} }
ac := &ACMEClient{ ac := &ACMEClient{
@ -630,7 +630,7 @@ func TestACMEClient_GetAuthz(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
az := acme.Authorization{ az := acme.Authorization{
Status: "valid", Status: "valid",
Expires: time.Now(), ExpiresAt: time.Now(),
Identifier: acme.Identifier{Type: "dns", Value: "example.com"}, Identifier: acme.Identifier{Type: "dns", Value: "example.com"},
} }
ac := &ACMEClient{ ac := &ACMEClient{
@ -988,7 +988,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
ord := acme.Order{ ord := acme.Order{
Status: "valid", Status: "valid",
Expires: time.Now(), // "soon" ExpiresAt: time.Now(), // "soon"
FinalizeURL: "finalize-url", FinalizeURL: "finalize-url",
CertificateURL: "cert-url", CertificateURL: "cert-url",
} }