Skip to content

Commit

Permalink
Implementation of the Prometheus endpoint (#1669)
Browse files Browse the repository at this point in the history
Implementation of the http://{metricsAddress}/metrics Prometheus endpoint.
  • Loading branch information
azazeal authored Jan 26, 2024
1 parent 27ea4de commit dd1ff9c
Show file tree
Hide file tree
Showing 13 changed files with 566 additions and 142 deletions.
13 changes: 13 additions & 0 deletions authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ type Authority struct {

// If true, do not output initialization logs
quietInit bool

// Called whenever applicable, in order to instrument the authority.
meter Meter
}

// Info contains information about the authority.
Expand All @@ -126,6 +129,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
config: cfg,
certificates: new(sync.Map),
validateSCEP: true,
meter: noopMeter{},
}

// Apply options.
Expand All @@ -134,6 +138,9 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
return nil, err
}
}
if a.keyManager != nil {
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
}

if !a.skipInit {
// Initialize authority from options or configuration.
Expand All @@ -151,6 +158,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
a := &Authority{
config: &config.Config{},
certificates: new(sync.Map),
meter: noopMeter{},
}

// Apply options.
Expand All @@ -159,6 +167,9 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
return nil, err
}
}
if a.keyManager != nil {
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
}

// Validate required options
switch {
Expand Down Expand Up @@ -337,6 +348,8 @@ func (a *Authority) init() error {
if err != nil {
return err
}

a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
}

// Initialize linkedca client if necessary. On a linked RA, the issuer
Expand Down
12 changes: 6 additions & 6 deletions authority/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,16 +286,16 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error {
// extra extension cannot be found, authorize the renewal by default.
//
// TODO(mariano): should we authorize by default?
func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) error {
func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) (provisioner.Interface, error) {
serial := cert.SerialNumber.String()
var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)}

isRevoked, err := a.IsRevoked(serial)
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
}
if isRevoked {
return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
return nil, errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
}
p, err := a.LoadProvisionerByCertificate(cert)
if err != nil {
Expand All @@ -305,13 +305,13 @@ func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate)
// returns the noop provisioner if this happens, and it allows
// certificate renewals.
if p, ok = a.provisioners.LoadByCertificate(cert); !ok {
return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
return nil, errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
}
}
if err := p.AuthorizeRenew(ctx, cert); err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
}
return nil
return p, nil
}

// authorizeSSHCertificate returns an error if the given certificate is revoked.
Expand Down
2 changes: 1 addition & 1 deletion authority/authorize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ func TestAuthority_authorizeRenew(t *testing.T) {
t.Run(name, func(t *testing.T) {
tc := genTestCase(t)

err := tc.auth.authorizeRenew(context.Background(), tc.cert)
_, err := tc.auth.authorizeRenew(context.Background(), tc.cert)
if err != nil {
if assert.NotNil(t, tc.err) {
var sc render.StatusCodedError
Expand Down
7 changes: 7 additions & 0 deletions authority/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Config struct {
Templates *templates.Templates `json:"templates,omitempty"`
CommonName string `json:"commonName,omitempty"`
CRL *CRLConfig `json:"crl,omitempty"`
MetricsAddress string `json:"metricsAddress,omitempty"`
SkipValidation bool `json:"-"`

// Keeps record of the filename the Config is read from
Expand Down Expand Up @@ -327,6 +328,12 @@ func (c *Config) Validate() error {
return errors.Errorf("invalid address %s", c.Address)
}

if addr := c.MetricsAddress; addr != "" {
if _, _, err := net.SplitHostPort(addr); err != nil {
return errors.Errorf("invalid metrics address %q", c.Address)
}
}

if c.TLS == nil {
c.TLS = &DefaultTLSOptions
} else {
Expand Down
87 changes: 87 additions & 0 deletions authority/meter.go
Original file line number Diff line number Diff line change
@@ -0,0 +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.Interface, error)

// X509Renewed is called whenever an X509 certificate is renewed.
X509Renewed(provisioner.Interface, error)

// X509Rekeyed is called whenever an X509 certificate is rekeyed.
X509Rekeyed(provisioner.Interface, error)

// X509WebhookAuthorized is called whenever an X509 authoring webhook is called.
X509WebhookAuthorized(provisioner.Interface, error)

// X509WebhookEnriched is called whenever an X509 enriching webhook is called.
X509WebhookEnriched(provisioner.Interface, error)

// SSHSigned is called whenever an SSH certificate is signed.
SSHSigned(provisioner.Interface, error)

// SSHRenewed is called whenever an SSH certificate is renewed.
SSHRenewed(provisioner.Interface, error)

// SSHRekeyed is called whenever an SSH certificate is rekeyed.
SSHRekeyed(provisioner.Interface, error)

// SSHWebhookAuthorized is called whenever an SSH authoring webhook is called.
SSHWebhookAuthorized(provisioner.Interface, error)

// SSHWebhookEnriched is called whenever an SSH enriching webhook is called.
SSHWebhookEnriched(provisioner.Interface, error)

// KMSSigned is called per KMS signer signature.
KMSSigned(error)
}

// 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) {}

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
}
13 changes: 13 additions & 0 deletions authority/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,16 @@ func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
}
return certs, nil
}

// WithMeter is an option that sets the authority's [Meter] to the provided one.
func WithMeter(m Meter) Option {
if m == nil {
m = noopMeter{}
}

return func(a *Authority) (_ error) {
a.meter = m

return
}
}
Loading

0 comments on commit dd1ff9c

Please sign in to comment.