Skip to content

Commit c26c8d7

Browse files
committed
Add SSH certificate type to metrics
This commit passes the commit updates the Meter interface with SSH certificates and X.509 certificate chains. This allows us to add certificate specific things into the metrics. In this PR we are adding the SSH certificate type, user, or host.
1 parent 4f5c524 commit c26c8d7

File tree

4 files changed

+62
-57
lines changed

4 files changed

+62
-57
lines changed

authority/meter.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,26 @@ package authority
22

33
import (
44
"crypto"
5+
"crypto/x509"
56
"io"
67

78
"go.step.sm/crypto/kms"
89
kmsapi "go.step.sm/crypto/kms/apiv1"
10+
"golang.org/x/crypto/ssh"
911

1012
"github.com/smallstep/certificates/authority/provisioner"
1113
)
1214

1315
// Meter wraps the set of defined callbacks for metrics gatherers.
1416
type Meter interface {
1517
// X509Signed is called whenever an X509 certificate is signed.
16-
X509Signed(provisioner.Interface, error)
18+
X509Signed([]*x509.Certificate, provisioner.Interface, error)
1719

1820
// X509Renewed is called whenever an X509 certificate is renewed.
19-
X509Renewed(provisioner.Interface, error)
21+
X509Renewed([]*x509.Certificate, provisioner.Interface, error)
2022

2123
// X509Rekeyed is called whenever an X509 certificate is rekeyed.
22-
X509Rekeyed(provisioner.Interface, error)
24+
X509Rekeyed([]*x509.Certificate, provisioner.Interface, error)
2325

2426
// X509WebhookAuthorized is called whenever an X509 authoring webhook is called.
2527
X509WebhookAuthorized(provisioner.Interface, error)
@@ -28,13 +30,13 @@ type Meter interface {
2830
X509WebhookEnriched(provisioner.Interface, error)
2931

3032
// SSHSigned is called whenever an SSH certificate is signed.
31-
SSHSigned(provisioner.Interface, error)
33+
SSHSigned(*ssh.Certificate, provisioner.Interface, error)
3234

3335
// SSHRenewed is called whenever an SSH certificate is renewed.
34-
SSHRenewed(provisioner.Interface, error)
36+
SSHRenewed(*ssh.Certificate, provisioner.Interface, error)
3537

3638
// SSHRekeyed is called whenever an SSH certificate is rekeyed.
37-
SSHRekeyed(provisioner.Interface, error)
39+
SSHRekeyed(*ssh.Certificate, provisioner.Interface, error)
3840

3941
// SSHWebhookAuthorized is called whenever an SSH authoring webhook is called.
4042
SSHWebhookAuthorized(provisioner.Interface, error)
@@ -49,17 +51,17 @@ type Meter interface {
4951
// noopMeter implements a noop [Meter].
5052
type noopMeter struct{}
5153

52-
func (noopMeter) SSHRekeyed(provisioner.Interface, error) {}
53-
func (noopMeter) SSHRenewed(provisioner.Interface, error) {}
54-
func (noopMeter) SSHSigned(provisioner.Interface, error) {}
55-
func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {}
56-
func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {}
57-
func (noopMeter) X509Rekeyed(provisioner.Interface, error) {}
58-
func (noopMeter) X509Renewed(provisioner.Interface, error) {}
59-
func (noopMeter) X509Signed(provisioner.Interface, error) {}
60-
func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {}
61-
func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {}
62-
func (noopMeter) KMSSigned(error) {}
54+
func (noopMeter) SSHRekeyed(*ssh.Certificate, provisioner.Interface, error) {}
55+
func (noopMeter) SSHRenewed(*ssh.Certificate, provisioner.Interface, error) {}
56+
func (noopMeter) SSHSigned(*ssh.Certificate, provisioner.Interface, error) {}
57+
func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {}
58+
func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {}
59+
func (noopMeter) X509Rekeyed([]*x509.Certificate, provisioner.Interface, error) {}
60+
func (noopMeter) X509Renewed([]*x509.Certificate, provisioner.Interface, error) {}
61+
func (noopMeter) X509Signed([]*x509.Certificate, provisioner.Interface, error) {}
62+
func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {}
63+
func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {}
64+
func (noopMeter) KMSSigned(error) {}
6365

6466
type instrumentedKeyManager struct {
6567
kms.KeyManager

authority/ssh.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (*
149149
// SignSSH creates a signed SSH certificate with the given public key and options.
150150
func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
151151
cert, prov, err := a.signSSH(ctx, key, opts, signOpts...)
152-
a.meter.SSHSigned(prov, err)
152+
a.meter.SSHSigned(cert, prov, err)
153153
return cert, err
154154
}
155155

@@ -337,7 +337,7 @@ func (a *Authority) isAllowedToSignSSHCertificate(cert *ssh.Certificate) error {
337337
// RenewSSH creates a signed SSH certificate using the old SSH certificate as a template.
338338
func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) {
339339
cert, prov, err := a.renewSSH(ctx, oldCert)
340-
a.meter.SSHRenewed(prov, err)
340+
a.meter.SSHRenewed(cert, prov, err)
341341
return cert, err
342342
}
343343

@@ -408,7 +408,7 @@ func (a *Authority) renewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
408408
// RekeySSH creates a signed SSH certificate using the old SSH certificate as a template.
409409
func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
410410
cert, prov, err := a.rekeySSH(ctx, oldCert, pub, signOpts...)
411-
a.meter.SSHRekeyed(prov, err)
411+
a.meter.SSHRekeyed(cert, prov, err)
412412
return cert, err
413413
}
414414

authority/tls.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
118118
// request, taking the provided context.Context.
119119
func (a *Authority) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
120120
chain, prov, err := a.signX509(ctx, csr, signOpts, extraOpts...)
121-
a.meter.X509Signed(prov, err)
121+
a.meter.X509Signed(chain, prov, err)
122122
return chain, err
123123
}
124124

@@ -372,9 +372,9 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
372372
func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) {
373373
chain, prov, err := a.renewContext(ctx, oldCert, pk)
374374
if pk == nil {
375-
a.meter.X509Renewed(prov, err)
375+
a.meter.X509Renewed(chain, prov, err)
376376
} else {
377-
a.meter.X509Rekeyed(prov, err)
377+
a.meter.X509Rekeyed(chain, prov, err)
378378
}
379379
return chain, err
380380
}

internal/metrix/meter.go

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
package metrix
33

44
import (
5+
"crypto/x509"
56
"net/http"
67
"strconv"
78
"time"
89

910
"github.com/smallstep/certificates/authority/provisioner"
11+
"golang.org/x/crypto/ssh"
1012

1113
"github.com/prometheus/client_golang/prometheus"
1214
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -15,6 +17,8 @@ import (
1517
// New initializes and returns a new [Meter].
1618
func New() (m *Meter) {
1719
initializedAt := time.Now()
20+
defaultLabels := []string{"provisioner", "success"}
21+
sshSignLabels := []string{"provisioner", "success", "type"}
1822

1923
m = &Meter{
2024
uptime: prometheus.NewGaugeFunc(
@@ -27,8 +31,8 @@ func New() (m *Meter) {
2731
return float64(time.Since(initializedAt) / time.Second)
2832
},
2933
),
30-
ssh: newProvisionerInstruments("ssh"),
31-
x509: newProvisionerInstruments("x509"),
34+
ssh: newProvisionerInstruments("ssh", sshSignLabels, defaultLabels),
35+
x509: newProvisionerInstruments("x509", defaultLabels, defaultLabels),
3236
kms: &kms{
3337
signed: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "signed", "Number of KMS-backed signatures"))),
3438
errors: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "errors", "Number of KMS-related errors"))),
@@ -77,18 +81,18 @@ type Meter struct {
7781
}
7882

7983
// SSHRekeyed implements [authority.Meter] for [Meter].
80-
func (m *Meter) SSHRekeyed(p provisioner.Interface, err error) {
81-
incrProvisionerCounter(m.ssh.rekeyed, p, err)
84+
func (m *Meter) SSHRekeyed(cert *ssh.Certificate, p provisioner.Interface, err error) {
85+
incrProvisionerCounter(m.ssh.rekeyed, p, err, sshCertValues(cert)...)
8286
}
8387

8488
// SSHRenewed implements [authority.Meter] for [Meter].
85-
func (m *Meter) SSHRenewed(p provisioner.Interface, err error) {
86-
incrProvisionerCounter(m.ssh.renewed, p, err)
89+
func (m *Meter) SSHRenewed(cert *ssh.Certificate, p provisioner.Interface, err error) {
90+
incrProvisionerCounter(m.ssh.renewed, p, err, sshCertValues(cert)...)
8791
}
8892

8993
// SSHSigned implements [authority.Meter] for [Meter].
90-
func (m *Meter) SSHSigned(p provisioner.Interface, err error) {
91-
incrProvisionerCounter(m.ssh.signed, p, err)
94+
func (m *Meter) SSHSigned(cert *ssh.Certificate, p provisioner.Interface, err error) {
95+
incrProvisionerCounter(m.ssh.signed, p, err, sshCertValues(cert)...)
9296
}
9397

9498
// SSHWebhookAuthorized implements [authority.Meter] for [Meter].
@@ -102,17 +106,17 @@ func (m *Meter) SSHWebhookEnriched(p provisioner.Interface, err error) {
102106
}
103107

104108
// X509Rekeyed implements [authority.Meter] for [Meter].
105-
func (m *Meter) X509Rekeyed(p provisioner.Interface, err error) {
109+
func (m *Meter) X509Rekeyed(_ []*x509.Certificate, p provisioner.Interface, err error) {
106110
incrProvisionerCounter(m.x509.rekeyed, p, err)
107111
}
108112

109113
// X509Renewed implements [authority.Meter] for [Meter].
110-
func (m *Meter) X509Renewed(p provisioner.Interface, err error) {
114+
func (m *Meter) X509Renewed(_ []*x509.Certificate, p provisioner.Interface, err error) {
111115
incrProvisionerCounter(m.x509.renewed, p, err)
112116
}
113117

114118
// X509Signed implements [authority.Meter] for [Meter].
115-
func (m *Meter) X509Signed(p provisioner.Interface, err error) {
119+
func (m *Meter) X509Signed(_ []*x509.Certificate, p provisioner.Interface, err error) {
116120
incrProvisionerCounter(m.x509.signed, p, err)
117121
}
118122

@@ -126,13 +130,27 @@ func (m *Meter) X509WebhookEnriched(p provisioner.Interface, err error) {
126130
incrProvisionerCounter(m.x509.webhookEnriched, p, err)
127131
}
128132

129-
func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error) {
133+
func sshCertValues(cert *ssh.Certificate) []string {
134+
switch cert.CertType {
135+
case ssh.UserCert:
136+
return []string{"user"}
137+
case ssh.HostCert:
138+
return []string{"host"}
139+
default:
140+
return []string{"unknown"}
141+
}
142+
}
143+
144+
func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error, extraValues ...string) {
130145
var name string
131146
if p != nil {
132147
name = p.GetName()
133148
}
134149

135-
cv.WithLabelValues(name, strconv.FormatBool(err == nil)).Inc()
150+
values := append([]string{
151+
name, strconv.FormatBool(err == nil),
152+
}, extraValues...)
153+
cv.WithLabelValues(values...).Inc()
136154
}
137155

138156
// KMSSigned implements [authority.Meter] for [Meter].
@@ -154,28 +172,13 @@ type provisionerInstruments struct {
154172
webhookEnriched *prometheus.CounterVec
155173
}
156174

157-
func newProvisionerInstruments(subsystem string) *provisionerInstruments {
175+
func newProvisionerInstruments(subsystem string, signLabels, webhookLabels []string) *provisionerInstruments {
158176
return &provisionerInstruments{
159-
rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed",
160-
"provisioner",
161-
"success",
162-
),
163-
renewed: newCounterVec(subsystem, "renewed_total", "Number of certificates renewed",
164-
"provisioner",
165-
"success",
166-
),
167-
signed: newCounterVec(subsystem, "signed_total", "Number of certificates signed",
168-
"provisioner",
169-
"success",
170-
),
171-
webhookAuthorized: newCounterVec(subsystem, "webhook_authorized_total", "Number of authorizing webhooks called",
172-
"provisioner",
173-
"success",
174-
),
175-
webhookEnriched: newCounterVec(subsystem, "webhook_enriched_total", "Number of enriching webhooks called",
176-
"provisioner",
177-
"success",
178-
),
177+
rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed", signLabels...),
178+
renewed: newCounterVec(subsystem, "renewed_total", "Number of certificates renewed", signLabels...),
179+
signed: newCounterVec(subsystem, "signed_total", "Number of certificates signed", signLabels...),
180+
webhookAuthorized: newCounterVec(subsystem, "webhook_authorized_total", "Number of authorizing webhooks called", webhookLabels...),
181+
webhookEnriched: newCounterVec(subsystem, "webhook_enriched_total", "Number of enriching webhooks called", webhookLabels...),
179182
}
180183
}
181184

0 commit comments

Comments
 (0)