diff --git a/authority/meter.go b/authority/meter.go index c99069a4..700749f5 100644 --- a/authority/meter.go +++ b/authority/meter.go @@ -2,10 +2,12 @@ package authority import ( "crypto" + "crypto/x509" "io" "go.step.sm/crypto/kms" kmsapi "go.step.sm/crypto/kms/apiv1" + "golang.org/x/crypto/ssh" "github.com/smallstep/certificates/authority/provisioner" ) @@ -13,13 +15,13 @@ import ( // Meter wraps the set of defined callbacks for metrics gatherers. type Meter interface { // X509Signed is called whenever an X509 certificate is signed. - X509Signed(provisioner.Interface, error) + X509Signed([]*x509.Certificate, provisioner.Interface, error) // X509Renewed is called whenever an X509 certificate is renewed. - X509Renewed(provisioner.Interface, error) + X509Renewed([]*x509.Certificate, provisioner.Interface, error) // X509Rekeyed is called whenever an X509 certificate is rekeyed. - X509Rekeyed(provisioner.Interface, error) + X509Rekeyed([]*x509.Certificate, provisioner.Interface, error) // X509WebhookAuthorized is called whenever an X509 authoring webhook is called. X509WebhookAuthorized(provisioner.Interface, error) @@ -28,13 +30,13 @@ type Meter interface { X509WebhookEnriched(provisioner.Interface, error) // SSHSigned is called whenever an SSH certificate is signed. - SSHSigned(provisioner.Interface, error) + SSHSigned(*ssh.Certificate, provisioner.Interface, error) // SSHRenewed is called whenever an SSH certificate is renewed. - SSHRenewed(provisioner.Interface, error) + SSHRenewed(*ssh.Certificate, provisioner.Interface, error) // SSHRekeyed is called whenever an SSH certificate is rekeyed. - SSHRekeyed(provisioner.Interface, error) + SSHRekeyed(*ssh.Certificate, provisioner.Interface, error) // SSHWebhookAuthorized is called whenever an SSH authoring webhook is called. SSHWebhookAuthorized(provisioner.Interface, error) @@ -49,17 +51,17 @@ type Meter interface { // noopMeter implements a noop [Meter]. type noopMeter struct{} -func (noopMeter) SSHRekeyed(provisioner.Interface, error) {} -func (noopMeter) SSHRenewed(provisioner.Interface, error) {} -func (noopMeter) SSHSigned(provisioner.Interface, error) {} -func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {} -func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {} -func (noopMeter) X509Rekeyed(provisioner.Interface, error) {} -func (noopMeter) X509Renewed(provisioner.Interface, error) {} -func (noopMeter) X509Signed(provisioner.Interface, error) {} -func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {} -func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {} -func (noopMeter) KMSSigned(error) {} +func (noopMeter) SSHRekeyed(*ssh.Certificate, provisioner.Interface, error) {} +func (noopMeter) SSHRenewed(*ssh.Certificate, provisioner.Interface, error) {} +func (noopMeter) SSHSigned(*ssh.Certificate, provisioner.Interface, error) {} +func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {} +func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {} +func (noopMeter) X509Rekeyed([]*x509.Certificate, provisioner.Interface, error) {} +func (noopMeter) X509Renewed([]*x509.Certificate, provisioner.Interface, error) {} +func (noopMeter) X509Signed([]*x509.Certificate, provisioner.Interface, error) {} +func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {} +func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {} +func (noopMeter) KMSSigned(error) {} type instrumentedKeyManager struct { kms.KeyManager diff --git a/authority/ssh.go b/authority/ssh.go index 2608b9d4..8268e083 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -149,7 +149,7 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (* // SignSSH creates a signed SSH certificate with the given public key and options. func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { cert, prov, err := a.signSSH(ctx, key, opts, signOpts...) - a.meter.SSHSigned(prov, err) + a.meter.SSHSigned(cert, prov, err) return cert, err } @@ -337,7 +337,7 @@ func (a *Authority) isAllowedToSignSSHCertificate(cert *ssh.Certificate) error { // RenewSSH creates a signed SSH certificate using the old SSH certificate as a template. func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) { cert, prov, err := a.renewSSH(ctx, oldCert) - a.meter.SSHRenewed(prov, err) + a.meter.SSHRenewed(cert, prov, err) return cert, err } @@ -408,7 +408,7 @@ func (a *Authority) renewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss // RekeySSH creates a signed SSH certificate using the old SSH certificate as a template. func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { cert, prov, err := a.rekeySSH(ctx, oldCert, pub, signOpts...) - a.meter.SSHRekeyed(prov, err) + a.meter.SSHRekeyed(cert, prov, err) return cert, err } diff --git a/authority/tls.go b/authority/tls.go index f9ae1d32..5a72e377 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -118,7 +118,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // request, taking the provided context.Context. func (a *Authority) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { chain, prov, err := a.signX509(ctx, csr, signOpts, extraOpts...) - a.meter.X509Signed(prov, err) + a.meter.X509Signed(chain, prov, err) return chain, err } @@ -372,9 +372,9 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { chain, prov, err := a.renewContext(ctx, oldCert, pk) if pk == nil { - a.meter.X509Renewed(prov, err) + a.meter.X509Renewed(chain, prov, err) } else { - a.meter.X509Rekeyed(prov, err) + a.meter.X509Rekeyed(chain, prov, err) } return chain, err } diff --git a/internal/metrix/meter.go b/internal/metrix/meter.go index ec858308..04316b5a 100644 --- a/internal/metrix/meter.go +++ b/internal/metrix/meter.go @@ -2,11 +2,13 @@ package metrix import ( + "crypto/x509" "net/http" "strconv" "time" "github.com/smallstep/certificates/authority/provisioner" + "golang.org/x/crypto/ssh" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -15,6 +17,8 @@ import ( // New initializes and returns a new [Meter]. func New() (m *Meter) { initializedAt := time.Now() + defaultLabels := []string{"provisioner", "success"} + sshSignLabels := []string{"provisioner", "success", "type"} m = &Meter{ uptime: prometheus.NewGaugeFunc( @@ -27,8 +31,8 @@ func New() (m *Meter) { return float64(time.Since(initializedAt) / time.Second) }, ), - ssh: newProvisionerInstruments("ssh"), - x509: newProvisionerInstruments("x509"), + ssh: newProvisionerInstruments("ssh", sshSignLabels, defaultLabels), + x509: newProvisionerInstruments("x509", defaultLabels, defaultLabels), kms: &kms{ signed: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "signed", "Number of KMS-backed signatures"))), errors: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "errors", "Number of KMS-related errors"))), @@ -77,18 +81,18 @@ type Meter struct { } // SSHRekeyed implements [authority.Meter] for [Meter]. -func (m *Meter) SSHRekeyed(p provisioner.Interface, err error) { - incrProvisionerCounter(m.ssh.rekeyed, p, err) +func (m *Meter) SSHRekeyed(cert *ssh.Certificate, p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.rekeyed, p, err, sshCertValues(cert)...) } // SSHRenewed implements [authority.Meter] for [Meter]. -func (m *Meter) SSHRenewed(p provisioner.Interface, err error) { - incrProvisionerCounter(m.ssh.renewed, p, err) +func (m *Meter) SSHRenewed(cert *ssh.Certificate, p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.renewed, p, err, sshCertValues(cert)...) } // SSHSigned implements [authority.Meter] for [Meter]. -func (m *Meter) SSHSigned(p provisioner.Interface, err error) { - incrProvisionerCounter(m.ssh.signed, p, err) +func (m *Meter) SSHSigned(cert *ssh.Certificate, p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.signed, p, err, sshCertValues(cert)...) } // SSHWebhookAuthorized implements [authority.Meter] for [Meter]. @@ -102,17 +106,17 @@ func (m *Meter) SSHWebhookEnriched(p provisioner.Interface, err error) { } // X509Rekeyed implements [authority.Meter] for [Meter]. -func (m *Meter) X509Rekeyed(p provisioner.Interface, err error) { +func (m *Meter) X509Rekeyed(_ []*x509.Certificate, p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.rekeyed, p, err) } // X509Renewed implements [authority.Meter] for [Meter]. -func (m *Meter) X509Renewed(p provisioner.Interface, err error) { +func (m *Meter) X509Renewed(_ []*x509.Certificate, p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.renewed, p, err) } // X509Signed implements [authority.Meter] for [Meter]. -func (m *Meter) X509Signed(p provisioner.Interface, err error) { +func (m *Meter) X509Signed(_ []*x509.Certificate, p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.signed, p, err) } @@ -126,13 +130,27 @@ func (m *Meter) X509WebhookEnriched(p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.webhookEnriched, p, err) } -func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error) { +func sshCertValues(cert *ssh.Certificate) []string { + switch cert.CertType { + case ssh.UserCert: + return []string{"user"} + case ssh.HostCert: + return []string{"host"} + default: + return []string{"unknown"} + } +} + +func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error, extraValues ...string) { var name string if p != nil { name = p.GetName() } - cv.WithLabelValues(name, strconv.FormatBool(err == nil)).Inc() + values := append([]string{ + name, strconv.FormatBool(err == nil), + }, extraValues...) + cv.WithLabelValues(values...).Inc() } // KMSSigned implements [authority.Meter] for [Meter]. @@ -154,28 +172,13 @@ type provisionerInstruments struct { webhookEnriched *prometheus.CounterVec } -func newProvisionerInstruments(subsystem string) *provisionerInstruments { +func newProvisionerInstruments(subsystem string, signLabels, webhookLabels []string) *provisionerInstruments { return &provisionerInstruments{ - rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed", - "provisioner", - "success", - ), - renewed: newCounterVec(subsystem, "renewed_total", "Number of certificates renewed", - "provisioner", - "success", - ), - signed: newCounterVec(subsystem, "signed_total", "Number of certificates signed", - "provisioner", - "success", - ), - webhookAuthorized: newCounterVec(subsystem, "webhook_authorized_total", "Number of authorizing webhooks called", - "provisioner", - "success", - ), - webhookEnriched: newCounterVec(subsystem, "webhook_enriched_total", "Number of enriching webhooks called", - "provisioner", - "success", - ), + rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed", signLabels...), + renewed: newCounterVec(subsystem, "renewed_total", "Number of certificates renewed", signLabels...), + signed: newCounterVec(subsystem, "signed_total", "Number of certificates signed", signLabels...), + webhookAuthorized: newCounterVec(subsystem, "webhook_authorized_total", "Number of authorizing webhooks called", webhookLabels...), + webhookEnriched: newCounterVec(subsystem, "webhook_enriched_total", "Number of enriching webhooks called", webhookLabels...), } }