diff --git a/authority/authority.go b/authority/authority.go index a95d66082..c112bc257 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -8,7 +8,6 @@ import ( "crypto/sha256" "crypto/x509" "encoding/hex" - "io" "log" "net/http" "strings" @@ -972,50 +971,3 @@ func (a *Authority) startCRLGenerator() error { return nil } - -//nolint:gocritic // used in defered statements -func (a *Authority) incrProvisionerCounter(prov *provisioner.Interface, err *error, count func(Meter, string, bool)) { - var name string - if p := *prov; p != nil { - name = p.GetName() - } - - count(a.meter, name, *err == nil) -} - -func (a *Authority) incrWebhookCounter(prov provisioner.Interface, err error, count func(Meter, string, bool)) { - var name string - if prov != nil { - name = prov.GetName() - } - - count(a.meter, name, err == nil) -} - -type instrumentedKeyManager struct { - kms.KeyManager - meter Meter -} - -func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) { - if s, err = i.KeyManager.CreateSigner(req); err == nil { - s = &instrumentedKMSSigner{s, i.meter} - } - - return -} - -type instrumentedKMSSigner struct { - crypto.Signer - meter Meter -} - -func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { - if signature, err = i.Signer.Sign(rand, digest, opts); err != nil { - i.meter.KMSError() - } else { - i.meter.KMSSigned() - } - - return -} diff --git a/authority/meter.go b/authority/meter.go index 5cbd991d8..cccda22ab 100644 --- a/authority/meter.go +++ b/authority/meter.go @@ -1,56 +1,87 @@ package authority +import ( + "crypto" + "io" + + "go.step.sm/crypto/kms" + kmsapi "go.step.sm/crypto/kms/apiv1" + + "github.com/smallstep/certificates/authority/provisioner" +) + // Meter wraps the set of defined callbacks for metrics gatherers. type Meter interface { // X509Signed is called whenever an X509 certificate is signed. - X509Signed(provisioner string, success bool) + X509Signed(provisioner.Interface, error) // X509Renewed is called whenever an X509 certificate is renewed. - X509Renewed(provisioner string, success bool) + X509Renewed(provisioner.Interface, error) // X509Rekeyed is called whenever an X509 certificate is rekeyed. - X509Rekeyed(provisioner string, success bool) + X509Rekeyed(provisioner.Interface, error) // X509WebhookAuthorized is called whenever an X509 authoring webhook is called. - X509WebhookAuthorized(provisioner string, success bool) + X509WebhookAuthorized(provisioner.Interface, error) // X509WebhookEnriched is called whenever an X509 enriching webhook is called. - X509WebhookEnriched(provisioner string, success bool) + X509WebhookEnriched(provisioner.Interface, error) // SSHSigned is called whenever an SSH certificate is signed. - SSHSigned(provisioner string, success bool) + SSHSigned(provisioner.Interface, error) // SSHRenewed is called whenever an SSH certificate is renewed. - SSHRenewed(provisioner string, success bool) + SSHRenewed(provisioner.Interface, error) // SSHRekeyed is called whenever an SSH certificate is rekeyed. - SSHRekeyed(provisioner string, success bool) + SSHRekeyed(provisioner.Interface, error) // SSHWebhookAuthorized is called whenever an SSH authoring webhook is called. - SSHWebhookAuthorized(provisioner string, success bool) + SSHWebhookAuthorized(provisioner.Interface, error) // SSHWebhookEnriched is called whenever an SSH enriching webhook is called. - SSHWebhookEnriched(provisioner string, success bool) + SSHWebhookEnriched(provisioner.Interface, error) // KMSSigned is called per KMS signer signature. - KMSSigned() - - // KMSSigned is called per KMS signer signature error. - KMSError() + KMSSigned(error) } // noopMeter implements a noop [Meter]. type noopMeter struct{} -func (noopMeter) SSHRekeyed(string, bool) {} -func (noopMeter) SSHRenewed(string, bool) {} -func (noopMeter) SSHSigned(string, bool) {} -func (noopMeter) SSHWebhookAuthorized(string, bool) {} -func (noopMeter) SSHWebhookEnriched(string, bool) {} -func (noopMeter) X509Rekeyed(string, bool) {} -func (noopMeter) X509Renewed(string, bool) {} -func (noopMeter) X509Signed(string, bool) {} -func (noopMeter) X509WebhookAuthorized(string, bool) {} -func (noopMeter) X509WebhookEnriched(string, bool) {} -func (noopMeter) KMSSigned() {} -func (noopMeter) KMSError() {} +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) {} + +type instrumentedKeyManager struct { + kms.KeyManager + meter Meter +} + +func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) { + if s, err = i.KeyManager.CreateSigner(req); err == nil { + s = &instrumentedKMSSigner{s, i.meter} + } + + return +} + +type instrumentedKMSSigner struct { + crypto.Signer + meter Meter +} + +func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + signature, err = i.Signer.Sign(rand, digest, opts) + i.meter.KMSSigned(err) + + return +} diff --git a/authority/ssh.go b/authority/ssh.go index 2aee85c0f..fe8de6fbf 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -148,7 +148,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(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (cert *ssh.Certificate, err error) { var prov provisioner.Interface - defer a.incrProvisionerCounter(&prov, &err, Meter.SSHSigned) + defer func() { a.meter.SSHSigned(prov, err) }() var ( certOptions []sshutil.Option @@ -345,7 +345,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) (cert *ssh.Certificate, err error) { var prov provisioner.Interface - defer a.incrProvisionerCounter(&prov, &err, Meter.SSHRenewed) + defer func() { a.meter.SSHRenewed(prov, err) }() if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { err = errs.BadRequest("cannot renew a certificate without validity period") @@ -426,7 +426,7 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (cer // 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) (cert *ssh.Certificate, err error) { var prov provisioner.Interface - defer a.incrProvisionerCounter(&prov, &err, Meter.SSHRekeyed) + defer func() { a.meter.SSHRekeyed(prov, err) }() var validators []provisioner.SSHCertValidator @@ -733,7 +733,7 @@ func (a *Authority) callEnrichingWebhooksSSH(prov provisioner.Interface, webhook ); err == nil { err = webhookCtl.Enrich(whEnrichReq) - a.incrWebhookCounter(prov, err, Meter.SSHWebhookEnriched) + a.meter.SSHWebhookEnriched(prov, err) } return @@ -750,7 +750,7 @@ func (a *Authority) callAuthorizingWebhooksSSH(prov provisioner.Interface, webho ); err == nil { err = webhookCtl.Authorize(whAuthBody) - a.incrWebhookCounter(prov, err, Meter.SSHWebhookAuthorized) + a.meter.SSHWebhookAuthorized(prov, err) } return diff --git a/authority/tls.go b/authority/tls.go index 44de8138d..b569808bf 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -94,7 +94,7 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { // Sign creates a signed certificate from a certificate signing request. func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) (cert []*x509.Certificate, err error) { var prov provisioner.Interface - defer a.incrProvisionerCounter(&prov, &err, Meter.X509Signed) + defer func() { a.meter.X509Signed(prov, err) }() var ( certOptions []x509util.Option @@ -374,9 +374,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) (cert []*x509.Certificate, err error) { var prov provisioner.Interface if pk == nil { - defer a.incrProvisionerCounter(&prov, &err, Meter.X509Renewed) + defer func() { a.meter.X509Renewed(prov, err) }() } else { - defer a.incrProvisionerCounter(&prov, &err, Meter.X509Rekeyed) + defer func() { a.meter.X509Rekeyed(prov, err) }() } isRekey := (pk != nil) @@ -1024,7 +1024,7 @@ func (a *Authority) callEnrichingWebhooksX509(prov provisioner.Interface, webhoo ); err == nil { err = webhookCtl.Enrich(whEnrichReq) - a.incrWebhookCounter(prov, err, Meter.X509WebhookEnriched) + a.meter.X509WebhookEnriched(prov, err) } return @@ -1049,7 +1049,7 @@ func (a *Authority) callAuthorizingWebhooksX509(prov provisioner.Interface, webh ); err == nil { err = webhookCtl.Authorize(whAuthBody) - a.incrWebhookCounter(prov, err, Meter.X509WebhookAuthorized) + a.meter.X509WebhookAuthorized(prov, err) } return diff --git a/internal/metrix/meter.go b/internal/metrix/meter.go index a744ed342..a867b197b 100644 --- a/internal/metrix/meter.go +++ b/internal/metrix/meter.go @@ -6,6 +6,8 @@ import ( "strconv" "time" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -25,8 +27,8 @@ func New() (m *Meter) { return float64(time.Since(initializedAt) / time.Second) }, ), - ssh: newProvisioner("ssh"), - x509: newProvisioner("x509"), + ssh: newProvisionerInstruments("ssh"), + x509: newProvisionerInstruments("x509"), 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"))), @@ -65,77 +67,81 @@ type Meter struct { http.Handler uptime prometheus.GaugeFunc - ssh *provisioner - x509 *provisioner + ssh *provisionerInstruments + x509 *provisionerInstruments kms *kms } // SSHRekeyed implements [authority.Meter] for [Meter]. -func (m *Meter) SSHRekeyed(p string, success bool) { - count(m.ssh.rekeyed, p, success) +func (m *Meter) SSHRekeyed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.rekeyed, p, err) } // SSHRenewed implements [authority.Meter] for [Meter]. -func (m *Meter) SSHRenewed(provisioner string, success bool) { - count(m.ssh.renewed, provisioner, success) +func (m *Meter) SSHRenewed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.renewed, p, err) } // SSHSigned implements [authority.Meter] for [Meter]. -func (m *Meter) SSHSigned(provisioner string, success bool) { - count(m.ssh.signed, provisioner, success) +func (m *Meter) SSHSigned(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.signed, p, err) } // SSHAuthorized implements [authority.Meter] for [Meter]. -func (m *Meter) SSHWebhookAuthorized(provisioner string, success bool) { - count(m.ssh.webhookAuthorized, provisioner, success) +func (m *Meter) SSHWebhookAuthorized(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.webhookAuthorized, p, err) } // SSHEnriched implements [authority.Meter] for [Meter]. -func (m *Meter) SSHWebhookEnriched(provisioner string, success bool) { - count(m.ssh.webhookEnriched, provisioner, success) +func (m *Meter) SSHWebhookEnriched(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.webhookEnriched, p, err) } // X509Rekeyed implements [authority.Meter] for [Meter]. -func (m *Meter) X509Rekeyed(provisioner string, success bool) { - count(m.x509.rekeyed, provisioner, success) +func (m *Meter) X509Rekeyed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.rekeyed, p, err) } // X509Renewed implements [authority.Meter] for [Meter]. -func (m *Meter) X509Renewed(provisioner string, success bool) { - count(m.x509.renewed, provisioner, success) +func (m *Meter) X509Renewed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.renewed, p, err) } // X509Signed implements [authority.Meter] for [Meter]. -func (m *Meter) X509Signed(provisioner string, success bool) { - count(m.x509.signed, provisioner, success) +func (m *Meter) X509Signed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.signed, p, err) } // X509Authorized implements [authority.Meter] for [Meter]. -func (m *Meter) X509WebhookAuthorized(provisioner string, success bool) { - count(m.x509.webhookAuthorized, provisioner, success) +func (m *Meter) X509WebhookAuthorized(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.webhookAuthorized, p, err) } // X509Enriched implements [authority.Meter] for [Meter]. -func (m *Meter) X509WebhookEnriched(provisioner string, success bool) { - count(m.x509.webhookEnriched, provisioner, success) +func (m *Meter) X509WebhookEnriched(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.webhookEnriched, p, err) } -func count(cv *prometheus.CounterVec, provisioner string, success bool) { - cv.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc() -} +func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error) { + var name string + if p != nil { + name = p.GetName() + } -// KMSSigned implements [authority.Meter] for [Meter]. -func (m *Meter) KMSSigned() { - m.kms.signed.Inc() + cv.WithLabelValues(name, strconv.FormatBool(err == nil)).Inc() } -// KMSErrors implements [authority.Meter] for [Meter]. -func (m *Meter) KMSError() { - m.kms.errors.Inc() +// KMSSigned implements [authority.Meter] for [Meter]. +func (m *Meter) KMSSigned(err error) { + if err == nil { + m.kms.signed.Inc() + } else { + m.kms.errors.Inc() + } } -// provisioner wraps the counters exported by provisioners. -type provisioner struct { +// provisionerInstruments wraps the counters exported by provisioners. +type provisionerInstruments struct { rekeyed *prometheus.CounterVec renewed *prometheus.CounterVec signed *prometheus.CounterVec @@ -144,8 +150,8 @@ type provisioner struct { webhookEnriched *prometheus.CounterVec } -func newProvisioner(subsystem string) *provisioner { - return &provisioner{ +func newProvisionerInstruments(subsystem string) *provisionerInstruments { + return &provisionerInstruments{ rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed", "provisioner", "success",