From bdf4c0f836951e9493103afa710f7889593604fe Mon Sep 17 00:00:00 2001 From: max furman Date: Wed, 24 Mar 2021 20:07:21 -0700 Subject: [PATCH] add acme order unit tests --- acme/order.go | 7 +- acme/order_test.go | 1943 ++++++++------------------------------------ 2 files changed, 358 insertions(+), 1592 deletions(-) diff --git a/acme/order.go b/acme/order.go index 400a4ce2..fdc250ea 100644 --- a/acme/order.go +++ b/acme/order.go @@ -204,12 +204,15 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques Intermediates: certChain[1:], } if err := db.CreateCertificate(ctx, cert); err != nil { - return err + return WrapErrorISE(err, "error creating certificate for order %s", o.ID) } o.CertificateID = cert.ID o.Status = StatusValid - return db.UpdateOrder(ctx, o) + if err = db.UpdateOrder(ctx, o); err != nil { + return WrapErrorISE(err, "error updating order %s", o.ID) + } + return nil } // uniqueSortedLowerNames returns the set of all unique names in the input after all diff --git a/acme/order_test.go b/acme/order_test.go index d86afeb5..993a92f2 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -2,11 +2,15 @@ package acme import ( "context" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" "testing" "time" "github.com/pkg/errors" "github.com/smallstep/assert" + "github.com/smallstep/certificates/authority/provisioner" ) func TestOrder_UpdateStatus(t *testing.T) { @@ -256,882 +260,6 @@ func TestOrder_UpdateStatus(t *testing.T) { } } -/* -var certDuration = 6 * time.Hour - -func defaultOrderOps() OrderOptions { - return OrderOptions{ - AccountID: "accID", - Identifiers: []Identifier{ - {Type: "dns", Value: "acme.example.com"}, - {Type: "dns", Value: "step.example.com"}, - }, - NotBefore: clock.Now(), - NotAfter: clock.Now().Add(certDuration), - } -} - -func newO() (*order, error) { - mockdb := &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return []byte("foo"), true, nil - }, - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - } - return newOrder(mockdb, defaultOrderOps()) -} - -func Test_getOrder(t *testing.T) { - type test struct { - id string - db nosql.DB - o *order - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/not-found": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{ - o: o, - id: o.ID, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - }, - err: MalformedErr(errors.Errorf("order %s not found: not found", o.ID)), - } - }, - "fail/db-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{ - o: o, - id: o.ID, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, errors.New("force") - }, - }, - err: ServerInternalErr(errors.Errorf("error loading order %s: force", o.ID)), - } - }, - "fail/unmarshal-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{ - o: o, - id: o.ID, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return nil, nil - }, - }, - err: ServerInternalErr(errors.New("error unmarshaling order: unexpected end of JSON input")), - } - }, - "ok": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - return test{ - o: o, - id: o.ID, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - }, - }, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if o, err := getOrder(tc.db, tc.id); err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - assert.Equals(t, tc.o.ID, o.ID) - assert.Equals(t, tc.o.AccountID, o.AccountID) - assert.Equals(t, tc.o.Status, o.Status) - assert.Equals(t, tc.o.Identifiers, o.Identifiers) - assert.Equals(t, tc.o.Created, o.Created) - assert.Equals(t, tc.o.Expires, o.Expires) - assert.Equals(t, tc.o.Authorizations, o.Authorizations) - assert.Equals(t, tc.o.NotBefore, o.NotBefore) - assert.Equals(t, tc.o.NotAfter, o.NotAfter) - assert.Equals(t, tc.o.Certificate, o.Certificate) - assert.Equals(t, tc.o.Error, o.Error) - } - } - }) - } -} - -func TestOrderToACME(t *testing.T) { - dir := newDirectory("ca.smallstep.com", "acme") - prov := newProv() - provName := url.PathEscape(prov.GetName()) - baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"} - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, baseURL) - - type test struct { - o *order - err *Error - } - tests := map[string]func(t *testing.T) test{ - "ok/no-cert": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{o: o} - }, - "ok/cert": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusValid - o.Certificate = "cert-id" - return test{o: o} - }, - } - for name, run := range tests { - tc := run(t) - t.Run(name, func(t *testing.T) { - acmeOrder, err := tc.o.toACME(ctx, nil, dir) - if err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - assert.Equals(t, acmeOrder.ID, tc.o.ID) - assert.Equals(t, acmeOrder.Status, tc.o.Status) - assert.Equals(t, acmeOrder.Identifiers, tc.o.Identifiers) - assert.Equals(t, acmeOrder.Finalize, - fmt.Sprintf("%s/acme/%s/order/%s/finalize", baseURL.String(), provName, tc.o.ID)) - if tc.o.Certificate != "" { - assert.Equals(t, acmeOrder.Certificate, fmt.Sprintf("%s/acme/%s/certificate/%s", baseURL.String(), provName, tc.o.Certificate)) - } - - expiry, err := time.Parse(time.RFC3339, acmeOrder.Expires) - assert.FatalError(t, err) - assert.Equals(t, expiry.String(), tc.o.Expires.String()) - nbf, err := time.Parse(time.RFC3339, acmeOrder.NotBefore) - assert.FatalError(t, err) - assert.Equals(t, nbf.String(), tc.o.NotBefore.String()) - naf, err := time.Parse(time.RFC3339, acmeOrder.NotAfter) - assert.FatalError(t, err) - assert.Equals(t, naf.String(), tc.o.NotAfter.String()) - } - } - }) - } -} - -func TestOrderSave(t *testing.T) { - type test struct { - o, old *order - db nosql.DB - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/old-nil/swap-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{ - o: o, - old: nil, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, - err: ServerInternalErr(errors.New("error storing order: force")), - } - }, - "fail/old-nil/swap-false": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{ - o: o, - old: nil, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return []byte("foo"), false, nil - }, - }, - err: ServerInternalErr(errors.New("error storing order; value has changed since last read")), - } - }, - "ok/old-nil": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - return test{ - o: o, - old: nil, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, old, nil) - assert.Equals(t, b, newval) - assert.Equals(t, bucket, orderTable) - assert.Equals(t, []byte(o.ID), key) - return nil, true, nil - }, - }, - } - }, - "ok/old-not-nil": func(t *testing.T) test { - oldo, err := newO() - assert.FatalError(t, err) - o, err := newO() - assert.FatalError(t, err) - - oldb, err := json.Marshal(oldo) - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - return test{ - o: o, - old: oldo, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, old, oldb) - assert.Equals(t, b, newval) - assert.Equals(t, bucket, orderTable) - assert.Equals(t, []byte(o.ID), key) - return []byte("foo"), true, nil - }, - }, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if err := tc.o.save(tc.db, tc.old); err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - assert.Nil(t, tc.err) - } - }) - } -} - -func Test_newOrder(t *testing.T) { - type test struct { - ops OrderOptions - db nosql.DB - err *Error - authzs *([]string) - } - tests := map[string]func(t *testing.T) test{ - "fail/unexpected-identifier-type": func(t *testing.T) test { - ops := defaultOrderOps() - ops.Identifiers[0].Type = "foo" - return test{ - ops: ops, - err: MalformedErr(errors.New("unexpected authz type foo")), - } - }, - "fail/save-order-error": func(t *testing.T) test { - count := 0 - return test{ - ops: defaultOrderOps(), - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 8 { - return nil, false, errors.New("force") - } - count++ - return nil, true, nil - }, - }, - err: ServerInternalErr(errors.New("error storing order: force")), - } - }, - "fail/get-orderIDs-error": func(t *testing.T) test { - count := 0 - ops := defaultOrderOps() - return test{ - ops: ops, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { - return nil, false, errors.New("force") - } - count++ - return nil, true, nil - }, - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, errors.New("force") - }, - }, - err: ServerInternalErr(errors.Errorf("error loading orderIDs for account %s: force", ops.AccountID)), - } - }, - "fail/save-orderIDs-error": func(t *testing.T) test { - count := 0 - var ( - _oid = "" - oid = &_oid - ) - ops := defaultOrderOps() - return test{ - ops: ops, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(ops.AccountID)) - return nil, false, errors.New("force") - } else if count == 8 { - *oid = string(key) - } - count++ - return nil, true, nil - }, - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - MDel: func(bucket, key []byte) error { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(*oid)) - return nil - }, - }, - err: ServerInternalErr(errors.Errorf("error storing order IDs for account %s: force", ops.AccountID)), - } - }, - "ok": func(t *testing.T) test { - count := 0 - authzs := &([]string{}) - var ( - _oid = "" - oid = &_oid - ) - ops := defaultOrderOps() - return test{ - ops: ops, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(ops.AccountID)) - assert.Equals(t, old, nil) - newB, err := json.Marshal([]string{*oid}) - assert.FatalError(t, err) - assert.Equals(t, newval, newB) - } else if count == 8 { - *oid = string(key) - } else if count == 7 { - *authzs = append(*authzs, string(key)) - } else if count == 3 { - *authzs = []string{string(key)} - } - count++ - return nil, true, nil - }, - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - }, - authzs: authzs, - } - }, - "ok/validity-bounds-not-set": func(t *testing.T) test { - count := 0 - authzs := &([]string{}) - var ( - _oid = "" - oid = &_oid - ) - ops := defaultOrderOps() - ops.backdate = time.Minute - ops.defaultDuration = 12 * time.Hour - ops.NotBefore = time.Time{} - ops.NotAfter = time.Time{} - return test{ - ops: ops, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(ops.AccountID)) - assert.Equals(t, old, nil) - newB, err := json.Marshal([]string{*oid}) - assert.FatalError(t, err) - assert.Equals(t, newval, newB) - } else if count == 8 { - *oid = string(key) - } else if count == 7 { - *authzs = append(*authzs, string(key)) - } else if count == 3 { - *authzs = []string{string(key)} - } - count++ - return nil, true, nil - }, - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - }, - authzs: authzs, - } - }, - } - for name, run := range tests { - tc := run(t) - t.Run(name, func(t *testing.T) { - o, err := newOrder(tc.db, tc.ops) - if err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - assert.Equals(t, o.AccountID, tc.ops.AccountID) - assert.Equals(t, o.Status, StatusPending) - assert.Equals(t, o.Identifiers, tc.ops.Identifiers) - assert.Equals(t, o.Error, nil) - assert.Equals(t, o.Certificate, "") - assert.Equals(t, o.Authorizations, *tc.authzs) - - assert.True(t, o.Created.Before(time.Now().UTC().Add(time.Minute))) - assert.True(t, o.Created.After(time.Now().UTC().Add(-1*time.Minute))) - - expiry := o.Created.Add(defaultExpiryDuration) - assert.True(t, o.Expires.Before(expiry.Add(time.Minute))) - assert.True(t, o.Expires.After(expiry.Add(-1*time.Minute))) - - nbf := tc.ops.NotBefore - now := time.Now().UTC() - if !tc.ops.NotBefore.IsZero() { - assert.Equals(t, o.NotBefore, tc.ops.NotBefore) - } else { - nbf = o.NotBefore.Add(tc.ops.backdate) - assert.True(t, o.NotBefore.Before(now.Add(-tc.ops.backdate+time.Second))) - assert.True(t, o.NotBefore.Add(tc.ops.backdate+2*time.Second).After(now)) - } - if !tc.ops.NotAfter.IsZero() { - assert.Equals(t, o.NotAfter, tc.ops.NotAfter) - } else { - naf := nbf.Add(tc.ops.defaultDuration) - assert.Equals(t, o.NotAfter, naf) - } - } - } - }) - } -} - -func TestOrderIDs_save(t *testing.T) { - accID := "acc-id" - newOids := func() orderIDs { - return []string{"1", "2"} - } - type test struct { - oids, old orderIDs - db nosql.DB - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/old-nil/swap-error": func(t *testing.T) test { - return test{ - oids: newOids(), - old: nil, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, - err: ServerInternalErr(errors.Errorf("error storing order IDs for account %s: force", accID)), - } - }, - "fail/old-nil/swap-false": func(t *testing.T) test { - return test{ - oids: newOids(), - old: nil, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return []byte("foo"), false, nil - }, - }, - err: ServerInternalErr(errors.Errorf("error storing order IDs for account %s; order IDs changed since last read", accID)), - } - }, - "ok/old-nil": func(t *testing.T) test { - oids := newOids() - b, err := json.Marshal(oids) - assert.FatalError(t, err) - return test{ - oids: oids, - old: nil, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, old, nil) - assert.Equals(t, b, newval) - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(accID)) - return nil, true, nil - }, - }, - } - }, - "ok/old-not-nil": func(t *testing.T) test { - oldOids := newOids() - oids := append(oldOids, "3") - - oldb, err := json.Marshal(oldOids) - assert.FatalError(t, err) - b, err := json.Marshal(oids) - assert.FatalError(t, err) - return test{ - oids: oids, - old: oldOids, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, old, oldb) - assert.Equals(t, newval, b) - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(accID)) - return nil, true, nil - }, - }, - } - }, - "ok/new-empty-saved-as-nil": func(t *testing.T) test { - oldOids := newOids() - oids := []string{} - - oldb, err := json.Marshal(oldOids) - assert.FatalError(t, err) - return test{ - oids: oids, - old: oldOids, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, old, oldb) - assert.Equals(t, newval, nil) - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(accID)) - return nil, true, nil - }, - }, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if err := tc.oids.save(tc.db, tc.old, accID); err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - assert.Nil(t, tc.err) - } - }) - } -} - -func TestOrderUpdateStatus(t *testing.T) { - type test struct { - o, res *order - err *Error - db nosql.DB - } - tests := map[string]func(t *testing.T) test{ - "fail/already-invalid": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusInvalid - return test{ - o: o, - res: o, - } - }, - "fail/already-valid": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusValid - return test{ - o: o, - res: o, - } - }, - "fail/unexpected-status": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusDeactivated - return test{ - o: o, - res: o, - err: ServerInternalErr(errors.New("unrecognized order status: deactivated")), - } - }, - "fail/save-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Expires = time.Now().UTC().Add(-time.Minute) - return test{ - o: o, - res: o, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, - err: ServerInternalErr(errors.New("error storing order: force")), - } - }, - "ok/expired": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Expires = time.Now().UTC().Add(-time.Minute) - - _o := *o - clone := &_o - clone.Error = MalformedErr(errors.New("order has expired")) - clone.Status = StatusInvalid - return test{ - o: o, - res: clone, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, true, nil - }, - }, - } - }, - "fail/get-authz-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - return test{ - o: o, - res: o, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, errors.New("force") - }, - }, - err: ServerInternalErr(errors.New("error loading authz")), - } - }, - "ok/still-pending": func(t *testing.T) test { - az1, err := newAz() - assert.FatalError(t, err) - az2, err := newAz() - assert.FatalError(t, err) - az3, err := newAz() - assert.FatalError(t, err) - - ch1, err := newHTTPCh() - assert.FatalError(t, err) - ch2, err := newTLSALPNCh() - assert.FatalError(t, err) - ch3, err := newDNSCh() - assert.FatalError(t, err) - - ch1b, err := json.Marshal(ch1) - assert.FatalError(t, err) - ch2b, err := json.Marshal(ch2) - assert.FatalError(t, err) - ch3b, err := json.Marshal(ch3) - assert.FatalError(t, err) - - o, err := newO() - assert.FatalError(t, err) - o.Authorizations = []string{az1.getID(), az2.getID(), az3.getID()} - - _az3, ok := az3.(*dnsAuthz) - assert.Fatal(t, ok) - _az3.baseAuthz.Status = StatusValid - - b1, err := json.Marshal(az1) - assert.FatalError(t, err) - b2, err := json.Marshal(az2) - assert.FatalError(t, err) - b3, err := json.Marshal(az3) - assert.FatalError(t, err) - - count := 0 - return test{ - o: o, - res: o, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - ret = b1 - case 1: - ret = ch1b - case 2: - ret = ch2b - case 3: - ret = ch3b - case 4: - ret = b2 - case 5: - ret = ch1b - case 6: - ret = ch2b - case 7: - ret = ch3b - case 8: - ret = b3 - default: - return nil, errors.New("unexpected count") - } - count++ - return ret, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, true, nil - }, - }, - } - }, - "ok/invalid": func(t *testing.T) test { - az1, err := newAz() - assert.FatalError(t, err) - az2, err := newAz() - assert.FatalError(t, err) - az3, err := newAz() - assert.FatalError(t, err) - - ch1, err := newHTTPCh() - assert.FatalError(t, err) - ch2, err := newTLSALPNCh() - assert.FatalError(t, err) - ch3, err := newDNSCh() - assert.FatalError(t, err) - - ch1b, err := json.Marshal(ch1) - assert.FatalError(t, err) - ch2b, err := json.Marshal(ch2) - assert.FatalError(t, err) - ch3b, err := json.Marshal(ch3) - assert.FatalError(t, err) - - o, err := newO() - assert.FatalError(t, err) - o.Authorizations = []string{az1.getID(), az2.getID(), az3.getID()} - - _az3, ok := az3.(*dnsAuthz) - assert.Fatal(t, ok) - _az3.baseAuthz.Status = StatusInvalid - - b1, err := json.Marshal(az1) - assert.FatalError(t, err) - b2, err := json.Marshal(az2) - assert.FatalError(t, err) - b3, err := json.Marshal(az3) - assert.FatalError(t, err) - - _o := *o - clone := &_o - clone.Status = StatusInvalid - - count := 0 - return test{ - o: o, - res: clone, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - ret = b1 - case 1: - ret = ch1b - case 2: - ret = ch2b - case 3: - ret = ch3b - case 4: - ret = b2 - case 5: - ret = ch1b - case 6: - ret = ch2b - case 7: - ret = ch3b - case 8: - ret = b3 - default: - return nil, errors.New("unexpected count") - } - count++ - return ret, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, true, nil - }, - }, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - o, err := tc.o.updateStatus(tc.db) - if err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - expB, err := json.Marshal(tc.res) - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - assert.Equals(t, expB, b) - } - } - }) - } -} - type mockSignAuth struct { sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) loadProvisionerByID func(string) (provisioner.Interface, error) @@ -1155,822 +283,457 @@ func (m *mockSignAuth) LoadProvisionerByID(id string) (provisioner.Interface, er return m.ret1.(provisioner.Interface), m.err } -func TestOrderFinalize(t *testing.T) { - prov := newProv() +func TestOrder_Finalize(t *testing.T) { type test struct { - o, res *order - err *Error - db nosql.DB - csr *x509.CertificateRequest - sa SignAuthority - prov Provisioner + o *Order + err *Error + db DB + ca CertificateAuthority + csr *x509.CertificateRequest + prov Provisioner } tests := map[string]func(t *testing.T) test{ - "fail/already-invalid": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusInvalid + "fail/invalid": func(t *testing.T) test { + o := &Order{ + ID: "oid", + Status: StatusInvalid, + } return test{ o: o, - err: OrderNotReadyErr(errors.Errorf("order %s has been abandoned", o.ID)), + err: NewError(ErrorOrderNotReadyType, "order %s has been abandoned", o.ID), + } + }, + "fail/pending": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusPending, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + } + az1 := &Authorization{ + ID: "a", + Status: StatusValid, + } + az2 := &Authorization{ + ID: "b", + Status: StatusPending, + ExpiresAt: now.Add(5 * time.Minute), + } + + return test{ + o: o, + db: &MockDB{ + MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { + switch id { + case az1.ID: + return az1, nil + case az2.ID: + return az2, nil + default: + assert.FatalError(t, errors.Errorf("unexpected authz key %s", id)) + return nil, errors.New("force") + } + }, + }, + err: NewError(ErrorOrderNotReadyType, "order %s is not ready", o.ID), } }, "ok/already-valid": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusValid - o.Certificate = "cert-id" + o := &Order{ + ID: "oid", + Status: StatusValid, + } return test{ - o: o, - res: o, + o: o, } }, - "fail/still-pending": func(t *testing.T) test { - az1, err := newAz() - assert.FatalError(t, err) - az2, err := newAz() - assert.FatalError(t, err) - az3, err := newAz() - assert.FatalError(t, err) + "fail/error-unexpected-status": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: "foo", + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + } - ch1, err := newHTTPCh() - assert.FatalError(t, err) - ch2, err := newTLSALPNCh() - assert.FatalError(t, err) - ch3, err := newDNSCh() - assert.FatalError(t, err) - - ch1b, err := json.Marshal(ch1) - assert.FatalError(t, err) - ch2b, err := json.Marshal(ch2) - assert.FatalError(t, err) - ch3b, err := json.Marshal(ch3) - assert.FatalError(t, err) - - o, err := newO() - assert.FatalError(t, err) - o.Authorizations = []string{az1.getID(), az2.getID(), az3.getID()} - - _az3, ok := az3.(*dnsAuthz) - assert.Fatal(t, ok) - _az3.baseAuthz.Status = StatusValid - - b1, err := json.Marshal(az1) - assert.FatalError(t, err) - b2, err := json.Marshal(az2) - assert.FatalError(t, err) - b3, err := json.Marshal(az3) - assert.FatalError(t, err) - - count := 0 return test{ o: o, - res: o, - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - ret = b1 - case 1: - ret = ch1b - case 2: - ret = ch2b - case 3: - ret = ch3b - case 4: - ret = b2 - case 5: - ret = ch1b - case 6: - ret = ch2b - case 7: - ret = ch3b - case 8: - ret = b3 - default: - return nil, errors.New("unexpected count") - } - count++ - return ret, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, true, nil - }, + err: NewErrorISE("unrecognized order status: %s", o.Status), + } + }, + "fail/error-names-length-mismatch": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, }, - err: OrderNotReadyErr(errors.Errorf("order %s is not ready", o.ID)), } - }, - "fail/ready/csr-names-match-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - + orderNames := []string{"bar.internal", "foo.internal"} csr := &x509.CertificateRequest{ Subject: pkix.Name{ - CommonName: "acme.example.com", + CommonName: "foo.internal", }, - DNSNames: []string{"acme.example.com", "fail.smallstep.com"}, } + return test{ o: o, csr: csr, - err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), + err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", []string{"foo.internal"}, orderNames), } }, - "fail/ready/csr-names-match-error-2": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - + "fail/error-names-mismatch": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + } + orderNames := []string{"bar.internal", "foo.internal"} csr := &x509.CertificateRequest{ Subject: pkix.Name{ - CommonName: "", + CommonName: "foo.internal", }, - DNSNames: []string{"acme.example.com"}, + DNSNames: []string{"zap.internal"}, } + return test{ o: o, csr: csr, - err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), + err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+ + "CSR names = %v, Order names = %v", []string{"foo.internal", "zap.internal"}, orderNames), } }, - "fail/ready/no-ipAddresses": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - + "fail/error-provisioner-auth": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + } csr := &x509.CertificateRequest{ Subject: pkix.Name{ - CommonName: "", + CommonName: "foo.internal", }, - // DNSNames: []string{"acme.example.com", "step.example.com"}, - IPAddresses: []net.IP{net.ParseIP("1.1.1.1")}, + DNSNames: []string{"bar.internal"}, } + return test{ o: o, csr: csr, - err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), - } - }, - "fail/ready/no-emailAddresses": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "", - }, - // DNSNames: []string{"acme.example.com", "step.example.com"}, - EmailAddresses: []string{"max@smallstep.com", "mariano@smallstep.com"}, - } - return test{ - o: o, - csr: csr, - err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), - } - }, - "fail/ready/no-URIs": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - u, err := url.Parse("https://google.com") - assert.FatalError(t, err) - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "", - }, - // DNSNames: []string{"acme.example.com", "step.example.com"}, - URIs: []*url.URL{u}, - } - return test{ - o: o, - csr: csr, - err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), - } - }, - "fail/ready/provisioner-auth-sign-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - DNSNames: []string{"step.example.com", "acme.example.com"}, - } - return test{ - o: o, - csr: csr, - err: ServerInternalErr(errors.New("error retrieving authorization options from ACME provisioner: force")), prov: &MockProvisioner{ MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") return nil, errors.New("force") }, }, + err: NewErrorISE("error retrieving authorization options from ACME provisioner: force"), } }, - "fail/ready/sign-cert-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - + "fail/error-template-options": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + } csr := &x509.CertificateRequest{ Subject: pkix.Name{ - CommonName: "acme.example.com", + CommonName: "foo.internal", }, - DNSNames: []string{"step.example.com", "acme.example.com"}, + DNSNames: []string{"bar.internal"}, } + return test{ o: o, csr: csr, - err: ServerInternalErr(errors.Errorf("error generating certificate for order %s: force", o.ID)), - sa: &mockSignAuth{ - err: errors.New("force"), - }, - } - }, - "fail/ready/store-cert-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - DNSNames: []string{"step.example.com", "acme.example.com"}, - } - crt := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - } - inter := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "intermediate", - }, - } - return test{ - o: o, - csr: csr, - err: ServerInternalErr(errors.Errorf("error storing certificate: force")), - sa: &mockSignAuth{ - ret1: crt, ret2: inter, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, - } - }, - "fail/ready/store-order-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - DNSNames: []string{"acme.example.com", "step.example.com"}, - } - crt := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - } - inter := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "intermediate", - }, - } - count := 0 - return test{ - o: o, - csr: csr, - err: ServerInternalErr(errors.Errorf("error storing order: force")), - sa: &mockSignAuth{ - ret1: crt, ret2: inter, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 1 { - return nil, false, errors.New("force") - } - count++ - return nil, true, nil - }, - }, - } - }, - "ok/ready/sign": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - DNSNames: []string{"acme.example.com", "step.example.com"}, - } - crt := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - } - inter := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "intermediate", - }, - } - - _o := *o - clone := &_o - clone.Status = StatusValid - - count := 0 - return test{ - o: o, - res: clone, - csr: csr, - sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { - assert.Equals(t, len(signOps), 6) - return []*x509.Certificate{crt, inter}, nil - }, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 0 { - clone.Certificate = string(key) - } - count++ - return nil, true, nil - }, - }, - } - }, - "ok/ready/no-sans": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - o.Identifiers = []Identifier{ - {Type: "dns", Value: "step.example.com"}, - } - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "step.example.com", - }, - } - crt := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "step.example.com", - }, - DNSNames: []string{"step.example.com"}, - } - inter := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "intermediate", - }, - } - - clone := *o - clone.Status = StatusValid - count := 0 - return test{ - o: o, - res: &clone, - csr: csr, - sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { - assert.Equals(t, len(signOps), 6) - return []*x509.Certificate{crt, inter}, nil - }, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 0 { - clone.Certificate = string(key) - } - count++ - return nil, true, nil - }, - }, - } - }, - "ok/ready/sans-and-name": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusReady - - csr := &x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - DNSNames: []string{"step.example.com"}, - } - crt := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "acme.example.com", - }, - DNSNames: []string{"acme.example.com", "step.example.com"}, - } - inter := &x509.Certificate{ - Subject: pkix.Name{ - CommonName: "intermediate", - }, - } - - clone := *o - clone.Status = StatusValid - count := 0 - return test{ - o: o, - res: &clone, - csr: csr, - sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { - assert.Equals(t, len(signOps), 6) - return []*x509.Certificate{crt, inter}, nil - }, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 0 { - clone.Certificate = string(key) - } - count++ - return nil, true, nil - }, - }, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - p := tc.prov - if p == nil { - p = prov - } - o, err := tc.o.finalize(tc.db, tc.csr, tc.sa, p) - if err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - expB, err := json.Marshal(tc.res) - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - assert.Equals(t, expB, b) - } - } - }) - } -} - -func Test_getOrderIDsByAccount(t *testing.T) { - type test struct { - id string - db nosql.DB - res []string - err *Error - } - tests := map[string]func(t *testing.T) test{ - "ok/not-found": func(t *testing.T) test { - return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - }, - res: []string{}, - } - }, - "fail/db-error": func(t *testing.T) test { - return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, errors.New("force") - }, - }, - err: ServerInternalErr(errors.New("error loading orderIDs for account foo: force")), - } - }, - "fail/unmarshal-error": func(t *testing.T) test { - return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte("foo")) + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") return nil, nil }, - }, - err: ServerInternalErr(errors.New("error unmarshaling orderIDs for account foo: unexpected end of JSON input")), - } - }, - "fail/error-loading-order-from-order-IDs": func(t *testing.T) test { - oids := []string{"o1", "o2", "o3"} - boids, err := json.Marshal(oids) - assert.FatalError(t, err) - dbHit := 0 - return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - dbHit++ - switch dbHit { - case 1: - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte("foo")) - return boids, nil - case 2: - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte("o1")) - return nil, errors.New("force") - default: - assert.FatalError(t, errors.New("should not be here")) - return nil, nil + MgetOptions: func() *provisioner.Options { + return &provisioner.Options{ + X509: &provisioner.X509Options{ + TemplateData: json.RawMessage([]byte("fo{o")), + }, } }, }, - err: ServerInternalErr(errors.New("error loading order o1 for account foo: error loading order o1: force")), + err: NewErrorISE("error creating template options from ACME provisioner: error unmarshaling template data: invalid character 'o' in literal false (expecting 'a')"), } }, - "fail/error-updating-order-from-order-IDs": func(t *testing.T) test { - oids := []string{"o1", "o2", "o3"} - boids, err := json.Marshal(oids) - assert.FatalError(t, err) + "fail/error-ca-sign": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + } + csr := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + DNSNames: []string{"bar.internal"}, + } - o, err := newO() - assert.FatalError(t, err) - bo, err := json.Marshal(o) - assert.FatalError(t, err) - - dbHit := 0 return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - dbHit++ - switch dbHit { - case 1: - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte("foo")) - return boids, nil - case 2: - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte("o1")) - return bo, nil - case 3: - assert.Equals(t, bucket, authzTable) - assert.Equals(t, key, []byte(o.Authorizations[0])) - return nil, errors.New("force") - default: - assert.FatalError(t, errors.New("should not be here")) - return nil, nil - } + o: o, + csr: csr, + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") + return nil, nil + }, + MgetOptions: func() *provisioner.Options { + return nil }, }, - err: ServerInternalErr(errors.Errorf("error updating order o1 for account foo: error loading authz %s: force", o.Authorizations[0])), + ca: &mockSignAuth{ + sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, _csr, csr) + return nil, errors.New("force") + }, + }, + err: NewErrorISE("error signing certificate for order oID: force"), } }, - "ok/no-change-to-pending-orders": func(t *testing.T) test { - oids := []string{"o1", "o2", "o3"} - boids, err := json.Marshal(oids) - assert.FatalError(t, err) + "fail/error-db.CreateCertificate": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + } + csr := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + DNSNames: []string{"bar.internal"}, + } - o, err := newO() - assert.FatalError(t, err) - bo, err := json.Marshal(o) - assert.FatalError(t, err) - - az, err := newAz() - assert.FatalError(t, err) - baz, err := json.Marshal(az) - assert.FatalError(t, err) - - ch, err := newDNSCh() - assert.FatalError(t, err) - bch, err := json.Marshal(ch) - assert.FatalError(t, err) + foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}} + bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} + baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - switch string(bucket) { - case string(ordersByAccountIDTable): - assert.Equals(t, key, []byte("foo")) - return boids, nil - case string(orderTable): - return bo, nil - case string(authzTable): - return baz, nil - case string(challengeTable): - return bch, nil - default: - assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket)) - return nil, nil - } + o: o, + csr: csr, + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") + return nil, nil }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("should not be attempting to store anything") + MgetOptions: func() *provisioner.Options { + return nil }, }, - res: oids, + ca: &mockSignAuth{ + sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, _csr, csr) + return []*x509.Certificate{foo, bar, baz}, nil + }, + }, + db: &MockDB{ + MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { + assert.Equals(t, cert.AccountID, o.AccountID) + assert.Equals(t, cert.OrderID, o.ID) + assert.Equals(t, cert.Leaf, foo) + assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz}) + return errors.New("force") + }, + }, + err: NewErrorISE("error creating certificate for order oID: force"), } }, - "fail/error-storing-new-oids": func(t *testing.T) test { - oids := []string{"o1", "o2", "o3"} - boids, err := json.Marshal(oids) - assert.FatalError(t, err) + "fail/error-db.UpdateOrder": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, + }, + } + csr := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + DNSNames: []string{"bar.internal"}, + } - o, err := newO() - assert.FatalError(t, err) - bo, err := json.Marshal(o) - assert.FatalError(t, err) + foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}} + bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} + baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} - invalidOrder, err := newO() - assert.FatalError(t, err) - invalidOrder.Status = StatusInvalid - binvalidOrder, err := json.Marshal(invalidOrder) - assert.FatalError(t, err) - - az, err := newAz() - assert.FatalError(t, err) - baz, err := json.Marshal(az) - assert.FatalError(t, err) - - ch, err := newDNSCh() - assert.FatalError(t, err) - bch, err := json.Marshal(ch) - assert.FatalError(t, err) - - dbGetOrder := 0 return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - switch string(bucket) { - case string(ordersByAccountIDTable): - assert.Equals(t, key, []byte("foo")) - return boids, nil - case string(orderTable): - dbGetOrder++ - if dbGetOrder == 1 { - return binvalidOrder, nil - } - return bo, nil - case string(authzTable): - return baz, nil - case string(challengeTable): - return bch, nil - default: - assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket)) - return nil, nil - } + o: o, + csr: csr, + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") + return nil, nil }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte("foo")) - return nil, false, errors.New("force") + MgetOptions: func() *provisioner.Options { + return nil }, }, - err: ServerInternalErr(errors.New("error storing orderIDs as part of getOrderIDsByAccount logic: len(orderIDs) = 2: error storing order IDs for account foo: force")), + ca: &mockSignAuth{ + sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, _csr, csr) + return []*x509.Certificate{foo, bar, baz}, nil + }, + }, + db: &MockDB{ + MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { + cert.ID = "certID" + assert.Equals(t, cert.AccountID, o.AccountID) + assert.Equals(t, cert.OrderID, o.ID) + assert.Equals(t, cert.Leaf, foo) + assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz}) + return nil + }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "certID") + assert.Equals(t, updo.Status, StatusValid) + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return errors.New("force") + }, + }, + err: NewErrorISE("error updating order oID: force"), } }, - "ok": func(t *testing.T) test { - oids := []string{"o1", "o2", "o3", "o4"} - boids, err := json.Marshal(oids) - assert.FatalError(t, err) - - o, err := newO() - assert.FatalError(t, err) - bo, err := json.Marshal(o) - assert.FatalError(t, err) - - invalidOrder, err := newO() - assert.FatalError(t, err) - invalidOrder.Status = StatusInvalid - binvalidOrder, err := json.Marshal(invalidOrder) - assert.FatalError(t, err) - - az, err := newAz() - assert.FatalError(t, err) - baz, err := json.Marshal(az) - assert.FatalError(t, err) - - ch, err := newDNSCh() - assert.FatalError(t, err) - bch, err := json.Marshal(ch) - assert.FatalError(t, err) - - dbGetOrder := 0 - return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - switch string(bucket) { - case string(ordersByAccountIDTable): - assert.Equals(t, key, []byte("foo")) - return boids, nil - case string(orderTable): - dbGetOrder++ - if dbGetOrder == 1 || dbGetOrder == 3 { - return binvalidOrder, nil - } - return bo, nil - case string(authzTable): - return baz, nil - case string(challengeTable): - return bch, nil - default: - assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket)) - return nil, nil - } - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte("foo")) - return nil, true, nil - }, + "ok/new-cert": func(t *testing.T) test { + now := clock.Now() + o := &Order{ + ID: "oID", + AccountID: "accID", + Status: StatusReady, + ExpiresAt: now.Add(5 * time.Minute), + AuthorizationIDs: []string{"a", "b"}, + Identifiers: []Identifier{ + {Type: "dns", Value: "foo.internal"}, + {Type: "dns", Value: "bar.internal"}, }, - res: []string{"o2", "o4"}, } - }, - "ok/no-pending-orders": func(t *testing.T) test { - oids := []string{"o1"} - boids, err := json.Marshal(oids) - assert.FatalError(t, err) + csr := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.internal", + }, + DNSNames: []string{"bar.internal"}, + } - invalidOrder, err := newO() - assert.FatalError(t, err) - invalidOrder.Status = StatusInvalid - binvalidOrder, err := json.Marshal(invalidOrder) - assert.FatalError(t, err) + foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}} + bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} + baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} return test{ - id: "foo", - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - switch string(bucket) { - case string(ordersByAccountIDTable): - assert.Equals(t, key, []byte("foo")) - return boids, nil - case string(orderTable): - return binvalidOrder, nil - default: - assert.FatalError(t, errors.Errorf("did not expect query to table %s", bucket)) - return nil, nil - } + o: o, + csr: csr, + prov: &MockProvisioner{ + MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) { + assert.Equals(t, token, "") + return nil, nil }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte("foo")) - assert.Equals(t, old, boids) - assert.Nil(t, newval) - return nil, true, nil + MgetOptions: func() *provisioner.Options { + return nil + }, + }, + ca: &mockSignAuth{ + sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, _csr, csr) + return []*x509.Certificate{foo, bar, baz}, nil + }, + }, + db: &MockDB{ + MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { + cert.ID = "certID" + assert.Equals(t, cert.AccountID, o.AccountID) + assert.Equals(t, cert.OrderID, o.ID) + assert.Equals(t, cert.Leaf, foo) + assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz}) + return nil + }, + MockUpdateOrder: func(ctx context.Context, updo *Order) error { + assert.Equals(t, updo.CertificateID, "certID") + assert.Equals(t, updo.Status, StatusValid) + assert.Equals(t, updo.ID, o.ID) + assert.Equals(t, updo.AccountID, o.AccountID) + assert.Equals(t, updo.ExpiresAt, o.ExpiresAt) + assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs) + assert.Equals(t, updo.Identifiers, o.Identifiers) + return nil }, }, - res: []string{}, } }, } for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run(t) - var oiba = orderIDsByAccount{} - if oids, err := oiba.unsafeGetOrderIDsByAccount(tc.db, tc.id); err != nil { + if err := tc.o.Finalize(context.Background(), tc.db, tc.csr, tc.ca, tc.prov); err != nil { if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) + switch k := err.(type) { + case *Error: + assert.Equals(t, k.Type, tc.err.Type) + assert.Equals(t, k.Detail, tc.err.Detail) + assert.Equals(t, k.Status, tc.err.Status) + assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) + assert.Equals(t, k.Detail, tc.err.Detail) + default: + assert.FatalError(t, errors.New("unexpected error type")) + } } } else { - if assert.Nil(t, tc.err) { - assert.Equals(t, tc.res, oids) - } + assert.Nil(t, tc.err) } }) } } -*/