Add stats to CA

This commit is contained in:
Richard Barnes 2015-10-21 17:00:06 -04:00
parent 3637dfb0c1
commit 128b784805
4 changed files with 56 additions and 13 deletions

View File

@ -20,6 +20,7 @@ import (
"sync"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
@ -49,6 +50,15 @@ var badSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
x509.ECDSAWithSHA1: true,
}
// Metrics for CA statistics
const (
// Increments when CA observes an HSM fault
metricHSMFaultObserved = "CA.OCSP.HSMFault.Observed"
// Increments when CA rejects a request due to an HSM fault
metricHSMFaultRejected = "CA.OCSP.HSMFault.Rejected"
)
// HSM faults tend to be persistent. If we observe one, we will refuse any
// further requests for signing (certificates or OCSP) for this period.
const hsmFaultTimeout = 5 * 60 * time.Second
@ -64,6 +74,7 @@ type CertificateAuthorityImpl struct {
Publisher core.Publisher
Clk clock.Clock // TODO(jmhodges): should be private, like log
log *blog.AuditLogger
stats statsd.Statter
Prefix int // Prepended to the serial number
ValidityPeriod time.Duration
NotAfter time.Time
@ -79,7 +90,7 @@ type CertificateAuthorityImpl struct {
// using CFSSL's authenticated signature scheme. A CA created in this way
// issues for a single profile on the remote signer, which is indicated
// by name in this constructor.
func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, issuerCert string) (*CertificateAuthorityImpl, error) {
func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats statsd.Statter, issuerCert string) (*CertificateAuthorityImpl, error) {
var ca *CertificateAuthorityImpl
var err error
logger := blog.GetAuditLogger()
@ -139,6 +150,7 @@ func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, issuerCer
Prefix: config.SerialPrefix,
Clk: clk,
log: logger,
stats: stats,
NotAfter: issuer.NotAfter,
}
@ -199,6 +211,7 @@ func (ca *CertificateAuthorityImpl) noteHSMFault(err error) {
defer ca.hsmFaultLock.Unlock()
if err != nil {
ca.stats.Inc(metricHSMFaultObserved, 1, 1.0)
ca.hsmFaultLastObserved = ca.Clk.Now()
}
return
@ -209,6 +222,7 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
if ca.checkHSMFault() {
err := core.ServiceUnavailableError("GenerateOCSP call rejected; HSM is unavailable")
ca.log.WarningErr(err)
ca.stats.Inc(metricHSMFaultRejected, 1, 1.0)
return nil, err
}
@ -247,6 +261,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
if ca.checkHSMFault() {
err := core.ServiceUnavailableError("IssueCertificate call rejected; HSM is unavailable")
ca.log.WarningErr(err)
ca.stats.Inc(metricHSMFaultRejected, 1, 1.0)
return emptyCert, err
}

View File

@ -116,6 +116,7 @@ type testCtx struct {
reg core.Registration
pa core.PolicyAuthority
fc clock.FakeClock
stats *mocks.Statter
cleanUp func()
}
@ -192,7 +193,10 @@ func setup(t *testing.T) *testCtx {
},
},
}
return &testCtx{ssa, caConfig, reg, pa, fc, cleanUp}
stats := mocks.NewStatter()
return &testCtx{ssa, caConfig, reg, pa, fc, &stats, cleanUp}
}
func TestFailNoSerial(t *testing.T) {
@ -200,14 +204,14 @@ func TestFailNoSerial(t *testing.T) {
defer ctx.cleanUp()
ctx.caConfig.SerialPrefix = 0
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
}
func TestIssueCertificate(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -284,7 +288,7 @@ func TestIssueCertificate(t *testing.T) {
func TestRejectNoName(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -301,7 +305,7 @@ func TestRejectNoName(t *testing.T) {
func TestRejectTooManyNames(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -318,7 +322,7 @@ func TestRejectTooManyNames(t *testing.T) {
func TestDeduplication(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -342,7 +346,7 @@ func TestDeduplication(t *testing.T) {
func TestRejectValidityTooLong(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -360,7 +364,7 @@ func TestRejectValidityTooLong(t *testing.T) {
func TestShortKey(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -376,7 +380,7 @@ func TestShortKey(t *testing.T) {
func TestRejectBadAlgorithm(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -393,7 +397,7 @@ func TestCapitalizedLetters(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ctx.caConfig.MaxNames = 3
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -414,7 +418,7 @@ func TestHSMFaultTimeout(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -471,4 +475,8 @@ func TestHSMFaultTimeout(t *testing.T) {
_, err = ca.GenerateOCSP(ocspRequest)
test.AssertError(t, err, "CA failed to persist HSM fault")
test.AssertEquals(t, err.Error(), "GenerateOCSP call rejected; HSM is unavailable")
// Verify that the appropriate stats got recorded for all this
test.AssertEquals(t, ctx.stats.Counters[metricHSMFaultObserved], int64(2))
test.AssertEquals(t, ctx.stats.Counters[metricHSMFaultRejected], int64(4))
}

View File

@ -39,7 +39,7 @@ func main() {
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist)
cmd.FailOnError(err, "Couldn't create PA")
cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), c.Common.IssuerCert)
cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), stats, c.Common.IssuerCert)
cmd.FailOnError(err, "Failed to create CA impl")
cai.PA = pa

View File

@ -15,6 +15,7 @@ import (
"strings"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
@ -414,3 +415,22 @@ type BadHSMOCSPSigner string
func (bhos BadHSMOCSPSigner) Sign(ocsp.SignRequest) ([]byte, error) {
return nil, fmt.Errorf(string(bhos))
}
// Statter is a stat counter that is a no-op except for locally handling Inc
// calls (which are most of what we use).
type Statter struct {
statsd.NoopClient
Counters map[string]int64
}
// Inc increments the indicated metric by the indicated value, in the Counters
// map maintained by the statter
func (s *Statter) Inc(metric string, value int64, rate float32) error {
s.Counters[metric] += value
return nil
}
// NewStatter returns an empty statter with all counters zero
func NewStatter() Statter {
return Statter{statsd.NoopClient{}, map[string]int64{}}
}