Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api/render: initial implementation of the package #860

Merged
merged 20 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5acceee
api/render: initial implementation of the package
azazeal Mar 18, 2022
cd6ea41
acme/api: refactored to support api/render
azazeal Mar 18, 2022
793792b
authority/admin: refactored to support api/render
azazeal Mar 18, 2022
a456451
ca: refactored to support api/render
azazeal Mar 18, 2022
5cc2834
api: refactored to support api/render
azazeal Mar 18, 2022
7496598
api/render: implemented Error
azazeal Mar 18, 2022
7918fad
api: refactored to support api/render.Error
azazeal Mar 18, 2022
9d02bc5
acme/api: refactored to support api/render.Error
azazeal Mar 18, 2022
c742ca4
authority/admin: refactored to support api/render.Error
azazeal Mar 18, 2022
d6e28b6
ca: refactored to support api/render.Error
azazeal Mar 18, 2022
e875baa
ca: fixed broken tests
azazeal Mar 21, 2022
eb2dfc3
api/render, api/log: moved error logging to this package
azazeal Mar 28, 2022
29ffc82
acme: refactored Error so that it implements render.RenderableError
azazeal Mar 28, 2022
0977eb6
authority/admin: refactored Error so that it implements render.Render…
azazeal Mar 28, 2022
ff5e482
api/render: implemented RenderableError
azazeal Mar 28, 2022
8ec9845
api/render: added test coverage for Error
azazeal Mar 28, 2022
7c394f5
api/render: implemented statusCodeFromError
azazeal Mar 28, 2022
520f143
api: refactored RootsPEM to work with render.Error
azazeal Mar 28, 2022
691fbad
acme, authority/admin: fixed pointer receiver name for consistency
azazeal Mar 28, 2022
3d31449
api/render, errs: moved StatusCoder & StackTracer to the render package
azazeal Mar 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions acme/api/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"net/http"

"github.com/go-chi/chi"

"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/logging"
)

Expand Down Expand Up @@ -70,23 +71,23 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
payload, err := payloadFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
var nar NewAccountRequest
if err := json.Unmarshal(payload.value, &nar); err != nil {
api.WriteError(w, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload"))
return
}
if err := nar.Validate(); err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

prov, err := acmeProvisionerFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

Expand All @@ -96,26 +97,26 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
acmeErr, ok := err.(*acme.Error)
if !ok || acmeErr.Status != http.StatusBadRequest {
// Something went wrong ...
api.WriteError(w, err)
render.Error(w, err)
return
}

// Account does not exist //
if nar.OnlyReturnExisting {
api.WriteError(w, acme.NewError(acme.ErrorAccountDoesNotExistType,
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType,
"account does not exist"))
return
}

jwk, err := jwkFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

eak, err := h.validateExternalAccountBinding(ctx, &nar)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

Expand All @@ -125,18 +126,18 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
Status: acme.StatusValid,
}
if err := h.db.CreateAccount(ctx, acc); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error creating account"))
render.Error(w, acme.WrapErrorISE(err, "error creating account"))
return
}

if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
err := eak.BindTo(acc)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
if err := h.db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error updating external account binding key"))
render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key"))
return
}
acc.ExternalAccountBinding = nar.ExternalAccountBinding
Expand All @@ -149,20 +150,20 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
h.linker.LinkAccount(ctx, acc)

w.Header().Set("Location", h.linker.GetLink(r.Context(), AccountLinkType, acc.ID))
api.JSONStatus(w, acc, httpStatus)
render.JSONStatus(w, acc, httpStatus)
}

// GetOrUpdateAccount is the api for updating an ACME account.
func (h *Handler) GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acc, err := accountFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

Expand All @@ -171,12 +172,12 @@ func (h *Handler) GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
if !payload.isPostAsGet {
var uar UpdateAccountRequest
if err := json.Unmarshal(payload.value, &uar); err != nil {
api.WriteError(w, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload"))
return
}
if err := uar.Validate(); err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
if len(uar.Status) > 0 || len(uar.Contact) > 0 {
Expand All @@ -187,7 +188,7 @@ func (h *Handler) GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
}

if err := h.db.UpdateAccount(ctx, acc); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error updating account"))
render.Error(w, acme.WrapErrorISE(err, "error updating account"))
return
}
}
Expand All @@ -196,7 +197,7 @@ func (h *Handler) GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
h.linker.LinkAccount(ctx, acc)

w.Header().Set("Location", h.linker.GetLink(ctx, AccountLinkType, acc.ID))
api.JSON(w, acc)
render.JSON(w, acc)
}

func logOrdersByAccount(w http.ResponseWriter, oids []string) {
Expand All @@ -213,22 +214,22 @@ func (h *Handler) GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acc, err := accountFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
accID := chi.URLParam(r, "accID")
if acc.ID != accID {
api.WriteError(w, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
return
}
orders, err := h.db.GetOrdersByAccountID(ctx, acc.ID)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

h.linker.LinkOrdersByAccountID(ctx, orders)

api.JSON(w, orders)
render.JSON(w, orders)
logOrdersByAccount(w, orders)
}
38 changes: 20 additions & 18 deletions acme/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"time"

"github.com/go-chi/chi"

"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/provisioner"
)

Expand Down Expand Up @@ -181,11 +183,11 @@ func (h *Handler) GetDirectory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

api.JSON(w, &Directory{
render.JSON(w, &Directory{
NewNonce: h.linker.GetLink(ctx, NewNonceLinkType),
NewAccount: h.linker.GetLink(ctx, NewAccountLinkType),
NewOrder: h.linker.GetLink(ctx, NewOrderLinkType),
Expand All @@ -200,51 +202,51 @@ func (h *Handler) GetDirectory(w http.ResponseWriter, r *http.Request) {
// NotImplemented returns a 501 and is generally a placeholder for functionality which
// MAY be added at some point in the future but is not in any way a guarantee of such.
func (h *Handler) NotImplemented(w http.ResponseWriter, r *http.Request) {
api.WriteError(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
}

// GetAuthorization ACME api for retrieving an Authz.
func (h *Handler) GetAuthorization(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acc, err := accountFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
az, err := h.db.GetAuthorization(ctx, chi.URLParam(r, "authzID"))
if err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error retrieving authorization"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving authorization"))
return
}
if acc.ID != az.AccountID {
api.WriteError(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own authorization '%s'", acc.ID, az.ID))
return
}
if err = az.UpdateStatus(ctx, h.db); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error updating authorization status"))
render.Error(w, acme.WrapErrorISE(err, "error updating authorization status"))
return
}

h.linker.LinkAuthorization(ctx, az)

w.Header().Set("Location", h.linker.GetLink(ctx, AuthzLinkType, az.ID))
api.JSON(w, az)
render.JSON(w, az)
}

// GetChallenge ACME api for retrieving a Challenge.
func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acc, err := accountFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
// Just verify that the payload was set, since we're not strictly adhering
// to ACME V2 spec for reasons specified below.
_, err = payloadFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}

Expand All @@ -257,49 +259,49 @@ func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) {
azID := chi.URLParam(r, "authzID")
ch, err := h.db.GetChallenge(ctx, chi.URLParam(r, "chID"), azID)
if err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error retrieving challenge"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving challenge"))
return
}
ch.AuthorizationID = azID
if acc.ID != ch.AccountID {
api.WriteError(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own challenge '%s'", acc.ID, ch.ID))
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
if err = ch.Validate(ctx, h.db, jwk, h.validateChallengeOptions); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error validating challenge"))
render.Error(w, acme.WrapErrorISE(err, "error validating challenge"))
return
}

h.linker.LinkChallenge(ctx, ch, azID)

w.Header().Add("Link", link(h.linker.GetLink(ctx, AuthzLinkType, azID), "up"))
w.Header().Set("Location", h.linker.GetLink(ctx, ChallengeLinkType, azID, ch.ID))
api.JSON(w, ch)
render.JSON(w, ch)
}

// GetCertificate ACME api for retrieving a Certificate.
func (h *Handler) GetCertificate(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acc, err := accountFromContext(ctx)
if err != nil {
api.WriteError(w, err)
render.Error(w, err)
return
}
certID := chi.URLParam(r, "certID")

cert, err := h.db.GetCertificate(ctx, certID)
if err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error retrieving certificate"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate"))
return
}
if cert.AccountID != acc.ID {
api.WriteError(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own certificate '%s'", acc.ID, certID))
return
}
Expand Down
Loading