2021-05-03 19:48:20 +00:00
|
|
|
package admin
|
2021-05-03 19:48:20 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/smallstep/certificates/errs"
|
|
|
|
"github.com/smallstep/certificates/logging"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ProblemType is the type of the ACME problem.
|
|
|
|
type ProblemType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ErrorNotFoundType resource not found.
|
|
|
|
ErrorNotFoundType ProblemType = iota
|
|
|
|
// ErrorAuthorityMismatchType resource Authority ID does not match the
|
|
|
|
// context Authority ID.
|
|
|
|
ErrorAuthorityMismatchType
|
|
|
|
// ErrorDeletedType resource has been deleted.
|
|
|
|
ErrorDeletedType
|
2021-05-12 07:03:40 +00:00
|
|
|
// ErrorBadRequestType bad request.
|
|
|
|
ErrorBadRequestType
|
2021-05-03 19:48:20 +00:00
|
|
|
// ErrorNotImplementedType not implemented.
|
|
|
|
ErrorNotImplementedType
|
|
|
|
// ErrorUnauthorizedType internal server error.
|
|
|
|
ErrorUnauthorizedType
|
2021-05-03 19:48:20 +00:00
|
|
|
// ErrorServerInternalType internal server error.
|
|
|
|
ErrorServerInternalType
|
|
|
|
)
|
|
|
|
|
|
|
|
// String returns the string representation of the acme problem type,
|
|
|
|
// fulfilling the Stringer interface.
|
|
|
|
func (ap ProblemType) String() string {
|
|
|
|
switch ap {
|
|
|
|
case ErrorNotFoundType:
|
|
|
|
return "notFound"
|
|
|
|
case ErrorAuthorityMismatchType:
|
|
|
|
return "authorityMismatch"
|
|
|
|
case ErrorDeletedType:
|
|
|
|
return "deleted"
|
2021-05-12 07:03:40 +00:00
|
|
|
case ErrorBadRequestType:
|
|
|
|
return "badRequest"
|
2021-05-03 19:48:20 +00:00
|
|
|
case ErrorNotImplementedType:
|
|
|
|
return "notImplemented"
|
|
|
|
case ErrorUnauthorizedType:
|
|
|
|
return "unauthorized"
|
2021-05-03 19:48:20 +00:00
|
|
|
case ErrorServerInternalType:
|
|
|
|
return "internalServerError"
|
|
|
|
default:
|
|
|
|
return fmt.Sprintf("unsupported error type '%d'", int(ap))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type errorMetadata struct {
|
|
|
|
details string
|
|
|
|
status int
|
|
|
|
typ string
|
|
|
|
String string
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
errorServerInternalMetadata = errorMetadata{
|
|
|
|
typ: ErrorServerInternalType.String(),
|
|
|
|
details: "the server experienced an internal error",
|
|
|
|
status: 500,
|
|
|
|
}
|
|
|
|
errorMap = map[ProblemType]errorMetadata{
|
|
|
|
ErrorNotFoundType: {
|
|
|
|
typ: ErrorNotFoundType.String(),
|
|
|
|
details: "resource not found",
|
|
|
|
status: 400,
|
|
|
|
},
|
|
|
|
ErrorAuthorityMismatchType: {
|
|
|
|
typ: ErrorAuthorityMismatchType.String(),
|
|
|
|
details: "resource not owned by authority",
|
|
|
|
status: 401,
|
|
|
|
},
|
|
|
|
ErrorDeletedType: {
|
2021-05-12 07:03:40 +00:00
|
|
|
typ: ErrorDeletedType.String(),
|
2021-05-03 19:48:20 +00:00
|
|
|
details: "resource is deleted",
|
2021-05-03 19:48:20 +00:00
|
|
|
status: http.StatusUnauthorized,
|
|
|
|
},
|
|
|
|
ErrorNotImplementedType: {
|
|
|
|
typ: ErrorNotImplementedType.String(),
|
|
|
|
details: "not implemented",
|
|
|
|
status: http.StatusNotImplemented,
|
2021-05-03 19:48:20 +00:00
|
|
|
},
|
2021-05-12 07:03:40 +00:00
|
|
|
ErrorBadRequestType: {
|
|
|
|
typ: ErrorBadRequestType.String(),
|
|
|
|
details: "bad request",
|
2021-05-03 19:48:20 +00:00
|
|
|
status: http.StatusBadRequest,
|
|
|
|
},
|
|
|
|
ErrorUnauthorizedType: {
|
|
|
|
typ: ErrorUnauthorizedType.String(),
|
|
|
|
details: "unauthorized",
|
|
|
|
status: http.StatusUnauthorized,
|
2021-05-12 07:03:40 +00:00
|
|
|
},
|
2021-05-03 19:48:20 +00:00
|
|
|
ErrorServerInternalType: errorServerInternalMetadata,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Error represents an ACME
|
|
|
|
type Error struct {
|
2021-05-18 04:07:25 +00:00
|
|
|
Type string `json:"type"`
|
|
|
|
Detail string `json:"detail"`
|
|
|
|
Message string `json:"message"`
|
|
|
|
Err error `json:"-"`
|
|
|
|
Status int `json:"-"`
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
2021-05-07 00:03:12 +00:00
|
|
|
// IsType returns true if the error type matches the input type.
|
|
|
|
func (e *Error) IsType(pt ProblemType) bool {
|
|
|
|
return pt.String() == e.Type
|
|
|
|
}
|
|
|
|
|
2021-05-03 19:48:20 +00:00
|
|
|
// NewError creates a new Error type.
|
|
|
|
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
|
|
|
|
return newError(pt, errors.Errorf(msg, args...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func newError(pt ProblemType, err error) *Error {
|
|
|
|
meta, ok := errorMap[pt]
|
|
|
|
if !ok {
|
|
|
|
meta = errorServerInternalMetadata
|
|
|
|
return &Error{
|
|
|
|
Type: meta.typ,
|
|
|
|
Detail: meta.details,
|
|
|
|
Status: meta.status,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Error{
|
|
|
|
Type: meta.typ,
|
|
|
|
Detail: meta.details,
|
|
|
|
Status: meta.status,
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewErrorISE creates a new ErrorServerInternalType Error.
|
|
|
|
func NewErrorISE(msg string, args ...interface{}) *Error {
|
|
|
|
return NewError(ErrorServerInternalType, msg, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WrapError attempts to wrap the internal error.
|
|
|
|
func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error {
|
|
|
|
switch e := err.(type) {
|
|
|
|
case nil:
|
|
|
|
return nil
|
|
|
|
case *Error:
|
|
|
|
if e.Err == nil {
|
|
|
|
e.Err = errors.Errorf(msg+"; "+e.Detail, args...)
|
|
|
|
} else {
|
|
|
|
e.Err = errors.Wrapf(e.Err, msg, args...)
|
|
|
|
}
|
|
|
|
return e
|
|
|
|
default:
|
|
|
|
return newError(typ, errors.Wrapf(err, msg, args...))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WrapErrorISE shortcut to wrap an internal server error type.
|
|
|
|
func WrapErrorISE(err error, msg string, args ...interface{}) *Error {
|
|
|
|
return WrapError(ErrorServerInternalType, err, msg, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StatusCode returns the status code and implements the StatusCoder interface.
|
|
|
|
func (e *Error) StatusCode() int {
|
|
|
|
return e.Status
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error allows AError to implement the error interface.
|
|
|
|
func (e *Error) Error() string {
|
2021-05-18 04:07:25 +00:00
|
|
|
return e.Err.Error()
|
2021-05-03 19:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Cause returns the internal error and implements the Causer interface.
|
|
|
|
func (e *Error) Cause() error {
|
|
|
|
if e.Err == nil {
|
|
|
|
return errors.New(e.Detail)
|
|
|
|
}
|
|
|
|
return e.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToLog implements the EnableLogger interface.
|
|
|
|
func (e *Error) ToLog() (interface{}, error) {
|
|
|
|
b, err := json.Marshal(e)
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapErrorISE(err, "error marshaling authority.Error for logging")
|
|
|
|
}
|
|
|
|
return string(b), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteError writes to w a JSON representation of the given error.
|
|
|
|
func WriteError(w http.ResponseWriter, err *Error) {
|
2021-05-18 04:07:25 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2021-05-03 19:48:20 +00:00
|
|
|
w.WriteHeader(err.StatusCode())
|
|
|
|
|
2021-05-18 04:07:25 +00:00
|
|
|
err.Message = err.Err.Error()
|
2021-05-03 19:48:20 +00:00
|
|
|
// Write errors in the response writer
|
|
|
|
if rl, ok := w.(logging.ResponseLogger); ok {
|
|
|
|
rl.WithFields(map[string]interface{}{
|
|
|
|
"error": err.Err,
|
|
|
|
})
|
|
|
|
if os.Getenv("STEPDEBUG") == "1" {
|
|
|
|
if e, ok := err.Err.(errs.StackTracer); ok {
|
|
|
|
rl.WithFields(map[string]interface{}{
|
|
|
|
"stack-trace": fmt.Sprintf("%+v", e),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.NewEncoder(w).Encode(err); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}
|