Merge pull request #38 from smallstep/log-certificate
Add certificate information to logs
This commit is contained in:
commit
fcd1da970f
2 changed files with 180 additions and 11 deletions
69
api/api.go
69
api/api.go
|
@ -1,10 +1,16 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -13,6 +19,7 @@ import (
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/authority"
|
"github.com/smallstep/certificates/authority"
|
||||||
|
"github.com/smallstep/certificates/logging"
|
||||||
"github.com/smallstep/cli/crypto/tlsutil"
|
"github.com/smallstep/cli/crypto/tlsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -252,6 +259,8 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) {
|
||||||
WriteError(w, BadRequest(errors.Wrap(err, "error reading request body")))
|
WriteError(w, BadRequest(errors.Wrap(err, "error reading request body")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOtt(w, body.OTT)
|
||||||
if err := body.Validate(); err != nil {
|
if err := body.Validate(); err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
|
@ -275,6 +284,7 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
logCertificate(w, cert)
|
||||||
JSON(w, &SignResponse{
|
JSON(w, &SignResponse{
|
||||||
ServerPEM: Certificate{cert},
|
ServerPEM: Certificate{cert},
|
||||||
CaPEM: Certificate{root},
|
CaPEM: Certificate{root},
|
||||||
|
@ -297,6 +307,7 @@ func (h *caHandler) Renew(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
logCertificate(w, cert)
|
||||||
JSON(w, &SignResponse{
|
JSON(w, &SignResponse{
|
||||||
ServerPEM: Certificate{cert},
|
ServerPEM: Certificate{cert},
|
||||||
CaPEM: Certificate{root},
|
CaPEM: Certificate{root},
|
||||||
|
@ -372,6 +383,48 @@ func (h *caHandler) Federation(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oidStepProvisioner = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}
|
||||||
|
|
||||||
|
type stepProvisioner struct {
|
||||||
|
Type int
|
||||||
|
Name []byte
|
||||||
|
CredentialID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func logOtt(w http.ResponseWriter, token string) {
|
||||||
|
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||||
|
rl.WithFields(map[string]interface{}{
|
||||||
|
"ott": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
||||||
|
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"serial": cert.SerialNumber,
|
||||||
|
"subject": cert.Subject.CommonName,
|
||||||
|
"issuer": cert.Issuer.CommonName,
|
||||||
|
"valid-from": cert.NotBefore.Format(time.RFC3339),
|
||||||
|
"valid-to": cert.NotAfter.Format(time.RFC3339),
|
||||||
|
"public-key": fmtPublicKey(cert),
|
||||||
|
"certificate": base64.StdEncoding.EncodeToString(cert.Raw),
|
||||||
|
}
|
||||||
|
for _, ext := range cert.Extensions {
|
||||||
|
if ext.Id.Equal(oidStepProvisioner) {
|
||||||
|
val := &stepProvisioner{}
|
||||||
|
rest, err := asn1.Unmarshal(ext.Value, val)
|
||||||
|
if err != nil || len(rest) > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m["provisioner"] = fmt.Sprintf("%s (%s)", val.Name, val.CredentialID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rl.WithFields(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseCursor(r *http.Request) (cursor string, limit int, err error) {
|
func parseCursor(r *http.Request) (cursor string, limit int, err error) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
cursor = q.Get("cursor")
|
cursor = q.Get("cursor")
|
||||||
|
@ -383,3 +436,19 @@ func parseCursor(r *http.Request) (cursor string, limit int, err error) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add support for Ed25519 once it's supported
|
||||||
|
func fmtPublicKey(cert *x509.Certificate) string {
|
||||||
|
var params string
|
||||||
|
switch pk := cert.PublicKey.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
params = pk.Curve.Params().Name
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
params = strconv.Itoa(pk.Size() * 8)
|
||||||
|
case *dsa.PublicKey:
|
||||||
|
params = strconv.Itoa(pk.Q.BitLen() * 8)
|
||||||
|
default:
|
||||||
|
params = "unknown"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s", cert.PublicKeyAlgorithm, params)
|
||||||
|
}
|
||||||
|
|
122
api/api_test.go
122
api/api_test.go
|
@ -3,12 +3,19 @@ package api
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -18,6 +25,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/smallstep/certificates/authority"
|
"github.com/smallstep/certificates/authority"
|
||||||
|
"github.com/smallstep/certificates/logging"
|
||||||
"github.com/smallstep/cli/crypto/tlsutil"
|
"github.com/smallstep/cli/crypto/tlsutil"
|
||||||
"github.com/smallstep/cli/jose"
|
"github.com/smallstep/cli/jose"
|
||||||
)
|
)
|
||||||
|
@ -98,6 +106,23 @@ Q7vMNPBWrJWu+A++vHY61WGET+h4lY3GFr2I8OE4IiHPQi1D7Y0+fwOmStwuRPM4
|
||||||
DCbKzWTW8lqVdp9Kyf7XEhhc2R8C5w==
|
DCbKzWTW8lqVdp9Kyf7XEhhc2R8C5w==
|
||||||
-----END CERTIFICATE REQUEST-----`
|
-----END CERTIFICATE REQUEST-----`
|
||||||
|
|
||||||
|
stepCertPEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIChTCCAiugAwIBAgIRAJ3O5T28Rdj2lr/UPjf+GAUwCgYIKoZIzj0EAwIwJDEi
|
||||||
|
MCAGA1UEAxMZU21hbGxzdGVwIEludGVybWVkaWF0ZSBDQTAeFw0xOTAyMjAyMDE1
|
||||||
|
NDNaFw0xOTAyMjEyMDE1NDNaMHExCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEW
|
||||||
|
MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEcMBoGA1UEChMTU21hbGxzdGVwIExhYnMg
|
||||||
|
SW5jLjEfMB0GA1UEAxMWaW50ZXJuYWwuc21hbGxzdGVwLmNvbTBZMBMGByqGSM49
|
||||||
|
AgEGCCqGSM49AwEHA0IABC0aKrTNl+gXFuNkXisqX4/foLO3VMt+Kphngziim+fz
|
||||||
|
aJhiS9JU+oFYLTNW6HWGUD8CNzfwrmWlVsAmiJwHKlKjgfAwge0wDgYDVR0PAQH/
|
||||||
|
BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU
|
||||||
|
JheKvlZqNv1IcgaC8WOS1Zg0i1QwHwYDVR0jBBgwFoAUu97PaFQPfuyKOeew7Hg4
|
||||||
|
5WFIAVMwIQYDVR0RBBowGIIWaW50ZXJuYWwuc21hbGxzdGVwLmNvbTBZBgwrBgEE
|
||||||
|
AYKkZMYoQAEESTBHAgEBBBVtYXJpYW5vQHNtYWxsc3RlcC5jb20EK2pPMzdkdERi
|
||||||
|
a3UtUW5hYnM1VlIwWXc2WUZGdjl3ZUExOGRwM2h0dmRFanMwCgYIKoZIzj0EAwID
|
||||||
|
SAAwRQIhAIrn17fP5CBrGtKuhyPiq6eSwryBCf8ki+k17u5a+E/LAiB24Y2E0Put
|
||||||
|
nIHOI54lAqDeF7A0y73fPRVCiJEWmuxz0g==
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
pubKey = `{
|
pubKey = `{
|
||||||
"use": "sig",
|
"use": "sig",
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
|
@ -566,6 +591,9 @@ func Test_caHandler_Sign(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected1 := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`)
|
||||||
|
expected2 := []byte(`{"crt":"` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
@ -575,16 +603,16 @@ func Test_caHandler_Sign(t *testing.T) {
|
||||||
root *x509.Certificate
|
root *x509.Certificate
|
||||||
signErr error
|
signErr error
|
||||||
statusCode int
|
statusCode int
|
||||||
|
expected []byte
|
||||||
}{
|
}{
|
||||||
{"ok", string(valid), nil, nil, parseCertificate(certPEM), parseCertificate(rootPEM), nil, http.StatusCreated},
|
{"ok", string(valid), nil, nil, parseCertificate(certPEM), parseCertificate(rootPEM), nil, http.StatusCreated, expected1},
|
||||||
{"json read error", "{", nil, nil, nil, nil, nil, http.StatusBadRequest},
|
{"ok with Provisioner", string(valid), nil, nil, parseCertificate(stepCertPEM), parseCertificate(rootPEM), nil, http.StatusCreated, expected2},
|
||||||
{"validate error", string(invalid), nil, nil, nil, nil, nil, http.StatusBadRequest},
|
{"json read error", "{", nil, nil, nil, nil, nil, http.StatusBadRequest, nil},
|
||||||
{"authorize error", string(valid), nil, fmt.Errorf("an error"), nil, nil, nil, http.StatusUnauthorized},
|
{"validate error", string(invalid), nil, nil, nil, nil, nil, http.StatusBadRequest, nil},
|
||||||
{"sign error", string(valid), nil, nil, nil, nil, fmt.Errorf("an error"), http.StatusForbidden},
|
{"authorize error", string(valid), nil, fmt.Errorf("an error"), nil, nil, nil, http.StatusUnauthorized, nil},
|
||||||
|
{"sign error", string(valid), nil, nil, nil, nil, fmt.Errorf("an error"), http.StatusForbidden, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`)
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
h := New(&mockAuthority{
|
h := New(&mockAuthority{
|
||||||
|
@ -598,7 +626,7 @@ func Test_caHandler_Sign(t *testing.T) {
|
||||||
}).(*caHandler)
|
}).(*caHandler)
|
||||||
req := httptest.NewRequest("POST", "http://example.com/sign", strings.NewReader(tt.input))
|
req := httptest.NewRequest("POST", "http://example.com/sign", strings.NewReader(tt.input))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
h.Sign(w, req)
|
h.Sign(logging.NewResponseLogger(w), req)
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
|
|
||||||
if res.StatusCode != tt.statusCode {
|
if res.StatusCode != tt.statusCode {
|
||||||
|
@ -611,8 +639,8 @@ func Test_caHandler_Sign(t *testing.T) {
|
||||||
t.Errorf("caHandler.Root unexpected error = %v", err)
|
t.Errorf("caHandler.Root unexpected error = %v", err)
|
||||||
}
|
}
|
||||||
if tt.statusCode < http.StatusBadRequest {
|
if tt.statusCode < http.StatusBadRequest {
|
||||||
if !bytes.Equal(bytes.TrimSpace(body), expected) {
|
if !bytes.Equal(bytes.TrimSpace(body), tt.expected) {
|
||||||
t.Errorf("caHandler.Root Body = %s, wants %s", body, expected)
|
t.Errorf("caHandler.Root Body = %s, wants %s", body, tt.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -650,7 +678,7 @@ func Test_caHandler_Renew(t *testing.T) {
|
||||||
req := httptest.NewRequest("POST", "http://example.com/renew", nil)
|
req := httptest.NewRequest("POST", "http://example.com/renew", nil)
|
||||||
req.TLS = tt.tls
|
req.TLS = tt.tls
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
h.Renew(w, req)
|
h.Renew(logging.NewResponseLogger(w), req)
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
|
|
||||||
if res.StatusCode != tt.statusCode {
|
if res.StatusCode != tt.statusCode {
|
||||||
|
@ -920,3 +948,75 @@ func Test_caHandler_Federation(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_fmtPublicKey(t *testing.T) {
|
||||||
|
p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rsa1024, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var dsa2048 dsa.PrivateKey
|
||||||
|
if err := dsa.GenerateParameters(&dsa2048.Parameters, rand.Reader, dsa.L2048N256); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := dsa.GenerateKey(&dsa2048, rand.Reader); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
pub, priv interface{}
|
||||||
|
cert *x509.Certificate
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"p256", args{p256.Public(), p256, nil}, "ECDSA P-256"},
|
||||||
|
{"rsa1024", args{rsa1024.Public(), rsa1024, nil}, "RSA 1024"},
|
||||||
|
{"dsa2048", args{cert: &x509.Certificate{PublicKeyAlgorithm: x509.DSA, PublicKey: &dsa2048.PublicKey}}, "DSA 2048"},
|
||||||
|
{"unknown", args{cert: &x509.Certificate{PublicKeyAlgorithm: x509.ECDSA, PublicKey: []byte("12345678")}}, "ECDSA unknown"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var cert *x509.Certificate
|
||||||
|
if tt.args.cert != nil {
|
||||||
|
cert = tt.args.cert
|
||||||
|
} else {
|
||||||
|
cert = mustCertificate(t, tt.args.pub, tt.args.priv)
|
||||||
|
}
|
||||||
|
if got := fmtPublicKey(cert); got != tt.want {
|
||||||
|
t.Errorf("fmtPublicKey() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustCertificate(t *testing.T, pub, priv interface{}) *x509.Certificate {
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Acme Co"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(24 * time.Hour),
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, &template, &template, pub, priv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(der)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue