forked from TrueCloudLab/certificates
Merge pull request #1374 from smallstep/herman/log-ssh-certificate
Log SSH certificates
This commit is contained in:
commit
017c3273ef
6 changed files with 75 additions and 4 deletions
40
api/api.go
40
api/api.go
|
@ -1,6 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
|
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
|
||||||
|
@ -20,6 +21,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.step.sm/crypto/sshutil"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/smallstep/certificates/api/log"
|
"github.com/smallstep/certificates/api/log"
|
||||||
"github.com/smallstep/certificates/api/render"
|
"github.com/smallstep/certificates/api/render"
|
||||||
|
@ -469,7 +472,7 @@ func logOtt(w http.ResponseWriter, token string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogCertificate add certificate fields to the log message.
|
// LogCertificate adds certificate fields to the log message.
|
||||||
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
||||||
if rl, ok := w.(logging.ResponseLogger); ok {
|
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
|
@ -501,6 +504,41 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogSSHCertificate adds SSH certificate fields to the log message.
|
||||||
|
func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) {
|
||||||
|
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||||
|
mak := bytes.TrimSpace(ssh.MarshalAuthorizedKey(cert))
|
||||||
|
var certificate string
|
||||||
|
parts := strings.Split(string(mak), " ")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
certificate = parts[1]
|
||||||
|
}
|
||||||
|
var userOrHost string
|
||||||
|
if cert.CertType == ssh.HostCert {
|
||||||
|
userOrHost = "host"
|
||||||
|
} else {
|
||||||
|
userOrHost = "user"
|
||||||
|
}
|
||||||
|
certificateType := fmt.Sprintf("%s %s certificate", parts[0], userOrHost) // e.g. ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"serial": cert.Serial,
|
||||||
|
"principals": cert.ValidPrincipals,
|
||||||
|
"valid-from": time.Unix(int64(cert.ValidAfter), 0).Format(time.RFC3339),
|
||||||
|
"valid-to": time.Unix(int64(cert.ValidBefore), 0).Format(time.RFC3339),
|
||||||
|
"certificate": certificate,
|
||||||
|
"certificate-type": certificateType,
|
||||||
|
}
|
||||||
|
fingerprint, err := sshutil.FormatFingerprint(mak, sshutil.DefaultFingerprint)
|
||||||
|
if err == nil {
|
||||||
|
fpParts := strings.Split(fingerprint, " ")
|
||||||
|
if len(fpParts) > 3 {
|
||||||
|
m["public-key"] = fmt.Sprintf("%s %s", fpParts[1], fpParts[len(fpParts)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rl.WithFields(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ParseCursor parses the cursor and limit from the request query params.
|
// ParseCursor parses the cursor and limit from the request query params.
|
||||||
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()
|
||||||
|
|
|
@ -29,13 +29,14 @@ import (
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
sassert "github.com/stretchr/testify/assert"
|
sassert "github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
"go.step.sm/crypto/x509util"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
squarejose "gopkg.in/square/go-jose.v2"
|
squarejose "gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"go.step.sm/crypto/jose"
|
|
||||||
"go.step.sm/crypto/x509util"
|
|
||||||
|
|
||||||
"github.com/smallstep/assert"
|
"github.com/smallstep/assert"
|
||||||
|
|
||||||
"github.com/smallstep/certificates/authority"
|
"github.com/smallstep/certificates/authority"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/errs"
|
"github.com/smallstep/certificates/errs"
|
||||||
|
@ -1657,3 +1658,31 @@ func TestProvisionersResponse_MarshalJSON(t *testing.T) {
|
||||||
// MarshalJSON must not affect the struct properties itself
|
// MarshalJSON must not affect the struct properties itself
|
||||||
sassert.Equal(t, expList, r.Provisioners)
|
sassert.Equal(t, expList, r.Provisioners)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
fixtureECDSACertificate = `ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLnkvSk4odlo3b1R+RDw+LmorL3RkN354IilCIVFVen4AAAAIbmlzdHAyNTYAAABBBHjKHss8WM2ffMYlavisoLXR0I6UEIU+cidV1ogEH1U6+/SYaFPrlzQo0tGLM5CNkMbhInbyasQsrHzn8F1Rt7nHg5/tcSf9qwAAAAEAAAAGaGVybWFuAAAACgAAAAZoZXJtYW4AAAAAY8kvJwAAAABjyhBjAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEE/ayqpPrZZF5uA1UlDt4FreTf15agztQIzpxnWq/XoxAHzagRSkFGkdgFpjgsfiRpP8URHH3BZScqc0ZDCTxhoQAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAJuP1wCVwoyrKrEtHGfFXrVbRHySDjvXtS1tVTdHyqymAAAAIBa/CSSzfZb4D2NLP+eEmOOMJwSjYOiNM8fiOoAaqglI herman`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogSSHCertificate(t *testing.T) {
|
||||||
|
|
||||||
|
out, _, _, _, err := ssh.ParseAuthorizedKey([]byte(fixtureECDSACertificate))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cert, ok := out.(*ssh.Certificate)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
rl := logging.NewResponseLogger(w)
|
||||||
|
LogSSHCertificate(rl, cert)
|
||||||
|
|
||||||
|
sassert.Equal(t, 200, w.Result().StatusCode)
|
||||||
|
|
||||||
|
fields := rl.Fields()
|
||||||
|
sassert.Equal(t, uint64(14376510277651266987), fields["serial"])
|
||||||
|
sassert.Equal(t, []string{"herman"}, fields["principals"])
|
||||||
|
sassert.Equal(t, "ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate", fields["certificate-type"])
|
||||||
|
sassert.Equal(t, time.Unix(1674129191, 0).Format(time.RFC3339), fields["valid-from"])
|
||||||
|
sassert.Equal(t, time.Unix(1674186851, 0).Format(time.RFC3339), fields["valid-to"])
|
||||||
|
sassert.Equal(t, "AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLnkvSk4odlo3b1R+RDw+LmorL3RkN354IilCIVFVen4AAAAIbmlzdHAyNTYAAABBBHjKHss8WM2ffMYlavisoLXR0I6UEIU+cidV1ogEH1U6+/SYaFPrlzQo0tGLM5CNkMbhInbyasQsrHzn8F1Rt7nHg5/tcSf9qwAAAAEAAAAGaGVybWFuAAAACgAAAAZoZXJtYW4AAAAAY8kvJwAAAABjyhBjAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEE/ayqpPrZZF5uA1UlDt4FreTf15agztQIzpxnWq/XoxAHzagRSkFGkdgFpjgsfiRpP8URHH3BZScqc0ZDCTxhoQAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAJuP1wCVwoyrKrEtHGfFXrVbRHySDjvXtS1tVTdHyqymAAAAIBa/CSSzfZb4D2NLP+eEmOOMJwSjYOiNM8fiOoAaqglI", fields["certificate"])
|
||||||
|
sassert.Equal(t, "SHA256:RvkDPGwl/G9d7LUFm1kmWhvOD9I/moPq4yxcb0STwr0 (ECDSA-CERT)", fields["public-key"])
|
||||||
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ func Sign(w http.ResponseWriter, r *http.Request) {
|
||||||
if len(certChainPEM) > 1 {
|
if len(certChainPEM) > 1 {
|
||||||
caPEM = certChainPEM[1]
|
caPEM = certChainPEM[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
LogCertificate(w, certChain[0])
|
LogCertificate(w, certChain[0])
|
||||||
render.JSONStatus(w, &SignResponse{
|
render.JSONStatus(w, &SignResponse{
|
||||||
ServerPEM: certChainPEM[0],
|
ServerPEM: certChainPEM[0],
|
||||||
|
|
|
@ -338,6 +338,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
|
||||||
identityCertificate = certChainToPEM(certChain)
|
identityCertificate = certChainToPEM(certChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogSSHCertificate(w, cert)
|
||||||
render.JSONStatus(w, &SSHSignResponse{
|
render.JSONStatus(w, &SSHSignResponse{
|
||||||
Certificate: SSHCertificate{cert},
|
Certificate: SSHCertificate{cert},
|
||||||
AddUserCertificate: addUserCertificate,
|
AddUserCertificate: addUserCertificate,
|
||||||
|
|
|
@ -89,6 +89,7 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogSSHCertificate(w, newCert)
|
||||||
render.JSONStatus(w, &SSHRekeyResponse{
|
render.JSONStatus(w, &SSHRekeyResponse{
|
||||||
Certificate: SSHCertificate{newCert},
|
Certificate: SSHCertificate{newCert},
|
||||||
IdentityCertificate: identity,
|
IdentityCertificate: identity,
|
||||||
|
|
|
@ -81,6 +81,7 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogSSHCertificate(w, newCert)
|
||||||
render.JSONStatus(w, &SSHSignResponse{
|
render.JSONStatus(w, &SSHSignResponse{
|
||||||
Certificate: SSHCertificate{newCert},
|
Certificate: SSHCertificate{newCert},
|
||||||
IdentityCertificate: identity,
|
IdentityCertificate: identity,
|
||||||
|
|
Loading…
Reference in a new issue