Add ACME CA capabilities

This commit is contained in:
max furman 2019-05-26 17:41:10 -07:00
parent 68ab03dc1b
commit e3826dd1c3
54 changed files with 15687 additions and 184 deletions

View file

@ -28,8 +28,7 @@ import (
// Authority is the interface implemented by a CA authority.
type Authority interface {
SSHAuthority
// NOTE: Authorize will be deprecated in future releases. Please use the
// context specific Authorize[Sign|Revoke|etc.] methods.
// context specifies the Authorize[Sign|Revoke|etc.] method.
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
AuthorizeSign(ott string) ([]provisioner.SignOption, error)
GetTLSOptions() *tlsutil.TLSOptions
@ -37,6 +36,7 @@ type Authority interface {
Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error)
Renew(peer *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error)
LoadProvisionerByID(string) (provisioner.Interface, error)
GetProvisioners(cursor string, limit int) (provisioner.List, string, error)
Revoke(*authority.RevokeOptions) error
GetEncryptedKey(kid string) (string, error)
@ -308,13 +308,12 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusCreated)
logCertificate(w, cert)
JSON(w, &SignResponse{
JSONStatus(w, &SignResponse{
ServerPEM: Certificate{cert},
CaPEM: Certificate{root},
TLSOptions: h.Authority.GetTLSOptions(),
})
}, http.StatusCreated)
}
// Renew uses the information of certificate in the TLS connection to create a
@ -331,13 +330,12 @@ func (h *caHandler) Renew(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusCreated)
logCertificate(w, cert)
JSON(w, &SignResponse{
JSONStatus(w, &SignResponse{
ServerPEM: Certificate{cert},
CaPEM: Certificate{root},
TLSOptions: h.Authority.GetTLSOptions(),
})
}, http.StatusCreated)
}
// Provisioners returns the list of provisioners configured in the authority.
@ -383,10 +381,9 @@ func (h *caHandler) Roots(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{roots[i]}
}
w.WriteHeader(http.StatusCreated)
JSON(w, &RootsResponse{
JSONStatus(w, &RootsResponse{
Certificates: certs,
})
}, http.StatusCreated)
}
// Federation returns all the public certificates in the federation.
@ -402,10 +399,9 @@ func (h *caHandler) Federation(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{federated[i]}
}
w.WriteHeader(http.StatusCreated)
JSON(w, &FederationResponse{
JSONStatus(w, &FederationResponse{
Certificates: certs,
})
}, http.StatusCreated)
}
var oidStepProvisioner = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}

View file

@ -506,6 +506,7 @@ type mockAuthority struct {
signSSHAddUser func(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error)
loadProvisionerByID func(provID string) (provisioner.Interface, error)
getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error)
revoke func(*authority.RevokeOptions) error
getEncryptedKey func(kid string) (string, error)
@ -581,6 +582,13 @@ func (m *mockAuthority) LoadProvisionerByCertificate(cert *x509.Certificate) (pr
return m.ret1.(provisioner.Interface), m.err
}
func (m *mockAuthority) LoadProvisionerByID(provID string) (provisioner.Interface, error) {
if m.loadProvisionerByID != nil {
return m.loadProvisionerByID(provID)
}
return m.ret1.(provisioner.Interface), m.err
}
func (m *mockAuthority) Revoke(opts *authority.RevokeOptions) error {
if m.revoke != nil {
return m.revoke(opts)

View file

@ -7,6 +7,7 @@ import (
"os"
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/logging"
)
@ -109,7 +110,13 @@ func NotFound(err error) error {
// WriteError writes to w a JSON representation of the given error.
func WriteError(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json")
switch k := err.(type) {
case *acme.Error:
w.Header().Set("Content-Type", "application/problem+json")
err = k.ToACME()
default:
w.Header().Set("Content-Type", "application/json")
}
cause := errors.Cause(err)
if sc, ok := err.(StatusCoder); ok {
w.WriteHeader(sc.StatusCode())

View file

@ -87,8 +87,6 @@ func (h *caHandler) Revoke(w http.ResponseWriter, r *http.Request) {
}
logRevoke(w, opts)
w.WriteHeader(http.StatusOK)
JSON(w, &RevokeResponse{Status: "ok"})
}

View file

@ -10,6 +10,11 @@ import (
"github.com/smallstep/certificates/logging"
)
// EnableLogger is an interface that enables response logging for an object.
type EnableLogger interface {
ToLog() (interface{}, error)
}
// LogError adds to the response writer the given error if it implements
// logging.ResponseLogger. If it does not implement it, then writes the error
// using the log package.
@ -23,12 +28,40 @@ func LogError(rw http.ResponseWriter, err error) {
}
}
// LogEnabledResponse log the response object if it implements the EnableLogger
// interface.
func LogEnabledResponse(rw http.ResponseWriter, v interface{}) {
if el, ok := v.(EnableLogger); ok {
out, err := el.ToLog()
if err != nil {
LogError(rw, err)
return
}
if rl, ok := rw.(logging.ResponseLogger); ok {
rl.WithFields(map[string]interface{}{
"response": out,
})
} else {
log.Println(out)
}
}
}
// JSON writes the passed value into the http.ResponseWriter.
func JSON(w http.ResponseWriter, v interface{}) {
JSONStatus(w, v, http.StatusOK)
}
// JSONStatus writes the given value into the http.ResponseWriter and the
// given status is written as the status code of the response.
func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
LogError(w, err)
return
}
LogEnabledResponse(w, v)
}
// ReadJSON reads JSON from the request body and stores it in the value