Skip to content

Commit dd1ff9c

Browse files
authored
Implementation of the Prometheus endpoint (#1669)
Implementation of the http://{metricsAddress}/metrics Prometheus endpoint.
1 parent 27ea4de commit dd1ff9c

File tree

13 files changed

+566
-142
lines changed

13 files changed

+566
-142
lines changed

authority/authority.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ type Authority struct {
104104

105105
// If true, do not output initialization logs
106106
quietInit bool
107+
108+
// Called whenever applicable, in order to instrument the authority.
109+
meter Meter
107110
}
108111

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

131135
// Apply options.
@@ -134,6 +138,9 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
134138
return nil, err
135139
}
136140
}
141+
if a.keyManager != nil {
142+
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
143+
}
137144

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

156164
// Apply options.
@@ -159,6 +167,9 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
159167
return nil, err
160168
}
161169
}
170+
if a.keyManager != nil {
171+
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
172+
}
162173

163174
// Validate required options
164175
switch {
@@ -337,6 +348,8 @@ func (a *Authority) init() error {
337348
if err != nil {
338349
return err
339350
}
351+
352+
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
340353
}
341354

342355
// Initialize linkedca client if necessary. On a linked RA, the issuer

authority/authorize.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,16 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error {
286286
// extra extension cannot be found, authorize the renewal by default.
287287
//
288288
// TODO(mariano): should we authorize by default?
289-
func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) error {
289+
func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) (provisioner.Interface, error) {
290290
serial := cert.SerialNumber.String()
291291
var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)}
292292

293293
isRevoked, err := a.IsRevoked(serial)
294294
if err != nil {
295-
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
295+
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
296296
}
297297
if isRevoked {
298-
return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
298+
return nil, errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
299299
}
300300
p, err := a.LoadProvisionerByCertificate(cert)
301301
if err != nil {
@@ -305,13 +305,13 @@ func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate)
305305
// returns the noop provisioner if this happens, and it allows
306306
// certificate renewals.
307307
if p, ok = a.provisioners.LoadByCertificate(cert); !ok {
308-
return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
308+
return nil, errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
309309
}
310310
}
311311
if err := p.AuthorizeRenew(ctx, cert); err != nil {
312-
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
312+
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
313313
}
314-
return nil
314+
return p, nil
315315
}
316316

317317
// authorizeSSHCertificate returns an error if the given certificate is revoked.

authority/authorize_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ func TestAuthority_authorizeRenew(t *testing.T) {
876876
t.Run(name, func(t *testing.T) {
877877
tc := genTestCase(t)
878878

879-
err := tc.auth.authorizeRenew(context.Background(), tc.cert)
879+
_, err := tc.auth.authorizeRenew(context.Background(), tc.cert)
880880
if err != nil {
881881
if assert.NotNil(t, tc.err) {
882882
var sc render.StatusCodedError

authority/config/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ type Config struct {
8383
Templates *templates.Templates `json:"templates,omitempty"`
8484
CommonName string `json:"commonName,omitempty"`
8585
CRL *CRLConfig `json:"crl,omitempty"`
86+
MetricsAddress string `json:"metricsAddress,omitempty"`
8687
SkipValidation bool `json:"-"`
8788

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

331+
if addr := c.MetricsAddress; addr != "" {
332+
if _, _, err := net.SplitHostPort(addr); err != nil {
333+
return errors.Errorf("invalid metrics address %q", c.Address)
334+
}
335+
}
336+
330337
if c.TLS == nil {
331338
c.TLS = &DefaultTLSOptions
332339
} else {

authority/meter.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package authority
2+
3+
import (
4+
"crypto"
5+
"io"
6+
7+
"go.step.sm/crypto/kms"
8+
kmsapi "go.step.sm/crypto/kms/apiv1"
9+
10+
"github.com/smallstep/certificates/authority/provisioner"
11+
)
12+
13+
// Meter wraps the set of defined callbacks for metrics gatherers.
14+
type Meter interface {
15+
// X509Signed is called whenever an X509 certificate is signed.
16+
X509Signed(provisioner.Interface, error)
17+
18+
// X509Renewed is called whenever an X509 certificate is renewed.
19+
X509Renewed(provisioner.Interface, error)
20+
21+
// X509Rekeyed is called whenever an X509 certificate is rekeyed.
22+
X509Rekeyed(provisioner.Interface, error)
23+
24+
// X509WebhookAuthorized is called whenever an X509 authoring webhook is called.
25+
X509WebhookAuthorized(provisioner.Interface, error)
26+
27+
// X509WebhookEnriched is called whenever an X509 enriching webhook is called.
28+
X509WebhookEnriched(provisioner.Interface, error)
29+
30+
// SSHSigned is called whenever an SSH certificate is signed.
31+
SSHSigned(provisioner.Interface, error)
32+
33+
// SSHRenewed is called whenever an SSH certificate is renewed.
34+
SSHRenewed(provisioner.Interface, error)
35+
36+
// SSHRekeyed is called whenever an SSH certificate is rekeyed.
37+
SSHRekeyed(provisioner.Interface, error)
38+
39+
// SSHWebhookAuthorized is called whenever an SSH authoring webhook is called.
40+
SSHWebhookAuthorized(provisioner.Interface, error)
41+
42+
// SSHWebhookEnriched is called whenever an SSH enriching webhook is called.
43+
SSHWebhookEnriched(provisioner.Interface, error)
44+
45+
// KMSSigned is called per KMS signer signature.
46+
KMSSigned(error)
47+
}
48+
49+
// noopMeter implements a noop [Meter].
50+
type noopMeter struct{}
51+
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) {}
63+
64+
type instrumentedKeyManager struct {
65+
kms.KeyManager
66+
meter Meter
67+
}
68+
69+
func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) {
70+
if s, err = i.KeyManager.CreateSigner(req); err == nil {
71+
s = &instrumentedKMSSigner{s, i.meter}
72+
}
73+
74+
return
75+
}
76+
77+
type instrumentedKMSSigner struct {
78+
crypto.Signer
79+
meter Meter
80+
}
81+
82+
func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
83+
signature, err = i.Signer.Sign(rand, digest, opts)
84+
i.meter.KMSSigned(err)
85+
86+
return
87+
}

authority/options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,16 @@ func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
381381
}
382382
return certs, nil
383383
}
384+
385+
// WithMeter is an option that sets the authority's [Meter] to the provided one.
386+
func WithMeter(m Meter) Option {
387+
if m == nil {
388+
m = noopMeter{}
389+
}
390+
391+
return func(a *Authority) (_ error) {
392+
a.meter = m
393+
394+
return
395+
}
396+
}

0 commit comments

Comments
 (0)