added stats for rekey

pull/1690/head
Panagiotis Siatras 5 months ago
parent 65544b0d1a
commit bae0577bf7
No known key found for this signature in database

@ -129,6 +129,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
config: cfg,
certificates: new(sync.Map),
validateSCEP: true,
meter: noopMeter{},
}
// Apply options.
@ -145,10 +146,6 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
}
}
if a.meter == nil {
a.meter = noopMeter{}
}
return a, nil
}
@ -158,6 +155,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
a := &Authority{
config: &config.Config{},
certificates: new(sync.Map),
meter: noopMeter{},
}
// Apply options.

@ -3,25 +3,33 @@ package authority
// Meter wraps the set of defined callbacks for metrics gatherers.
type Meter interface {
// X509Signed is called whenever a X509 CSR is signed.
X509Signed(provisioner string)
X509Signed(provisioner string, success bool)
// X509Renewed is called whenever a X509 certificate is renewed.
X509Renewed(provisioner string)
X509Renewed(provisioner string, success bool)
// X509Rekeyed is called whenever a X509 certificate is rekeyed.
X509Rekeyed(provisioner string, success bool)
// SSHSigned is called whenever a SSH CSR is signed.
SSHSigned(provisioner string)
SSHSigned(provisioner string, success bool)
// SSHRenewed is called whenever a SSH certificate is renewed.
SSHRenewed(provisioner string, success bool)
// SSHRenewedf is called whenever a SSH certificate is renewed.
SSHRenewed(provisioner string)
// SSHRekeyed is called whenever a SSH certificate is rekeyed.
SSHRekeyed(provisioner string, success bool)
}
// noopMeter implements a noop [Meter].
type noopMeter struct{}
func (noopMeter) X509Signed(string) {}
func (noopMeter) X509Signed(string, bool) {}
func (noopMeter) X509Renewed(string, bool) {}
func (noopMeter) X509Renewed(string) {}
func (noopMeter) SSHSigned(string, bool) {}
func (noopMeter) SSHSigned(string) {}
func (noopMeter) SSHRenewed(string, bool) {}
func (noopMeter) SSHRenewed(string) {}
func (noopMeter) SSHRekeyed(string, bool) {}

@ -384,6 +384,10 @@ func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
// 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 nil

@ -162,6 +162,13 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
var prov provisioner.Interface
measure := func(ok bool) {
// we don't know whether the provisioner is set in the following for/switch
if prov != nil {
a.meter.SSHSigned(prov.GetName(), ok)
}
}
var webhookCtl webhookController
for _, op := range signOpts {
switch o := op.(type) {
@ -192,6 +199,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
webhookCtl = o
default:
measure(false)
return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o)
}
}
@ -206,6 +215,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Call enriching webhooks
if err := callEnrichingWebhooksSSH(webhookCtl, cr); err != nil {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("signOptions", signOpts),
@ -215,6 +226,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Create certificate from template.
certificate, err := sshutil.NewCertificate(cr, certOptions...)
if err != nil {
measure(false)
var te *sshutil.TemplateError
if errors.As(err, &te) {
return nil, errs.ApplyOptions(
@ -238,12 +251,16 @@ 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 {
measure(false)
return nil, errs.BadRequestErr(err, err.Error())
}
// Use provisioner modifiers.
for _, m := range mods {
if err := m.Modify(certTpl, opts); err != nil {
measure(false)
return nil, errs.ForbiddenErr(err, "error creating ssh certificate")
}
}
@ -253,20 +270,28 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
switch certTpl.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
measure(false)
return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
measure(false)
return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
measure(false)
return nil, 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 {
measure(false)
var ee *errs.Error
if errors.As(err, &ee) {
return nil, ee
@ -278,6 +303,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Send certificate to webhooks for authorization
if err := callAuthorizingWebhooksSSH(webhookCtl, certificate, certTpl); err != nil {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"),
)
@ -286,21 +313,27 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil {
measure(false)
return nil, 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 {
measure(false)
return nil, errs.ForbiddenErr(err, "error validating ssh certificate")
}
}
if err = a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
measure(false)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
}
a.meter.SSHSigned(prov.GetName())
measure(true)
return cert, nil
}
@ -322,6 +355,12 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
// Attempt to extract the provisioner from the token.
var prov provisioner.Interface
measure := func(ok bool) {
if prov != nil {
a.meter.SSHRenewed(prov.GetName(), ok)
}
}
if token, ok := provisioner.TokenFromContext(ctx); ok {
prov, _, _ = a.getProvisionerFromToken(token)
}
@ -350,29 +389,39 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
switch certTpl.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
measure(false)
return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
measure(false)
return nil, errs.NotImplemented("renewSSH: host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
measure(false)
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType)
}
// Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil {
measure(false)
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
}
if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
measure(false)
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
}
// TODO(@azazeal): SSH renew trigger
measure(true)
return cert, nil
}
@ -382,6 +431,13 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
var validators []provisioner.SSHCertValidator
var prov provisioner.Interface
measure := func(ok bool) {
// we don't know whether the provisioner is set in the following for/switch
if prov != nil {
a.meter.SSHRekeyed(prov.GetName(), ok)
}
}
for _, op := range signOpts {
switch o := op.(type) {
// Capture current provisioner
@ -391,15 +447,21 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
case provisioner.SSHCertValidator:
validators = append(validators, o)
default:
measure(false)
return nil, errs.InternalServer("rekeySSH; invalid extra option type %T", o)
}
}
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
measure(false)
return nil, errs.BadRequest("cannot rekey a certificate without validity period")
}
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
measure(false)
return nil, err
}
@ -427,15 +489,21 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
switch cert.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
measure(false)
return nil, errs.NotImplemented("rekeySSH; user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
measure(false)
return nil, errs.NotImplemented("rekeySSH; host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
measure(false)
return nil, errs.BadRequest("unexpected certificate type '%d'", cert.CertType)
}
@ -443,20 +511,28 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
// Sign certificate.
cert, err = sshutil.CreateCertificate(cert, signer)
if err != nil {
measure(false)
return nil, 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 {
measure(false)
return nil, errs.ForbiddenErr(err, "error validating ssh certificate")
}
}
if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
measure(false)
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
}
measure(true)
return cert, nil
}

@ -112,6 +112,13 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration
var prov provisioner.Interface
measure := func(ok bool) {
// we don't know whether the provisioner is set in the following for/switch
if prov != nil {
a.meter.X509Signed(prov.GetName(), ok)
}
}
var pInfo *casapi.ProvisionerInfo
var attData *provisioner.AttestationData
var webhookCtl webhookController
@ -159,11 +166,15 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
webhookCtl = k
default:
measure(false)
return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...)
}
}
if err := callEnrichingWebhooksX509(webhookCtl, attData, csr); err != nil {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("csr", csr),
@ -173,6 +184,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
cert, err := x509util.NewCertificate(csr, certOptions...)
if err != nil {
measure(false)
var te *x509util.TemplateError
if errors.As(err, &te) {
return nil, errs.ApplyOptions(
@ -197,6 +210,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Set default subject
if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
@ -205,6 +220,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
for _, m := range certModifiers {
if err := m.Modify(leaf, signOpts); err != nil {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
@ -215,6 +232,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Certificate validation.
for _, v := range certValidators {
if err := v.Valid(leaf, signOpts); err != nil {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "error validating certificate"),
opts...,
@ -225,6 +244,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 {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
@ -235,6 +256,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 {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
@ -244,6 +267,8 @@ 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 {
measure(false)
var ee *errs.Error
if errors.As(err, &ee) {
return nil, errs.ApplyOptions(ee, opts...)
@ -257,6 +282,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 {
measure(false)
return nil, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
@ -273,6 +300,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
Provisioner: pInfo,
})
if err != nil {
measure(false)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
}
@ -284,12 +313,14 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Store certificate in the db.
if err = a.storeCertificate(prov, fullchain); err != nil {
if !errors.Is(err, db.ErrNotImplemented) {
measure(false)
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error storing certificate in db", opts...)
}
}
a.meter.X509Signed(prov.GetName())
measure(true)
return fullchain, nil
}
@ -349,6 +380,13 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate,
if err != nil {
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
measure := func(ok bool) {
if pk == nil {
a.meter.X509Renewed(prov.GetName(), ok)
} else {
a.meter.X509Rekeyed(prov.GetName(), ok)
}
}
// Durations
backdate := a.config.AuthorityConfig.Backdate.Duration
@ -418,6 +456,8 @@ 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 {
measure(false)
var ee *errs.Error
if errors.As(err, &ee) {
return nil, errs.StatusCodeError(ee.StatusCode(), err, opts...)
@ -439,17 +479,21 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate,
Token: token,
})
if err != nil {
measure(false)
return nil, 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) {
measure(false)
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
}
a.meter.X509Renewed(prov.GetName())
measure(true)
return fullchain, nil
}

@ -3,6 +3,7 @@ package metrix
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
@ -12,20 +13,32 @@ import (
// New initializes and returns a new [Meter].
func New() (m *Meter) {
m = &Meter{
x509: signatures{
signed: newCounterVec("x509", "signed_total", "Number of X509 CSRs signed",
ssh: signatures{
rekeyed: newCounterVec("ssh", "rekeyed_total", "Number of SSH certificates rekeyed",
"provisioner",
"success",
),
renewed: newCounterVec("x509", "renewed_total", "Number of X509 certificates renewed",
renewed: newCounterVec("ssh", "renewed_total", "Number of SSH certificates renewed",
"provisioner",
"success",
),
},
ssh: signatures{
signed: newCounterVec("ssh", "signed_total", "Number of SSH CSRs signed",
"provisioner",
"success",
),
renewed: newCounterVec("ssh", "renewed_total", "Number of SSH certificates renewed",
},
x509: signatures{
rekeyed: newCounterVec("x509", "rekeyed_total", "Number of X509 certificates rekeyed",
"provisioner",
"success",
),
renewed: newCounterVec("x509", "renewed_total", "Number of X509 certificates renewed",
"provisioner",
"success",
),
signed: newCounterVec("x509", "signed_total", "Number of X509 CSRs signed",
"provisioner",
"success",
),
},
}
@ -33,10 +46,12 @@ func New() (m *Meter) {
reg := prometheus.NewRegistry()
reg.MustRegister(
m.ssh.rekeyed,
m.ssh.renewed,
m.ssh.signed,
m.x509.rekeyed,
m.x509.renewed,
m.x509.signed,
m.ssh.signed,
m.ssh.renewed,
)
h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{
@ -56,33 +71,44 @@ func New() (m *Meter) {
type Meter struct {
http.Handler
x509 signatures
ssh signatures
x509 signatures
}
type signatures struct {
signed *prometheus.CounterVec
rekeyed *prometheus.CounterVec
renewed *prometheus.CounterVec
signed *prometheus.CounterVec
}
// X509Signed implements [authority.Meter] for [Meter].
func (m *Meter) X509Signed(provisioner string) {
m.x509.signed.WithLabelValues(provisioner).Inc()
// SSHRekeyed implements [authority.Meter] for [Meter].
func (m *Meter) SSHRekeyed(provisioner string, success bool) {
m.ssh.rekeyed.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc()
}
// X509Renewed implements [authority.Meter] for [Meter].
func (m *Meter) X509Renewed(provisioner string) {
m.x509.renewed.WithLabelValues(provisioner).Inc()
// SSHRenewed implements [authority.Meter] for [Meter].
func (m *Meter) SSHRenewed(provisioner string, success bool) {
m.ssh.renewed.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc()
}
// SSHSigned implements [authority.Meter] for [Meter].
func (m *Meter) SSHSigned(provisioner string) {
m.ssh.signed.WithLabelValues(provisioner).Inc()
func (m *Meter) SSHSigned(provisioner string, success bool) {
m.ssh.signed.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc()
}
// SSHRenewed implements [authority.Meter] for [Meter].
func (m *Meter) SSHRenewed(provisioner string) {
m.ssh.renewed.WithLabelValues(provisioner).Inc()
// X509Rekeyed implements [authority.Meter] for [Meter].
func (m *Meter) X509Rekeyed(provisioner string, success bool) {
m.x509.rekeyed.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc()
}
// X509Renewed implements [authority.Meter] for [Meter].
func (m *Meter) X509Renewed(provisioner string, success bool) {
m.x509.renewed.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc()
}
// X509Signed implements [authority.Meter] for [Meter].
func (m *Meter) X509Signed(provisioner string, success bool) {
m.x509.signed.WithLabelValues(provisioner, strconv.FormatBool(success)).Inc()
}
func newCounterVec(subsystem, name, help string, labels ...string) *prometheus.CounterVec {

Loading…
Cancel
Save