Implementation of the Prometheus endpoint (#1669)

Implementation of the http://{metricsAddress}/metrics Prometheus endpoint.
pull/1692/head v0.25.3-rc5
Panagiotis Siatras 3 months ago committed by GitHub
parent 27ea4de240
commit dd1ff9c15b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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.
@ -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.
@ -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.
@ -151,6 +158,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
a := &Authority{
config: &config.Config{},
certificates: new(sync.Map),
meter: noopMeter{},
}
// Apply options.
@ -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 {
@ -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

@ -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 {
@ -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.

@ -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

@ -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
@ -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 {

@ -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
}

@ -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
}
}

@ -146,7 +146,13 @@ 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) (*ssh.Certificate, error) {
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)
return cert, err
}
func (a *Authority) signSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) {
var (
certOptions []sshutil.Option
mods []provisioner.SSHCertModifier
@ -155,7 +161,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Validate given options.
if err := opts.Validate(); err != nil {
return nil, err
return nil, nil, err
}
// Set backdate with the configured value
@ -184,7 +190,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// validate the given SSHOptions
case provisioner.SSHCertOptionsValidator:
if err := o.Valid(opts); err != nil {
return nil, errs.BadRequestErr(err, "error validating ssh certificate options")
return nil, prov, errs.BadRequestErr(err, "error validating ssh certificate options")
}
// call webhooks
@ -192,7 +198,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
webhookCtl = o
default:
return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o)
return nil, prov, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o)
}
}
@ -205,8 +211,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
}
// Call enriching webhooks
if err := callEnrichingWebhooksSSH(webhookCtl, cr); err != nil {
return nil, errs.ApplyOptions(
if err := a.callEnrichingWebhooksSSH(prov, webhookCtl, cr); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("signOptions", signOpts),
)
@ -216,20 +222,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
certificate, err := sshutil.NewCertificate(cr, certOptions...)
if err != nil {
var te *sshutil.TemplateError
if errors.As(err, &te) {
return nil, errs.ApplyOptions(
switch {
case errors.As(err, &te):
return nil, prov, errs.ApplyOptions(
errs.BadRequestErr(err, err.Error()),
errs.WithKeyVal("signOptions", signOpts),
)
}
// explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors
if strings.HasPrefix(err.Error(), "error unmarshaling certificate") {
return nil, errs.InternalServerErr(templatingError(err),
case strings.HasPrefix(err.Error(), "error unmarshaling certificate"):
// explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors
return nil, prov, errs.InternalServerErr(templatingError(err),
errs.WithKeyVal("signOptions", signOpts),
errs.WithMessage("error applying certificate template"),
)
default:
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
}
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
}
// Get actual *ssh.Certificate and continue with provisioner modifiers.
@ -238,13 +245,13 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Use SignSSHOptions to modify the certificate validity. It will be later
// checked or set if not defined.
if err := opts.ModifyValidity(certTpl); err != nil {
return nil, errs.BadRequestErr(err, err.Error())
return nil, prov, errs.BadRequestErr(err, err.Error())
}
// Use provisioner modifiers.
for _, m := range mods {
if err := m.Modify(certTpl, opts); err != nil {
return nil, errs.ForbiddenErr(err, "error creating ssh certificate")
return nil, prov, errs.ForbiddenErr(err, "error creating ssh certificate")
}
}
@ -253,32 +260,32 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
switch certTpl.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled")
return nil, prov, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled")
return nil, prov, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
return nil, prov, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
}
// Check if authority is allowed to sign the certificate
if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil {
var ee *errs.Error
if errors.As(err, &ee) {
return nil, ee
return nil, prov, ee
}
return nil, errs.InternalServerErr(err,
return nil, prov, errs.InternalServerErr(err,
errs.WithMessage("authority.SignSSH: error creating ssh certificate"),
)
}
// Send certificate to webhooks for authorization
if err := callAuthorizingWebhooksSSH(webhookCtl, certificate, certTpl); err != nil {
return nil, errs.ApplyOptions(
if err := a.callAuthorizingWebhooksSSH(prov, webhookCtl, certificate, certTpl); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"),
)
}
@ -286,21 +293,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
}
// User provisioners validators.
for _, v := range validators {
if err := v.Valid(cert, opts); err != nil {
return nil, errs.ForbiddenErr(err, "error validating ssh certificate")
return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate")
}
}
if err = a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
if err := a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
}
return cert, nil
return cert, prov, nil
}
// isAllowedToSignSSHCertificate checks if the Authority is allowed to sign the SSH certificate.
@ -310,12 +317,18 @@ 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)
return cert, err
}
func (a *Authority) renewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, provisioner.Interface, error) {
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("cannot renew a certificate without validity period")
return nil, nil, errs.BadRequest("cannot renew a certificate without validity period")
}
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
return nil, err
return nil, nil, err
}
// Attempt to extract the provisioner from the token.
@ -348,36 +361,41 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
switch certTpl.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled")
return nil, prov, errs.NotImplemented("renewSSH: user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("renewSSH: host certificate signing is not enabled")
return nil, prov, errs.NotImplemented("renewSSH: host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType)
return nil, prov, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType)
}
// Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
}
if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
}
return cert, nil
return cert, prov, nil
}
// 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) {
var validators []provisioner.SSHCertValidator
cert, prov, err := a.rekeySSH(ctx, oldCert, pub, signOpts...)
a.meter.SSHRekeyed(prov, err)
return cert, err
}
func (a *Authority) rekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) {
var prov provisioner.Interface
var validators []provisioner.SSHCertValidator
for _, op := range signOpts {
switch o := op.(type) {
// Capture current provisioner
@ -387,16 +405,16 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
case provisioner.SSHCertValidator:
validators = append(validators, o)
default:
return nil, errs.InternalServer("rekeySSH; invalid extra option type %T", o)
return nil, prov, errs.InternalServer("rekeySSH; invalid extra option type %T", o)
}
}
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("cannot rekey a certificate without validity period")
return nil, prov, errs.BadRequest("cannot rekey a certificate without validity period")
}
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
return nil, err
return nil, prov, err
}
backdate := a.config.AuthorityConfig.Backdate.Duration
@ -423,37 +441,37 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
switch cert.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("rekeySSH; user certificate signing is not enabled")
return nil, prov, errs.NotImplemented("rekeySSH; user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("rekeySSH; host certificate signing is not enabled")
return nil, prov, errs.NotImplemented("rekeySSH; host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
return nil, errs.BadRequest("unexpected certificate type '%d'", cert.CertType)
return nil, prov, errs.BadRequest("unexpected certificate type '%d'", cert.CertType)
}
var err error
// Sign certificate.
cert, err = sshutil.CreateCertificate(cert, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
}
// Apply validators from provisioner.
for _, v := range validators {
if err := v.Valid(cert, provisioner.SignSSHOptions{Backdate: backdate}); err != nil {
return nil, errs.ForbiddenErr(err, "error validating ssh certificate")
return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate")
}
}
if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
}
return cert, nil
return cert, prov, nil
}
func (a *Authority) storeSSHCertificate(prov provisioner.Interface, cert *ssh.Certificate) error {
@ -653,28 +671,36 @@ func (a *Authority) getAddUserCommand(principal string) string {
return strings.ReplaceAll(cmd, "<principal>", principal)
}
func callEnrichingWebhooksSSH(webhookCtl webhookController, cr sshutil.CertificateRequest) error {
func (a *Authority) callEnrichingWebhooksSSH(prov provisioner.Interface, webhookCtl webhookController, cr sshutil.CertificateRequest) (err error) {
if webhookCtl == nil {
return nil
return
}
whEnrichReq, err := webhook.NewRequestBody(
var whEnrichReq *webhook.RequestBody
if whEnrichReq, err = webhook.NewRequestBody(
webhook.WithSSHCertificateRequest(cr),
)
if err != nil {
return err
); err == nil {
err = webhookCtl.Enrich(whEnrichReq)
a.meter.SSHWebhookEnriched(prov, err)
}
return webhookCtl.Enrich(whEnrichReq)
return
}
func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) error {
func (a *Authority) callAuthorizingWebhooksSSH(prov provisioner.Interface, webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) (err error) {
if webhookCtl == nil {
return nil
return
}
whAuthBody, err := webhook.NewRequestBody(
var whAuthBody *webhook.RequestBody
if whAuthBody, err = webhook.NewRequestBody(
webhook.WithSSHCertificate(cert, certTpl),
)
if err != nil {
return err
); err == nil {
err = webhookCtl.Authorize(whAuthBody)
a.meter.SSHWebhookAuthorized(prov, err)
}
return webhookCtl.Authorize(whAuthBody)
return
}

@ -93,6 +93,12 @@ 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) ([]*x509.Certificate, error) {
chain, prov, err := a.signX509(csr, signOpts, extraOpts...)
a.meter.X509Signed(prov, err)
return chain, err
}
func (a *Authority) signX509(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, provisioner.Interface, error) {
var (
certOptions []x509util.Option
certValidators []provisioner.CertificateValidator
@ -100,9 +106,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
certEnforcers []provisioner.CertificateEnforcer
)
opts := []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)}
opts := []any{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)}
if err := csr.CheckSignature(); err != nil {
return nil, errs.ApplyOptions(
return nil, nil, errs.ApplyOptions(
errs.BadRequestErr(err, "invalid certificate request"),
opts...,
)
@ -111,10 +117,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Set backdate with the configured value
signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration
var prov provisioner.Interface
var pInfo *casapi.ProvisionerInfo
var attData *provisioner.AttestationData
var webhookCtl webhookController
var (
prov provisioner.Interface
pInfo *casapi.ProvisionerInfo
attData *provisioner.AttestationData
webhookCtl webhookController
)
for _, op := range extraOpts {
switch k := op.(type) {
// Capture current provisioner
@ -132,7 +140,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Validate the given certificate request.
case provisioner.CertificateRequestValidator:
if err := k.Valid(csr); err != nil {
return nil, errs.ApplyOptions(
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error validating certificate request"),
opts...,
)
@ -159,45 +167,46 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
webhookCtl = k
default:
return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...)
return nil, prov, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]any{k}, opts...)...)
}
}
if err := callEnrichingWebhooksX509(webhookCtl, attData, csr); err != nil {
return nil, errs.ApplyOptions(
if err := a.callEnrichingWebhooksX509(prov, webhookCtl, attData, csr); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts),
)
}
cert, err := x509util.NewCertificate(csr, certOptions...)
crt, err := x509util.NewCertificate(csr, certOptions...)
if err != nil {
var te *x509util.TemplateError
if errors.As(err, &te) {
return nil, errs.ApplyOptions(
switch {
case errors.As(err, &te):
return nil, prov, errs.ApplyOptions(
errs.BadRequestErr(err, err.Error()),
errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts),
)
}
// explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors
if strings.HasPrefix(err.Error(), "error unmarshaling certificate") {
return nil, errs.InternalServerErr(templatingError(err),
case strings.HasPrefix(err.Error(), "error unmarshaling certificate"):
// explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors
return nil, prov, errs.InternalServerErr(templatingError(err),
errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts),
errs.WithMessage("error applying certificate template"),
)
default:
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...)
}
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...)
}
// Certificate modifiers before validation
leaf := cert.GetCertificate()
leaf := crt.GetCertificate()
// Set default subject
if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil {
return nil, errs.ApplyOptions(
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
)
@ -205,7 +214,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
for _, m := range certModifiers {
if err := m.Modify(leaf, signOpts); err != nil {
return nil, errs.ApplyOptions(
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
)
@ -215,7 +224,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Certificate validation.
for _, v := range certValidators {
if err := v.Valid(leaf, signOpts); err != nil {
return nil, errs.ApplyOptions(
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error validating certificate"),
opts...,
)
@ -224,8 +233,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Certificate modifiers after validation
for _, m := range certEnforcers {
if err := m.Enforce(leaf); err != nil {
return nil, errs.ApplyOptions(
if err = m.Enforce(leaf); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
)
@ -234,8 +243,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Process injected modifiers after validation
for _, m := range a.x509Enforcers {
if err := m.Enforce(leaf); err != nil {
return nil, errs.ApplyOptions(
if err = m.Enforce(leaf); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
)
@ -243,12 +252,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
}
// Check if authority is allowed to sign the certificate
if err := a.isAllowedToSignX509Certificate(leaf); err != nil {
if err = a.isAllowedToSignX509Certificate(leaf); err != nil {
var ee *errs.Error
if errors.As(err, &ee) {
return nil, errs.ApplyOptions(ee, opts...)
return nil, prov, errs.ApplyOptions(ee, opts...)
}
return nil, errs.InternalServerErr(err,
return nil, prov, errs.InternalServerErr(err,
errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts),
errs.WithMessage("error creating certificate"),
@ -256,8 +265,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
}
// Send certificate to webhooks for authorization
if err := callAuthorizingWebhooksX509(webhookCtl, cert, leaf, attData); err != nil {
return nil, errs.ApplyOptions(
if err := a.callAuthorizingWebhooksX509(prov, webhookCtl, crt, leaf, attData); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
)
@ -265,6 +274,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Sign certificate
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: leaf,
CSR: csr,
@ -273,23 +283,22 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
Provisioner: pInfo,
})
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
}
fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...)
chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...)
// Wrap provisioner with extra information.
prov = wrapProvisioner(prov, attData)
// Wrap provisioner with extra information, if not nil
if prov != nil {
prov = wrapProvisioner(prov, attData)
}
// Store certificate in the db.
if err = a.storeCertificate(prov, fullchain); err != nil {
if !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error storing certificate in db", opts...)
}
if err := a.storeCertificate(prov, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error storing certificate in db", opts...)
}
return fullchain, nil
return chain, prov, nil
}
// isAllowedToSignX509Certificate checks if the Authority is allowed
@ -337,14 +346,25 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
// of rekey), and 'NotBefore/NotAfter' (the validity duration of the new
// certificate should be equal to the old one, but starting 'now').
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)
} else {
a.meter.X509Rekeyed(prov, err)
}
return chain, err
}
func (a *Authority) renewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, provisioner.Interface, error) {
isRekey := (pk != nil)
opts := []errs.Option{
errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()),
}
// Check step provisioner extensions
if err := a.authorizeRenew(ctx, oldCert); err != nil {
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
prov, err := a.authorizeRenew(ctx, oldCert)
if err != nil {
return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
// Durations
@ -414,15 +434,17 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate,
//
// TODO(hslatman,maraino): consider adding policies too and consider if
// RenewSSH should check policies.
if err := a.constraintsEngine.ValidateCertificate(newCert); err != nil {
if err = a.constraintsEngine.ValidateCertificate(newCert); err != nil {
var ee *errs.Error
if errors.As(err, &ee) {
return nil, errs.StatusCodeError(ee.StatusCode(), err, opts...)
switch {
case errors.As(err, &ee):
return nil, prov, errs.StatusCodeError(ee.StatusCode(), err, opts...)
default:
return nil, prov, errs.InternalServerErr(err,
errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()),
errs.WithMessage("error renewing certificate"),
)
}
return nil, errs.InternalServerErr(err,
errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()),
errs.WithMessage("error renewing certificate"),
)
}
// The token can optionally be in the context. If the CA is running in RA
@ -436,17 +458,16 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate,
Token: token,
})
if err != nil {
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...)
if err = a.storeRenewedCertificate(oldCert, fullchain); err != nil {
if !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...)
if err = a.storeRenewedCertificate(oldCert, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
return fullchain, nil
return chain, prov, nil
}
// storeCertificate allows to use an extension of the db.AuthDB interface that
@ -952,42 +973,52 @@ func templatingError(err error) error {
return errors.Wrap(cause, "error applying certificate template")
}
func callEnrichingWebhooksX509(webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) error {
func (a *Authority) callEnrichingWebhooksX509(prov provisioner.Interface, webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) (err error) {
if webhookCtl == nil {
return nil
return
}
var attested *webhook.AttestationData
if attData != nil {
attested = &webhook.AttestationData{
PermanentIdentifier: attData.PermanentIdentifier,
}
}
whEnrichReq, err := webhook.NewRequestBody(
var whEnrichReq *webhook.RequestBody
if whEnrichReq, err = webhook.NewRequestBody(
webhook.WithX509CertificateRequest(csr),
webhook.WithAttestationData(attested),
)
if err != nil {
return err
); err == nil {
err = webhookCtl.Enrich(whEnrichReq)
a.meter.X509WebhookEnriched(prov, err)
}
return webhookCtl.Enrich(whEnrichReq)
return
}
func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) error {
func (a *Authority) callAuthorizingWebhooksX509(prov provisioner.Interface, webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) (err error) {
if webhookCtl == nil {
return nil
return
}
var attested *webhook.AttestationData
if attData != nil {
attested = &webhook.AttestationData{
PermanentIdentifier: attData.PermanentIdentifier,
}
}
whAuthBody, err := webhook.NewRequestBody(
var whAuthBody *webhook.RequestBody
if whAuthBody, err = webhook.NewRequestBody(
webhook.WithX509Certificate(cert, leaf),
webhook.WithAttestationData(attested),
)
if err != nil {
return err
); err == nil {
err = webhookCtl.Authorize(whAuthBody)
a.meter.X509WebhookAuthorized(prov, err)
}
return webhookCtl.Authorize(whAuthBody)
return
}

@ -27,6 +27,7 @@ import (
adminAPI "github.com/smallstep/certificates/authority/admin/api"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/internal/metrix"
"github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/monitoring"
"github.com/smallstep/certificates/scep"
@ -125,6 +126,7 @@ type CA struct {
config *config.Config
srv *server.Server
insecureSrv *server.Server
metricsSrv *server.Server
opts *options
renewer *TLSRenewer
compactStop chan struct{}
@ -163,6 +165,13 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
opts = append(opts, authority.WithQuietInit())
}
var meter *metrix.Meter
if ca.config.MetricsAddress != "" {
meter = metrix.New()
opts = append(opts, authority.WithMeter(meter))
}
webhookTransport := http.DefaultTransport.(*http.Transport).Clone()
opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport}))
@ -318,6 +327,13 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
}
}
if meter != nil {
ca.metricsSrv = server.New(ca.config.MetricsAddress, meter, nil)
ca.metricsSrv.BaseContext = func(net.Listener) context.Context {
return baseContext
}
}
return ca, nil
}
@ -404,6 +420,14 @@ func (ca *CA) Run() error {
}()
}
if ca.metricsSrv != nil {
wg.Add(1)
go func() {
defer wg.Done()
errs <- ca.metricsSrv.ListenAndServe()
}()
}
wg.Add(1)
go func() {
defer wg.Done()
@ -480,6 +504,13 @@ func (ca *CA) Reload() error {
}
}
if ca.metricsSrv != nil {
if err = ca.metricsSrv.Reload(newCA.metricsSrv); err != nil {
logContinue("Reload failed because metrics server could not be replaced.")
return errors.Wrap(err, "error reloading metrics server")
}
}
if err = ca.srv.Reload(newCA.srv); err != nil {
logContinue("Reload failed because server could not be replaced.")
return errors.Wrap(err, "error reloading server")

@ -251,7 +251,8 @@ To get a linked authority token:
ca.WithSSHUserPassword(sshUserPassword),
ca.WithIssuerPassword(issuerPassword),
ca.WithLinkedCAToken(token),
ca.WithQuiet(quiet))
ca.WithQuiet(quiet),
)
if err != nil {
fatal(err)
}

@ -21,6 +21,7 @@ require (
github.com/hashicorp/vault/api/auth/kubernetes v0.5.0
github.com/newrelic/go-agent/v3 v3.29.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.15.1
github.com/rs/xid v1.5.0
github.com/sirupsen/logrus v1.9.3
github.com/slackhq/nebula v1.6.1
@ -73,6 +74,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
@ -125,6 +127,7 @@ require (
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
@ -134,6 +137,9 @@ require (
github.com/peterbourgon/diskv/v3 v3.0.1 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/schollz/jsonstore v1.1.0 // indirect

@ -72,6 +72,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGz
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
@ -350,6 +352,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@ -383,7 +387,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@ -540,6 +552,7 @@ golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -653,8 +666,8 @@ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -0,0 +1,196 @@
// Package metrix implements stats-related functionality.
package metrix
import (
"net/http"
"strconv"
"time"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// New initializes and returns a new [Meter].
func New() (m *Meter) {
initializedAt := time.Now()
m = &Meter{
uptime: prometheus.NewGaugeFunc(
prometheus.GaugeOpts(opts(
"",
"uptime_seconds",
"Number of seconds since service start",
)),
func() float64 {
return float64(time.Since(initializedAt) / time.Second)
},
),
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"))),
},
}
reg := prometheus.NewRegistry()
reg.MustRegister(
m.uptime,
m.ssh.rekeyed,
m.ssh.renewed,
m.ssh.signed,
m.x509.rekeyed,
m.x509.renewed,
m.x509.signed,
m.kms.signed,
m.kms.errors,
)
h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{
Registry: reg,
Timeout: 5 * time.Second,
MaxRequestsInFlight: 10,
})
mux := http.NewServeMux()
mux.Handle("/metrics", h)
m.Handler = mux
return
}
// Meter wraps the functionality of a Prometheus-compatible HTTP handler.
type Meter struct {
http.Handler
uptime prometheus.GaugeFunc
ssh *provisionerInstruments
x509 *provisionerInstruments
kms *kms
}
// SSHRekeyed implements [authority.Meter] for [Meter].
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(p provisioner.Interface, err error) {
incrProvisionerCounter(m.ssh.renewed, p, err)
}
// SSHSigned implements [authority.Meter] for [Meter].
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(p provisioner.Interface, err error) {
incrProvisionerCounter(m.ssh.webhookAuthorized, p, err)
}
// SSHEnriched implements [authority.Meter] for [Meter].
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(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) {
incrProvisionerCounter(m.x509.renewed, p, err)
}
// X509Signed implements [authority.Meter] for [Meter].
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(p provisioner.Interface, err error) {
incrProvisionerCounter(m.x509.webhookAuthorized, p, err)
}
// X509Enriched implements [authority.Meter] for [Meter].
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) {
var name string
if p != nil {
name = p.GetName()
}
cv.WithLabelValues(name, strconv.FormatBool(err == nil)).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()
}
}
// provisionerInstruments wraps the counters exported by provisioners.
type provisionerInstruments struct {
rekeyed *prometheus.CounterVec
renewed *prometheus.CounterVec
signed *prometheus.CounterVec
webhookAuthorized *prometheus.CounterVec
webhookEnriched *prometheus.CounterVec
}
func newProvisionerInstruments(subsystem 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",
),
}
}
type kms struct {
signed prometheus.Counter
errors prometheus.Counter
}
func newCounterVec(subsystem, name, help string, labels ...string) *prometheus.CounterVec {
opts := opts(subsystem, name, help)
return prometheus.NewCounterVec(prometheus.CounterOpts(opts), labels)
}
func opts(subsystem, name, help string) prometheus.Opts {
return prometheus.Opts{
Namespace: "step_ca",
Subsystem: subsystem,
Name: name,
Help: help,
}
}
Loading…
Cancel
Save