From 6b8585c702a7578725718d599f4a30bf17e942f0 Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 29 Mar 2021 12:04:14 -0700 Subject: [PATCH] PR review fixes / updates --- acme/account.go | 4 +- acme/api/handler.go | 11 +- acme/api/handler_test.go | 15 +- acme/authority_test.go | 1721 ----------------------------------- acme/authorization.go | 12 +- acme/challenge.go | 19 +- acme/common.go | 4 +- acme/db/nosql/account.go | 4 +- acme/db/nosql/authz.go | 4 +- acme/db/nosql/nonce.go | 41 +- acme/db/nosql/nonce_test.go | 128 +-- acme/db/nosql/order.go | 10 +- acme/order.go | 4 +- 13 files changed, 112 insertions(+), 1865 deletions(-) delete mode 100644 acme/authority_test.go diff --git a/acme/account.go b/acme/account.go index 3b6bafed..197a3400 100644 --- a/acme/account.go +++ b/acme/account.go @@ -11,11 +11,11 @@ import ( // Account is a subset of the internal account type containing only those // attributes required for responses in the ACME protocol. type Account struct { + ID string `json:"-"` + Key *jose.JSONWebKey `json:"-"` Contact []string `json:"contact,omitempty"` Status Status `json:"status"` OrdersURL string `json:"orders"` - ID string `json:"-"` - Key *jose.JSONWebKey `json:"-"` } // ToLog enables response logging. diff --git a/acme/api/handler.go b/acme/api/handler.go index 4fb1a18a..17565998 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -21,14 +21,14 @@ func link(url, typ string) string { } // Clock that returns time in UTC rounded to seconds. -type Clock int +type Clock struct{} // Now returns the UTC time rounded to seconds. func (c *Clock) Now() time.Time { return time.Now().UTC().Round(time.Second) } -var clock = new(Clock) +var clock Clock type payloadInfo struct { value []byte @@ -65,7 +65,7 @@ type HandlerOptions struct { // NewHandler returns a new ACME API handler. func NewHandler(ops HandlerOptions) api.RouterHandler { client := http.Client{ - Timeout: time.Duration(30 * time.Second), + Timeout: 30 * time.Second, } dialer := &net.Dialer{ Timeout: 30 * time.Second, @@ -89,8 +89,8 @@ func NewHandler(ops HandlerOptions) api.RouterHandler { func (h *Handler) Route(r api.Router) { getLink := h.linker.GetLinkExplicit // Standard ACME API - r.MethodFunc("GET", getLink(NewNonceLinkType, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetNonce)))) - r.MethodFunc("HEAD", getLink(NewNonceLinkType, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetNonce)))) + r.MethodFunc("GET", getLink(NewNonceLinkType, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.addDirLink(h.GetNonce))))) + r.MethodFunc("HEAD", getLink(NewNonceLinkType, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.addDirLink(h.GetNonce))))) r.MethodFunc("GET", getLink(DirectoryLinkType, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetDirectory)))) r.MethodFunc("HEAD", getLink(DirectoryLinkType, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetDirectory)))) @@ -218,6 +218,7 @@ func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) { api.WriteError(w, acme.WrapErrorISE(err, "error retrieving challenge")) return } + ch.AuthorizationID = azID if acc.ID != ch.AccountID { api.WriteError(w, acme.NewError(acme.ErrorUnauthorizedType, "account '%s' does not own challenge '%s'", acc.ID, ch.ID)) diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index 1ff08600..5501479d 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -582,6 +582,7 @@ func TestHandler_GetChallenge(t *testing.T) { assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.AccountID, "accID") + assert.Equals(t, ch.AuthorizationID, "authzID") assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String()) return acme.NewErrorISE("force") }, @@ -623,17 +624,19 @@ func TestHandler_GetChallenge(t *testing.T) { assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.AccountID, "accID") + assert.Equals(t, ch.AuthorizationID, "authzID") assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String()) return nil }, }, ch: &acme.Challenge{ - ID: "chID", - Status: acme.StatusPending, - Type: "http-01", - AccountID: "accID", - URL: url, - Error: acme.NewError(acme.ErrorConnectionType, "force"), + ID: "chID", + Status: acme.StatusPending, + AuthorizationID: "authzID", + Type: "http-01", + AccountID: "accID", + URL: url, + Error: acme.NewError(acme.ErrorConnectionType, "force"), }, vco: &acme.ValidateChallengeOptions{ HTTPGet: func(string) (*http.Response, error) { diff --git a/acme/authority_test.go b/acme/authority_test.go deleted file mode 100644 index 0e8de984..00000000 --- a/acme/authority_test.go +++ /dev/null @@ -1,1721 +0,0 @@ -package acme - -/* -func TestAuthorityGetLink(t *testing.T) { - auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - 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 { - auth *Authority - typ Link - abs bool - inputs []string - res string - } - tests := map[string]func(t *testing.T) test{ - "ok/new-account/abs": func(t *testing.T) test { - return test{ - auth: auth, - typ: NewAccountLink, - abs: true, - res: fmt.Sprintf("%s/acme/%s/new-account", baseURL.String(), provName), - } - }, - "ok/new-account/no-abs": func(t *testing.T) test { - return test{ - auth: auth, - typ: NewAccountLink, - abs: false, - res: fmt.Sprintf("/%s/new-account", provName), - } - }, - "ok/order/abs": func(t *testing.T) test { - return test{ - auth: auth, - typ: OrderLink, - abs: true, - inputs: []string{"foo"}, - res: fmt.Sprintf("%s/acme/%s/order/foo", baseURL.String(), provName), - } - }, - "ok/order/no-abs": func(t *testing.T) test { - return test{ - auth: auth, - typ: OrderLink, - abs: false, - inputs: []string{"foo"}, - res: fmt.Sprintf("/%s/order/foo", provName), - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - link := tc.auth.GetLink(ctx, tc.typ, tc.abs, tc.inputs...) - assert.Equals(t, tc.res, link) - }) - } -} - -func TestAuthorityGetDirectory(t *testing.T) { - auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - - prov := newProv() - 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 { - ctx context.Context - err *Error - } - tests := map[string]func(t *testing.T) test{ - "ok/empty-provisioner": func(t *testing.T) test { - return test{ - ctx: context.Background(), - } - }, - "ok/no-baseURL": func(t *testing.T) test { - return test{ - ctx: context.WithValue(context.Background(), ProvisionerContextKey, prov), - } - }, - "ok/baseURL": func(t *testing.T) test { - return test{ - ctx: ctx, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if dir, err := auth.GetDirectory(tc.ctx); 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) { - bu := BaseURLFromContext(tc.ctx) - if bu == nil { - bu = &url.URL{Scheme: "https", Host: "ca.smallstep.com"} - } - - var provName string - prov, err := ProvisionerFromContext(tc.ctx) - if err != nil { - provName = "" - } else { - provName = url.PathEscape(prov.GetName()) - } - - assert.Equals(t, dir.NewNonce, fmt.Sprintf("%s/acme/%s/new-nonce", bu.String(), provName)) - assert.Equals(t, dir.NewAccount, fmt.Sprintf("%s/acme/%s/new-account", bu.String(), provName)) - assert.Equals(t, dir.NewOrder, fmt.Sprintf("%s/acme/%s/new-order", bu.String(), provName)) - assert.Equals(t, dir.RevokeCert, fmt.Sprintf("%s/acme/%s/revoke-cert", bu.String(), provName)) - assert.Equals(t, dir.KeyChange, fmt.Sprintf("%s/acme/%s/key-change", bu.String(), provName)) - } - } - }) - } -} - -func TestAuthorityNewNonce(t *testing.T) { - type test struct { - auth *Authority - res *string - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/newNonce-error": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - res: nil, - err: ServerInternalErr(errors.New("error storing nonce: force")), - } - }, - "ok": func(t *testing.T) test { - var _res string - res := &_res - auth, err := NewAuthority(&db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - *res = string(key) - return nil, true, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - res: res, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if nonce, err := tc.auth.NewNonce(); 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, nonce, *tc.res) - } - } - }) - } -} - -func TestAuthorityUseNonce(t *testing.T) { - type test struct { - auth *Authority - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/newNonce-error": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{ - MUpdate: func(tx *database.Tx) error { - return errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - err: ServerInternalErr(errors.New("error deleting nonce foo: force")), - } - }, - "ok": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{ - MUpdate: func(tx *database.Tx) error { - return nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if err := tc.auth.UseNonce("foo"); 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 TestAuthorityNewAccount(t *testing.T) { - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - ops := AccountOptions{ - Key: jwk, Contact: []string{"foo", "bar"}, - } - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - ops AccountOptions - err *Error - acc **Account - } - tests := map[string]func(t *testing.T) test{ - "fail/newAccount-error": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - ops: ops, - err: ServerInternalErr(errors.New("error setting key-id to account-id index: force")), - } - }, - "ok": func(t *testing.T) test { - var ( - _acmeacc = &Account{} - acmeacc = &_acmeacc - count = 0 - dir = newDirectory("ca.smallstep.com", "acme") - ) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 1 { - var acc *account - assert.FatalError(t, json.Unmarshal(newval, &acc)) - *acmeacc, err = acc.toACME(ctx, nil, dir) - return nil, true, nil - } - count++ - return nil, true, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - ops: ops, - acc: acmeacc, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeAcc, err := tc.auth.NewAccount(ctx, tc.ops); 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) { - gotb, err := json.Marshal(acmeAcc) - assert.FatalError(t, err) - expb, err := json.Marshal(*tc.acc) - assert.FatalError(t, err) - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityGetAccount(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - id string - err *Error - acc *account - } - tests := map[string]func(t *testing.T) test{ - "fail/getAccount-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, accountTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.Errorf("error loading account %s: force", id)), - } - }, - "ok": func(t *testing.T) test { - acc, err := newAcc() - assert.FatalError(t, err) - b, err := json.Marshal(acc) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: acc.ID, - acc: acc, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeAcc, err := tc.auth.GetAccount(ctx, 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) { - gotb, err := json.Marshal(acmeAcc) - assert.FatalError(t, err) - - acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityGetAccountByKey(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - jwk *jose.JSONWebKey - err *Error - acc *account - } - tests := map[string]func(t *testing.T) test{ - "fail/generate-thumbprint-error": func(t *testing.T) test { - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - jwk.Key = "foo" - auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - jwk: jwk, - err: ServerInternalErr(errors.New("error generating jwk thumbprint: square/go-jose: unknown key type 'string'")), - } - }, - "fail/getAccount-error": func(t *testing.T) test { - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - kid, err := keyToID(jwk) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, accountByKeyIDTable) - assert.Equals(t, key, []byte(kid)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - jwk: jwk, - err: ServerInternalErr(errors.New("error loading key-account index: force")), - } - }, - "ok": func(t *testing.T) test { - acc, err := newAcc() - assert.FatalError(t, err) - b, err := json.Marshal(acc) - assert.FatalError(t, err) - count := 0 - kid, err := keyToID(acc.Key) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch { - case count == 0: - assert.Equals(t, bucket, accountByKeyIDTable) - assert.Equals(t, key, []byte(kid)) - ret = []byte(acc.ID) - case count == 1: - assert.Equals(t, bucket, accountTable) - assert.Equals(t, key, []byte(acc.ID)) - ret = b - } - count++ - return ret, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - jwk: acc.Key, - acc: acc, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeAcc, err := tc.auth.GetAccountByKey(ctx, tc.jwk); 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) { - gotb, err := json.Marshal(acmeAcc) - assert.FatalError(t, err) - - acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityGetOrder(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - id, accID string - err *Error - o *order - } - tests := map[string]func(t *testing.T) test{ - "fail/getOrder-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.New("error loading order foo: force")), - } - }, - "fail/order-not-owned-by-account": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: o.ID, - accID: "foo", - err: UnauthorizedErr(errors.New("account does not own order")), - } - }, - "fail/updateStatus-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - i := 0 - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - switch { - case i == 0: - i++ - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - default: - assert.Equals(t, bucket, authzTable) - assert.Equals(t, key, []byte(o.Authorizations[0])) - return nil, ServerInternalErr(errors.New("force")) - } - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: o.ID, - accID: o.AccountID, - err: ServerInternalErr(errors.Errorf("error loading authz %s: force", o.Authorizations[0])), - } - }, - "ok": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = "valid" - b, err := json.Marshal(o) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: o.ID, - accID: o.AccountID, - o: o, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeO, err := tc.auth.GetOrder(ctx, tc.accID, 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) { - gotb, err := json.Marshal(acmeO) - assert.FatalError(t, err) - - acmeExp, err := tc.o.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityGetCertificate(t *testing.T) { - type test struct { - auth *Authority - id, accID string - err *Error - cert *certificate - } - tests := map[string]func(t *testing.T) test{ - "fail/getCertificate-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, certTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.New("error loading certificate: force")), - } - }, - "fail/certificate-not-owned-by-account": func(t *testing.T) test { - cert, err := newcert() - assert.FatalError(t, err) - b, err := json.Marshal(cert) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, certTable) - assert.Equals(t, key, []byte(cert.ID)) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: cert.ID, - accID: "foo", - err: UnauthorizedErr(errors.New("account does not own certificate")), - } - }, - "ok": func(t *testing.T) test { - cert, err := newcert() - assert.FatalError(t, err) - b, err := json.Marshal(cert) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, certTable) - assert.Equals(t, key, []byte(cert.ID)) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: cert.ID, - accID: cert.AccountID, - cert: cert, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeCert, err := tc.auth.GetCertificate(tc.accID, 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) { - gotb, err := json.Marshal(acmeCert) - assert.FatalError(t, err) - - acmeExp, err := tc.cert.toACME(nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityGetAuthz(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - id, accID string - err *Error - acmeAz *Authz - } - tests := map[string]func(t *testing.T) test{ - "fail/getAuthz-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, authzTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.Errorf("error loading authz %s: force", id)), - } - }, - "fail/authz-not-owned-by-account": func(t *testing.T) test { - az, err := newAz() - assert.FatalError(t, err) - b, err := json.Marshal(az) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, authzTable) - assert.Equals(t, key, []byte(az.getID())) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: az.getID(), - accID: "foo", - err: UnauthorizedErr(errors.New("account does not own authz")), - } - }, - "fail/update-status-error": func(t *testing.T) test { - az, err := newAz() - assert.FatalError(t, err) - b, err := json.Marshal(az) - assert.FatalError(t, err) - count := 0 - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - assert.Equals(t, bucket, authzTable) - assert.Equals(t, key, []byte(az.getID())) - ret = b - case 1: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(az.getChallenges()[0])) - return nil, errors.New("force") - } - count++ - return ret, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: az.getID(), - accID: az.getAccountID(), - err: ServerInternalErr(errors.New("error updating authz status: error loading challenge")), - } - }, - "ok": func(t *testing.T) test { - var ch1B, ch2B, ch3B = &[]byte{}, &[]byte{}, &[]byte{} - count := 0 - mockdb := &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - switch count { - case 0: - *ch1B = newval - case 1: - *ch2B = newval - case 2: - *ch3B = newval - } - count++ - return nil, true, nil - }, - } - az, err := newAuthz(mockdb, "1234", Identifier{ - Type: "dns", Value: "acme.example.com", - }) - assert.FatalError(t, err) - _az, ok := az.(*dnsAuthz) - assert.Fatal(t, ok) - _az.baseAuthz.Status = StatusValid - b, err := json.Marshal(az) - assert.FatalError(t, err) - - ch1, err := unmarshalChallenge(*ch1B) - assert.FatalError(t, err) - ch2, err := unmarshalChallenge(*ch2B) - assert.FatalError(t, err) - ch3, err := unmarshalChallenge(*ch3B) - assert.FatalError(t, err) - count = 0 - mockdb = &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch1.getID())) - ret = *ch1B - case 1: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch2.getID())) - ret = *ch2B - case 2: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch3.getID())) - ret = *ch3B - } - count++ - return ret, nil - }, - } - acmeAz, err := az.toACME(ctx, mockdb, newDirectory("ca.smallstep.com", "acme")) - assert.FatalError(t, err) - - count = 0 - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - assert.Equals(t, bucket, authzTable) - assert.Equals(t, key, []byte(az.getID())) - ret = b - case 1: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch1.getID())) - ret = *ch1B - case 2: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch2.getID())) - ret = *ch2B - case 3: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch3.getID())) - ret = *ch3B - } - count++ - return ret, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: az.getID(), - accID: az.getAccountID(), - acmeAz: acmeAz, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeAz, err := tc.auth.GetAuthz(ctx, tc.accID, 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) { - gotb, err := json.Marshal(acmeAz) - assert.FatalError(t, err) - - expb, err := json.Marshal(tc.acmeAz) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityNewOrder(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - ops OrderOptions - ctx context.Context - err *Error - o **Order - } - tests := map[string]func(t *testing.T) test{ - "fail/no-provisioner": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{}, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - ops: defaultOrderOps(), - ctx: context.Background(), - err: ServerInternalErr(errors.New("provisioner expected in request context")), - } - }, - "fail/newOrder-error": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - ops: defaultOrderOps(), - ctx: ctx, - err: ServerInternalErr(errors.New("error creating order: error creating http challenge: error saving acme challenge: force")), - } - }, - "ok": func(t *testing.T) test { - var ( - _acmeO = &Order{} - acmeO = &_acmeO - count = 0 - dir = newDirectory("ca.smallstep.com", "acme") - err error - _accID string - accID = &_accID - ) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - switch count { - case 0: - assert.Equals(t, bucket, challengeTable) - case 1: - assert.Equals(t, bucket, challengeTable) - case 2: - assert.Equals(t, bucket, challengeTable) - case 3: - assert.Equals(t, bucket, authzTable) - case 4: - assert.Equals(t, bucket, challengeTable) - case 5: - assert.Equals(t, bucket, challengeTable) - case 6: - assert.Equals(t, bucket, challengeTable) - case 7: - assert.Equals(t, bucket, authzTable) - case 8: - assert.Equals(t, bucket, orderTable) - var o order - assert.FatalError(t, json.Unmarshal(newval, &o)) - *acmeO, err = o.toACME(ctx, nil, dir) - assert.FatalError(t, err) - *accID = o.AccountID - case 9: - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, string(key), *accID) - } - count++ - return nil, true, nil - }, - MGet: func(bucket, key []byte) ([]byte, error) { - return nil, database.ErrNotFound - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - ops: defaultOrderOps(), - ctx: ctx, - o: acmeO, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeO, err := tc.auth.NewOrder(tc.ctx, tc.ops); 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) { - gotb, err := json.Marshal(acmeO) - assert.FatalError(t, err) - expb, err := json.Marshal(*tc.o) - assert.FatalError(t, err) - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityGetOrdersByAccount(t *testing.T) { - 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 { - auth *Authority - id string - err *Error - res []string - } - tests := map[string]func(t *testing.T) test{ - "fail/getOrderIDsByAccount-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.New("error loading orderIDs for account foo: force")), - } - }, - "fail/getOrder-error": func(t *testing.T) test { - var ( - id = "zap" - oids = []string{"foo", "bar"} - count = 0 - err error - ) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - var ret []byte - switch count { - case 0: - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(id)) - ret, err = json.Marshal(oids) - assert.FatalError(t, err) - case 1: - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(oids[0])) - return nil, errors.New("force") - } - count++ - return ret, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.New("error loading order foo for account zap: error loading order foo: force")), - } - }, - "ok": func(t *testing.T) test { - accID := "zap" - - foo, err := newO() - assert.FatalError(t, err) - bfoo, err := json.Marshal(foo) - assert.FatalError(t, err) - - bar, err := newO() - assert.FatalError(t, err) - bar.Status = StatusInvalid - bbar, err := json.Marshal(bar) - assert.FatalError(t, err) - - zap, err := newO() - assert.FatalError(t, err) - bzap, err := json.Marshal(zap) - 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 - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - switch string(bucket) { - case string(orderTable): - dbGetOrder++ - switch dbGetOrder { - case 1: - return bfoo, nil - case 2: - return bbar, nil - case 3: - return bzap, nil - } - case string(ordersByAccountIDTable): - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, key, []byte(accID)) - ret, err := json.Marshal([]string{foo.ID, bar.ID, zap.ID}) - assert.FatalError(t, err) - return ret, nil - case string(challengeTable): - return bch, nil - case string(authzTable): - return baz, nil - } - return nil, errors.Errorf("should not be query db table %s", bucket) - }, - MCmpAndSwap: func(bucket, key, old, newVal []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, ordersByAccountIDTable) - assert.Equals(t, string(key), accID) - return nil, true, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: accID, - res: []string{ - fmt.Sprintf("%s/acme/%s/order/%s", baseURL.String(), provName, foo.ID), - fmt.Sprintf("%s/acme/%s/order/%s", baseURL.String(), provName, zap.ID), - }, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if orderLinks, err := tc.auth.GetOrdersByAccount(ctx, 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.res, orderLinks) - } - } - }) - } -} - -func TestAuthorityFinalizeOrder(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - id, accID string - ctx context.Context - err *Error - o *order - } - tests := map[string]func(t *testing.T) test{ - "fail/no-provisioner": func(t *testing.T) test { - auth, err := NewAuthority(&db.MockNoSQLDB{}, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: "foo", - ctx: context.Background(), - err: ServerInternalErr(errors.New("provisioner expected in request context")), - } - }, - "fail/getOrder-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - ctx: ctx, - err: ServerInternalErr(errors.New("error loading order foo: force")), - } - }, - "fail/order-not-owned-by-account": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - b, err := json.Marshal(o) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: o.ID, - accID: "foo", - ctx: ctx, - err: UnauthorizedErr(errors.New("account does not own order")), - } - }, - "fail/finalize-error": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Expires = time.Now().Add(-time.Minute) - b, err := json.Marshal(o) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: o.ID, - accID: o.AccountID, - ctx: ctx, - err: ServerInternalErr(errors.New("error finalizing order: error storing order: force")), - } - }, - "ok": func(t *testing.T) test { - o, err := newO() - assert.FatalError(t, err) - o.Status = StatusValid - o.Certificate = "certID" - b, err := json.Marshal(o) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, orderTable) - assert.Equals(t, key, []byte(o.ID)) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: o.ID, - accID: o.AccountID, - ctx: ctx, - o: o, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeO, err := tc.auth.FinalizeOrder(tc.ctx, tc.accID, tc.id, nil); 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) { - gotb, err := json.Marshal(acmeO) - assert.FatalError(t, err) - - acmeExp, err := tc.o.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityValidateChallenge(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - - type test struct { - auth *Authority - id, accID string - err *Error - ch challenge - jwk *jose.JSONWebKey - server *httptest.Server - } - tests := map[string]func(t *testing.T) test{ - "fail/getChallenge-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.Errorf("error loading challenge %s: force", id)), - } - }, - "fail/challenge-not-owned-by-account": func(t *testing.T) test { - ch, err := newHTTPCh() - assert.FatalError(t, err) - b, err := json.Marshal(ch) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: ch.getID(), - accID: "foo", - err: UnauthorizedErr(errors.New("account does not own challenge")), - } - }, - "fail/validate-error": func(t *testing.T) test { - keyauth := "temp" - keyauthp := &keyauth - // Create test server that returns challenge auth - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%s\r\n", *keyauthp) - })) - t.Cleanup(func() { ts.Close() }) - - ch, err := newHTTPChWithServer(strings.TrimPrefix(ts.URL, "http://")) - assert.FatalError(t, err) - - jwk, _, err := jose.GenerateDefaultKeyPair([]byte("pass")) - assert.FatalError(t, err) - - thumbprint, err := jwk.Thumbprint(crypto.SHA256) - assert.FatalError(t, err) - encPrint := base64.RawURLEncoding.EncodeToString(thumbprint) - *keyauthp = fmt.Sprintf("%s.%s", ch.getToken(), encPrint) - - b, err := json.Marshal(ch) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: ch.getID(), - accID: ch.getAccountID(), - jwk: jwk, - server: ts, - err: ServerInternalErr(errors.New("error attempting challenge validation: error saving acme challenge: force")), - } - }, - "ok/already-valid": func(t *testing.T) test { - ch, err := newHTTPCh() - assert.FatalError(t, err) - _ch, ok := ch.(*http01Challenge) - assert.Fatal(t, ok) - _ch.baseChallenge.Status = StatusValid - _ch.baseChallenge.Validated = clock.Now() - b, err := json.Marshal(ch) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - return b, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: ch.getID(), - accID: ch.getAccountID(), - ch: ch, - } - }, - "ok": func(t *testing.T) test { - keyauth := "temp" - keyauthp := &keyauth - // Create test server that returns challenge auth - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%s\r\n", *keyauthp) - })) - t.Cleanup(func() { ts.Close() }) - - ch, err := newHTTPChWithServer(strings.TrimPrefix(ts.URL, "http://")) - assert.FatalError(t, err) - - jwk, _, err := jose.GenerateDefaultKeyPair([]byte("pass")) - assert.FatalError(t, err) - - thumbprint, err := jwk.Thumbprint(crypto.SHA256) - assert.FatalError(t, err) - encPrint := base64.RawURLEncoding.EncodeToString(thumbprint) - *keyauthp = fmt.Sprintf("%s.%s", ch.getToken(), encPrint) - - b, err := json.Marshal(ch) - assert.FatalError(t, err) - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - return nil, true, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: ch.getID(), - accID: ch.getAccountID(), - jwk: jwk, - server: ts, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeCh, err := tc.auth.ValidateChallenge(ctx, tc.accID, tc.id, tc.jwk); 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) { - gotb, err := json.Marshal(acmeCh) - assert.FatalError(t, err) - - if tc.ch != nil { - acmeExp, err := tc.ch.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - } - }) - } -} - -func TestAuthorityUpdateAccount(t *testing.T) { - contact := []string{"baz", "zap"} - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - id string - contact []string - acc *account - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/getAccount-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, accountTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - contact: contact, - err: ServerInternalErr(errors.Errorf("error loading account %s: force", id)), - } - }, - "fail/update-error": func(t *testing.T) test { - acc, err := newAcc() - assert.FatalError(t, err) - b, err := json.Marshal(acc) - assert.FatalError(t, err) - - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: acc.ID, - contact: contact, - err: ServerInternalErr(errors.New("error storing account: force")), - } - }, - - "ok": func(t *testing.T) test { - acc, err := newAcc() - assert.FatalError(t, err) - b, err := json.Marshal(acc) - assert.FatalError(t, err) - - _acc := *acc - clone := &_acc - clone.Contact = contact - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, accountTable) - assert.Equals(t, key, []byte(acc.ID)) - return nil, true, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: acc.ID, - contact: contact, - acc: clone, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeAcc, err := tc.auth.UpdateAccount(ctx, tc.id, tc.contact); 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) { - gotb, err := json.Marshal(acmeAcc) - assert.FatalError(t, err) - - acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} - -func TestAuthorityDeactivateAccount(t *testing.T) { - prov := newProv() - ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov) - ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080") - type test struct { - auth *Authority - id string - acc *account - err *Error - } - tests := map[string]func(t *testing.T) test{ - "fail/getAccount-error": func(t *testing.T) test { - id := "foo" - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, accountTable) - assert.Equals(t, key, []byte(id)) - return nil, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: id, - err: ServerInternalErr(errors.Errorf("error loading account %s: force", id)), - } - }, - "fail/deactivate-error": func(t *testing.T) test { - acc, err := newAcc() - assert.FatalError(t, err) - b, err := json.Marshal(acc) - assert.FatalError(t, err) - - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: acc.ID, - err: ServerInternalErr(errors.New("error storing account: force")), - } - }, - - "ok": func(t *testing.T) test { - acc, err := newAcc() - assert.FatalError(t, err) - b, err := json.Marshal(acc) - assert.FatalError(t, err) - - _acc := *acc - clone := &_acc - clone.Status = StatusDeactivated - clone.Deactivated = clock.Now() - auth, err := NewAuthority(&db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, accountTable) - assert.Equals(t, key, []byte(acc.ID)) - return nil, true, nil - }, - }, "ca.smallstep.com", "acme", nil) - assert.FatalError(t, err) - return test{ - auth: auth, - id: acc.ID, - acc: clone, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - if acmeAcc, err := tc.auth.DeactivateAccount(ctx, 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) { - gotb, err := json.Marshal(acmeAcc) - assert.FatalError(t, err) - - acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir) - assert.FatalError(t, err) - expb, err := json.Marshal(acmeExp) - assert.FatalError(t, err) - - assert.Equals(t, expb, gotb) - } - } - }) - } -} -*/ diff --git a/acme/authorization.go b/acme/authorization.go index 4d5c42c8..d2df5ea5 100644 --- a/acme/authorization.go +++ b/acme/authorization.go @@ -8,15 +8,15 @@ import ( // Authorization representst an ACME Authorization. type Authorization struct { - Identifier Identifier `json:"identifier"` - Status Status `json:"status"` - ExpiresAt time.Time `json:"expires"` - Challenges []*Challenge `json:"challenges"` - Wildcard bool `json:"wildcard"` - Error *Error `json:"error,omitempty"` ID string `json:"-"` AccountID string `json:"-"` Token string `json:"-"` + Identifier Identifier `json:"identifier"` + Status Status `json:"status"` + Challenges []*Challenge `json:"challenges"` + Wildcard bool `json:"wildcard"` + ExpiresAt time.Time `json:"expires"` + Error *Error `json:"error,omitempty"` } // ToLog enables response logging. diff --git a/acme/challenge.go b/acme/challenge.go index b4f151cd..94c74b74 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -22,15 +22,16 @@ import ( // Challenge represents an ACME response Challenge type. type Challenge struct { - Type string `json:"type"` - Status Status `json:"status"` - Token string `json:"token"` - ValidatedAt string `json:"validated,omitempty"` - URL string `json:"url"` - Error *Error `json:"error,omitempty"` - ID string `json:"-"` - AccountID string `json:"-"` - Value string `json:"-"` + ID string `json:"-"` + AccountID string `json:"-"` + AuthorizationID string `json:"-"` + Value string `json:"-"` + Type string `json:"type"` + Status Status `json:"status"` + Token string `json:"token"` + ValidatedAt string `json:"validated,omitempty"` + URL string `json:"url"` + Error *Error `json:"error,omitempty"` } // ToLog enables response logging. diff --git a/acme/common.go b/acme/common.go index f7fd7141..05c909eb 100644 --- a/acme/common.go +++ b/acme/common.go @@ -15,14 +15,14 @@ type CertificateAuthority interface { } // Clock that returns time in UTC rounded to seconds. -type Clock int +type Clock struct{} // Now returns the UTC time rounded to seconds. func (c *Clock) Now() time.Time { return time.Now().UTC().Round(time.Second) } -var clock = new(Clock) +var clock Clock // Provisioner is an interface that implements a subset of the provisioner.Interface -- // only those methods required by the ACME api/authority. diff --git a/acme/db/nosql/account.go b/acme/db/nosql/account.go index d7ac9655..1c3bec5d 100644 --- a/acme/db/nosql/account.go +++ b/acme/db/nosql/account.go @@ -14,11 +14,11 @@ import ( // dbAccount represents an ACME account. type dbAccount struct { ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - DeactivatedAt time.Time `json:"deactivatedAt"` Key *jose.JSONWebKey `json:"key"` Contact []string `json:"contact,omitempty"` Status acme.Status `json:"status"` + CreatedAt time.Time `json:"createdAt"` + DeactivatedAt time.Time `json:"deactivatedAt"` } func (dba *dbAccount) clone() *dbAccount { diff --git a/acme/db/nosql/authz.go b/acme/db/nosql/authz.go index c3283603..6decbe4f 100644 --- a/acme/db/nosql/authz.go +++ b/acme/db/nosql/authz.go @@ -16,12 +16,12 @@ type dbAuthz struct { AccountID string `json:"accountID"` Identifier acme.Identifier `json:"identifier"` Status acme.Status `json:"status"` - ExpiresAt time.Time `json:"expiresAt"` + Token string `json:"token"` ChallengeIDs []string `json:"challengeIDs"` Wildcard bool `json:"wildcard"` CreatedAt time.Time `json:"createdAt"` + ExpiresAt time.Time `json:"expiresAt"` Error *acme.Error `json:"error"` - Token string `json:"token"` } func (ba *dbAuthz) clone() *dbAuthz { diff --git a/acme/db/nosql/nonce.go b/acme/db/nosql/nonce.go index 4f587ae0..6344f0c6 100644 --- a/acme/db/nosql/nonce.go +++ b/acme/db/nosql/nonce.go @@ -3,12 +3,12 @@ package nosql import ( "context" "encoding/base64" - "encoding/json" "time" "github.com/pkg/errors" "github.com/smallstep/certificates/acme" "github.com/smallstep/nosql" + "github.com/smallstep/nosql/database" ) // dbNonce contains nonce metadata used in the ACME protocol. @@ -45,24 +45,27 @@ func (db *DB) CreateNonce(ctx context.Context) (acme.Nonce, error) { // DeleteNonce verifies that the nonce is valid (by checking if it exists), // and if so, consumes the nonce resource by deleting it from the database. func (db *DB) DeleteNonce(ctx context.Context, nonce acme.Nonce) error { - id := string(nonce) - b, err := db.db.Get(nonceTable, []byte(nonce)) - if nosql.IsErrNotFound(err) { - return errors.Wrapf(err, "nonce %s not found", id) - } else if err != nil { - return errors.Wrapf(err, "error loading nonce %s", id) - } + err := db.db.Update(&database.Tx{ + Operations: []*database.TxEntry{ + { + Bucket: nonceTable, + Key: []byte(nonce), + Cmd: database.Get, + }, + { + Bucket: nonceTable, + Key: []byte(nonce), + Cmd: database.Delete, + }, + }, + }) - dbn := new(dbNonce) - if err := json.Unmarshal(b, dbn); err != nil { - return errors.Wrapf(err, "error unmarshaling nonce %s", string(nonce)) + switch { + case nosql.IsErrNotFound(err): + return acme.NewError(acme.ErrorBadNonceType, "nonce %s not found", string(nonce)) + case err != nil: + return errors.Wrapf(err, "error deleting nonce %s", string(nonce)) + default: + return nil } - if !dbn.DeletedAt.IsZero() { - return acme.NewError(acme.ErrorBadNonceType, "nonce %s already deleted", id) - } - - nu := dbn.clone() - nu.DeletedAt = clock.Now() - - return db.save(ctx, id, nu, dbn, "nonce", nonceTable) } diff --git a/acme/db/nosql/nonce_test.go b/acme/db/nosql/nonce_test.go index 131438f6..05d73d52 100644 --- a/acme/db/nosql/nonce_test.go +++ b/acme/db/nosql/nonce_test.go @@ -11,7 +11,7 @@ import ( "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/db" "github.com/smallstep/nosql" - nosqldb "github.com/smallstep/nosql/database" + "github.com/smallstep/nosql/database" ) func TestDB_CreateNonce(t *testing.T) { @@ -85,108 +85,57 @@ func TestDB_DeleteNonce(t *testing.T) { nonceID := "nonceID" type test struct { - db nosql.DB - err error + db nosql.DB + err error + acmeErr *acme.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, nonceTable) - assert.Equals(t, string(key), nonceID) + MUpdate: func(tx *database.Tx) error { + assert.Equals(t, tx.Operations[0].Bucket, nonceTable) + assert.Equals(t, tx.Operations[0].Key, []byte(nonceID)) + assert.Equals(t, tx.Operations[0].Cmd, database.Get) - return nil, nosqldb.ErrNotFound + assert.Equals(t, tx.Operations[1].Bucket, nonceTable) + assert.Equals(t, tx.Operations[1].Key, []byte(nonceID)) + assert.Equals(t, tx.Operations[1].Cmd, database.Delete) + return database.ErrNotFound }, }, - err: errors.New("nonce nonceID not found"), + acmeErr: acme.NewError(acme.ErrorBadNonceType, "nonce %s not found", nonceID), } }, - "fail/db.Get-error": func(t *testing.T) test { + "fail/db.Update-error": func(t *testing.T) test { return test{ db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, nonceTable) - assert.Equals(t, string(key), nonceID) + MUpdate: func(tx *database.Tx) error { + assert.Equals(t, tx.Operations[0].Bucket, nonceTable) + assert.Equals(t, tx.Operations[0].Key, []byte(nonceID)) + assert.Equals(t, tx.Operations[0].Cmd, database.Get) - return nil, errors.Errorf("force") + assert.Equals(t, tx.Operations[1].Bucket, nonceTable) + assert.Equals(t, tx.Operations[1].Key, []byte(nonceID)) + assert.Equals(t, tx.Operations[1].Cmd, database.Delete) + return errors.New("force") }, }, - err: errors.New("error loading nonce nonceID: 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, nonceTable) - assert.Equals(t, string(key), nonceID) - - a := []string{"foo", "bar", "baz"} - b, err := json.Marshal(a) - assert.FatalError(t, err) - - return b, nil - }, - }, - err: errors.New("error unmarshaling nonce nonceID"), - } - }, - "fail/already-used": func(t *testing.T) test { - return test{ - db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, nonceTable) - assert.Equals(t, string(key), nonceID) - - nonce := dbNonce{ - ID: nonceID, - CreatedAt: clock.Now().Add(-5 * time.Minute), - DeletedAt: clock.Now(), - } - b, err := json.Marshal(nonce) - assert.FatalError(t, err) - - return b, nil - }, - }, - err: acme.NewError(acme.ErrorBadNonceType, "nonce already deleted"), + err: errors.New("error deleting nonce nonceID: force"), } }, "ok": func(t *testing.T) test { - nonce := dbNonce{ - ID: nonceID, - CreatedAt: clock.Now().Add(-5 * time.Minute), - } - b, err := json.Marshal(nonce) - assert.FatalError(t, err) return test{ db: &db.MockNoSQLDB{ - MGet: func(bucket, key []byte) ([]byte, error) { - assert.Equals(t, bucket, nonceTable) - assert.Equals(t, string(key), nonceID) + MUpdate: func(tx *database.Tx) error { + assert.Equals(t, tx.Operations[0].Bucket, nonceTable) + assert.Equals(t, tx.Operations[0].Key, []byte(nonceID)) + assert.Equals(t, tx.Operations[0].Cmd, database.Get) - return b, nil - }, - MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, nonceTable) - assert.Equals(t, old, b) - - dbo := new(dbNonce) - assert.FatalError(t, json.Unmarshal(old, dbo)) - assert.Equals(t, dbo.ID, string(key)) - assert.True(t, clock.Now().Add(-6*time.Minute).Before(dbo.CreatedAt)) - assert.True(t, clock.Now().Add(-4*time.Minute).After(dbo.CreatedAt)) - assert.True(t, dbo.DeletedAt.IsZero()) - - dbn := new(dbNonce) - assert.FatalError(t, json.Unmarshal(nu, dbn)) - assert.Equals(t, dbn.ID, string(key)) - assert.True(t, clock.Now().Add(-6*time.Minute).Before(dbn.CreatedAt)) - assert.True(t, clock.Now().Add(-4*time.Minute).After(dbn.CreatedAt)) - assert.True(t, clock.Now().Add(-time.Minute).Before(dbn.DeletedAt)) - assert.True(t, clock.Now().Add(time.Minute).After(dbn.DeletedAt)) - return nil, true, nil + assert.Equals(t, tx.Operations[1].Bucket, nonceTable) + assert.Equals(t, tx.Operations[1].Key, []byte(nonceID)) + assert.Equals(t, tx.Operations[1].Cmd, database.Delete) + return nil }, }, } @@ -197,8 +146,19 @@ func TestDB_DeleteNonce(t *testing.T) { t.Run(name, func(t *testing.T) { db := DB{db: tc.db} if err := db.DeleteNonce(context.Background(), acme.Nonce(nonceID)); err != nil { - if assert.NotNil(t, tc.err) { - assert.HasPrefix(t, err.Error(), tc.err.Error()) + 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 { assert.Nil(t, tc.err) diff --git a/acme/db/nosql/order.go b/acme/db/nosql/order.go index 5513b0b6..ba3934af 100644 --- a/acme/db/nosql/order.go +++ b/acme/db/nosql/order.go @@ -18,15 +18,15 @@ type dbOrder struct { ID string `json:"id"` AccountID string `json:"accountID"` ProvisionerID string `json:"provisionerID"` - CreatedAt time.Time `json:"createdAt"` - ExpiresAt time.Time `json:"expiresAt,omitempty"` - Status acme.Status `json:"status"` Identifiers []acme.Identifier `json:"identifiers"` + AuthorizationIDs []string `json:"authorizationIDs"` + Status acme.Status `json:"status"` NotBefore time.Time `json:"notBefore,omitempty"` NotAfter time.Time `json:"notAfter,omitempty"` - Error *acme.Error `json:"error,omitempty"` - AuthorizationIDs []string `json:"authorizationIDs"` + CreatedAt time.Time `json:"createdAt"` + ExpiresAt time.Time `json:"expiresAt,omitempty"` CertificateID string `json:"certificate,omitempty"` + Error *acme.Error `json:"error,omitempty"` } func (a *dbOrder) clone() *dbOrder { diff --git a/acme/order.go b/acme/order.go index a6112362..7405906d 100644 --- a/acme/order.go +++ b/acme/order.go @@ -21,6 +21,8 @@ type Identifier struct { // Order contains order metadata for the ACME protocol order type. type Order struct { ID string `json:"id"` + AccountID string `json:"-"` + ProvisionerID string `json:"-"` Status Status `json:"status"` ExpiresAt time.Time `json:"expires,omitempty"` Identifiers []Identifier `json:"identifiers"` @@ -32,8 +34,6 @@ type Order struct { FinalizeURL string `json:"finalize"` CertificateID string `json:"-"` CertificateURL string `json:"certificate,omitempty"` - AccountID string `json:"-"` - ProvisionerID string `json:"-"` } // ToLog enables response logging.