Skip to content

Commit 017c327

Browse files
authored
Merge pull request smallstep#1374 from smallstep/herman/log-ssh-certificate
Log SSH certificates
2 parents f93548d + f17bfdf commit 017c327

File tree

6 files changed

+75
-4
lines changed

6 files changed

+75
-4
lines changed

api/api.go

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"bytes"
45
"context"
56
"crypto"
67
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
@@ -20,6 +21,8 @@ import (
2021

2122
"github.com/go-chi/chi"
2223
"github.com/pkg/errors"
24+
"go.step.sm/crypto/sshutil"
25+
"golang.org/x/crypto/ssh"
2326

2427
"github.com/smallstep/certificates/api/log"
2528
"github.com/smallstep/certificates/api/render"
@@ -469,7 +472,7 @@ func logOtt(w http.ResponseWriter, token string) {
469472
}
470473
}
471474

472-
// LogCertificate add certificate fields to the log message.
475+
// LogCertificate adds certificate fields to the log message.
473476
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
474477
if rl, ok := w.(logging.ResponseLogger); ok {
475478
m := map[string]interface{}{
@@ -501,6 +504,41 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
501504
}
502505
}
503506

507+
// LogSSHCertificate adds SSH certificate fields to the log message.
508+
func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) {
509+
if rl, ok := w.(logging.ResponseLogger); ok {
510+
mak := bytes.TrimSpace(ssh.MarshalAuthorizedKey(cert))
511+
var certificate string
512+
parts := strings.Split(string(mak), " ")
513+
if len(parts) > 1 {
514+
certificate = parts[1]
515+
}
516+
var userOrHost string
517+
if cert.CertType == ssh.HostCert {
518+
userOrHost = "host"
519+
} else {
520+
userOrHost = "user"
521+
}
522+
certificateType := fmt.Sprintf("%s %s certificate", parts[0], userOrHost) // e.g. ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
523+
m := map[string]interface{}{
524+
"serial": cert.Serial,
525+
"principals": cert.ValidPrincipals,
526+
"valid-from": time.Unix(int64(cert.ValidAfter), 0).Format(time.RFC3339),
527+
"valid-to": time.Unix(int64(cert.ValidBefore), 0).Format(time.RFC3339),
528+
"certificate": certificate,
529+
"certificate-type": certificateType,
530+
}
531+
fingerprint, err := sshutil.FormatFingerprint(mak, sshutil.DefaultFingerprint)
532+
if err == nil {
533+
fpParts := strings.Split(fingerprint, " ")
534+
if len(fpParts) > 3 {
535+
m["public-key"] = fmt.Sprintf("%s %s", fpParts[1], fpParts[len(fpParts)-1])
536+
}
537+
}
538+
rl.WithFields(m)
539+
}
540+
}
541+
504542
// ParseCursor parses the cursor and limit from the request query params.
505543
func ParseCursor(r *http.Request) (cursor string, limit int, err error) {
506544
q := r.URL.Query()

api/api_test.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@ import (
2929
"github.com/go-chi/chi"
3030
"github.com/pkg/errors"
3131
sassert "github.com/stretchr/testify/assert"
32-
"golang.org/x/crypto/ssh"
33-
squarejose "gopkg.in/square/go-jose.v2"
34-
32+
"github.com/stretchr/testify/require"
3533
"go.step.sm/crypto/jose"
3634
"go.step.sm/crypto/x509util"
35+
"golang.org/x/crypto/ssh"
36+
squarejose "gopkg.in/square/go-jose.v2"
3737

3838
"github.com/smallstep/assert"
39+
3940
"github.com/smallstep/certificates/authority"
4041
"github.com/smallstep/certificates/authority/provisioner"
4142
"github.com/smallstep/certificates/errs"
@@ -1657,3 +1658,31 @@ func TestProvisionersResponse_MarshalJSON(t *testing.T) {
16571658
// MarshalJSON must not affect the struct properties itself
16581659
sassert.Equal(t, expList, r.Provisioners)
16591660
}
1661+
1662+
const (
1663+
fixtureECDSACertificate = `ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLnkvSk4odlo3b1R+RDw+LmorL3RkN354IilCIVFVen4AAAAIbmlzdHAyNTYAAABBBHjKHss8WM2ffMYlavisoLXR0I6UEIU+cidV1ogEH1U6+/SYaFPrlzQo0tGLM5CNkMbhInbyasQsrHzn8F1Rt7nHg5/tcSf9qwAAAAEAAAAGaGVybWFuAAAACgAAAAZoZXJtYW4AAAAAY8kvJwAAAABjyhBjAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEE/ayqpPrZZF5uA1UlDt4FreTf15agztQIzpxnWq/XoxAHzagRSkFGkdgFpjgsfiRpP8URHH3BZScqc0ZDCTxhoQAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAJuP1wCVwoyrKrEtHGfFXrVbRHySDjvXtS1tVTdHyqymAAAAIBa/CSSzfZb4D2NLP+eEmOOMJwSjYOiNM8fiOoAaqglI herman`
1664+
)
1665+
1666+
func TestLogSSHCertificate(t *testing.T) {
1667+
1668+
out, _, _, _, err := ssh.ParseAuthorizedKey([]byte(fixtureECDSACertificate))
1669+
require.NoError(t, err)
1670+
1671+
cert, ok := out.(*ssh.Certificate)
1672+
require.True(t, ok)
1673+
1674+
w := httptest.NewRecorder()
1675+
rl := logging.NewResponseLogger(w)
1676+
LogSSHCertificate(rl, cert)
1677+
1678+
sassert.Equal(t, 200, w.Result().StatusCode)
1679+
1680+
fields := rl.Fields()
1681+
sassert.Equal(t, uint64(14376510277651266987), fields["serial"])
1682+
sassert.Equal(t, []string{"herman"}, fields["principals"])
1683+
sassert.Equal(t, "ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate", fields["certificate-type"])
1684+
sassert.Equal(t, time.Unix(1674129191, 0).Format(time.RFC3339), fields["valid-from"])
1685+
sassert.Equal(t, time.Unix(1674186851, 0).Format(time.RFC3339), fields["valid-to"])
1686+
sassert.Equal(t, "AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLnkvSk4odlo3b1R+RDw+LmorL3RkN354IilCIVFVen4AAAAIbmlzdHAyNTYAAABBBHjKHss8WM2ffMYlavisoLXR0I6UEIU+cidV1ogEH1U6+/SYaFPrlzQo0tGLM5CNkMbhInbyasQsrHzn8F1Rt7nHg5/tcSf9qwAAAAEAAAAGaGVybWFuAAAACgAAAAZoZXJtYW4AAAAAY8kvJwAAAABjyhBjAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEE/ayqpPrZZF5uA1UlDt4FreTf15agztQIzpxnWq/XoxAHzagRSkFGkdgFpjgsfiRpP8URHH3BZScqc0ZDCTxhoQAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAJuP1wCVwoyrKrEtHGfFXrVbRHySDjvXtS1tVTdHyqymAAAAIBa/CSSzfZb4D2NLP+eEmOOMJwSjYOiNM8fiOoAaqglI", fields["certificate"])
1687+
sassert.Equal(t, "SHA256:RvkDPGwl/G9d7LUFm1kmWhvOD9I/moPq4yxcb0STwr0 (ECDSA-CERT)", fields["public-key"])
1688+
}

api/sign.go

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func Sign(w http.ResponseWriter, r *http.Request) {
8888
if len(certChainPEM) > 1 {
8989
caPEM = certChainPEM[1]
9090
}
91+
9192
LogCertificate(w, certChain[0])
9293
render.JSONStatus(w, &SignResponse{
9394
ServerPEM: certChainPEM[0],

api/ssh.go

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
338338
identityCertificate = certChainToPEM(certChain)
339339
}
340340

341+
LogSSHCertificate(w, cert)
341342
render.JSONStatus(w, &SSHSignResponse{
342343
Certificate: SSHCertificate{cert},
343344
AddUserCertificate: addUserCertificate,

api/sshRekey.go

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
8989
return
9090
}
9191

92+
LogSSHCertificate(w, newCert)
9293
render.JSONStatus(w, &SSHRekeyResponse{
9394
Certificate: SSHCertificate{newCert},
9495
IdentityCertificate: identity,

api/sshRenew.go

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
8181
return
8282
}
8383

84+
LogSSHCertificate(w, newCert)
8485
render.JSONStatus(w, &SSHSignResponse{
8586
Certificate: SSHCertificate{newCert},
8687
IdentityCertificate: identity,

0 commit comments

Comments
 (0)