load-generator: Add revocation-by-account support (#4237)
Adds RFC 8555 revocation authorized using the issuing account.
This commit is contained in:
parent
c0246b3d97
commit
dc11681faa
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -22,8 +23,8 @@ import (
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
"github.com/letsencrypt/boulder/identifier"
|
"github.com/letsencrypt/boulder/identifier"
|
||||||
"github.com/letsencrypt/boulder/probs"
|
"github.com/letsencrypt/boulder/probs"
|
||||||
|
"github.com/letsencrypt/boulder/revocation"
|
||||||
"github.com/letsencrypt/boulder/test/load-generator/acme"
|
"github.com/letsencrypt/boulder/test/load-generator/acme"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,11 +32,12 @@ var (
|
||||||
// stringToOperation maps a configured plan action to a function that can
|
// stringToOperation maps a configured plan action to a function that can
|
||||||
// operate on a state/context.
|
// operate on a state/context.
|
||||||
stringToOperation = map[string]func(*State, *context) error{
|
stringToOperation = map[string]func(*State, *context) error{
|
||||||
"newAccount": newAccount,
|
"newAccount": newAccount,
|
||||||
"getAccount": getAccount,
|
"getAccount": getAccount,
|
||||||
"newOrder": newOrder,
|
"newOrder": newOrder,
|
||||||
"fulfillOrder": fulfillOrder,
|
"fulfillOrder": fulfillOrder,
|
||||||
"finalizeOrder": finalizeOrder,
|
"finalizeOrder": finalizeOrder,
|
||||||
|
"revokeCertificate": revokeCertificate,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -582,3 +584,81 @@ func postAsGet(s *State, ctx *context, url string, latencyTag string) (*http.Res
|
||||||
|
|
||||||
return s.post(url, requestPayload, ctx.ns, latencyTag, http.StatusOK)
|
return s.post(url, requestPayload, ctx.ns, latencyTag, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func popCertificate(ctx *context) string {
|
||||||
|
certIndex := mrand.Intn(len(ctx.certs))
|
||||||
|
certURL := ctx.certs[certIndex]
|
||||||
|
ctx.certs = append(ctx.certs[:certIndex], ctx.certs[certIndex+1:]...)
|
||||||
|
return certURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCert(s *State, ctx *context, url string) ([]byte, error) {
|
||||||
|
latencyTag := "/acme/cert/{serial}"
|
||||||
|
resp, err := postAsGet(s, ctx, url, latencyTag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s bad response: %s", url, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return ioutil.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// revokeCertificate removes a certificate url from the context, retrieves it,
|
||||||
|
// and sends a revocation request for the certificate to the ACME server.
|
||||||
|
// The revocation request is signed with the account key rather than the certificate
|
||||||
|
// key.
|
||||||
|
func revokeCertificate(s *State, ctx *context) error {
|
||||||
|
if len(ctx.certs) < 1 {
|
||||||
|
return errors.New("No certificates in the context that can be revoked")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := mrand.Float32(); r > s.revokeChance {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
certURL := popCertificate(ctx)
|
||||||
|
certPEM, err := getCert(s, ctx, certURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pemBlock, _ := pem.Decode(certPEM)
|
||||||
|
revokeObj := struct {
|
||||||
|
Certificate string
|
||||||
|
Reason int
|
||||||
|
}{
|
||||||
|
Certificate: base64.URLEncoding.EncodeToString(pemBlock.Bytes),
|
||||||
|
Reason: revocation.Unspecified,
|
||||||
|
}
|
||||||
|
|
||||||
|
revokeJSON, err := json.Marshal(revokeObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
revokeURL := s.directory.EndpointURL(acme.RevokeCertEndpoint)
|
||||||
|
// TODO(roland): randomly use the certificate key to sign the request instead of
|
||||||
|
// the account key
|
||||||
|
jws, err := ctx.signKeyIDV2Request(revokeJSON, revokeURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestPayload := []byte(jws.FullSerialize())
|
||||||
|
|
||||||
|
resp, err := s.post(
|
||||||
|
revokeURL,
|
||||||
|
requestPayload,
|
||||||
|
ctx.ns,
|
||||||
|
"/acme/revoke-cert",
|
||||||
|
http.StatusOK,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
_, err = ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
"newAccount",
|
"newAccount",
|
||||||
"newOrder",
|
"newOrder",
|
||||||
"fulfillOrder",
|
"fulfillOrder",
|
||||||
"finalizeOrder"
|
"finalizeOrder",
|
||||||
|
"revokeCertificate"
|
||||||
],
|
],
|
||||||
"rate": 1,
|
"rate": 1,
|
||||||
"runtime": "10s",
|
"runtime": "10s",
|
||||||
|
|
@ -22,5 +23,6 @@
|
||||||
"regEmail": "loadtesting@letsencrypt.org",
|
"regEmail": "loadtesting@letsencrypt.org",
|
||||||
"maxRegs": 20,
|
"maxRegs": 20,
|
||||||
"maxNamesPerCert": 20,
|
"maxNamesPerCert": 20,
|
||||||
"dontSaveState": true
|
"dontSaveState": true,
|
||||||
|
"revokeChance": 0.5
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type Config struct {
|
||||||
MaxRegs int // maximum number of registrations to create
|
MaxRegs int // maximum number of registrations to create
|
||||||
MaxNamesPerCert int // maximum number of names on one certificate/order
|
MaxNamesPerCert int // maximum number of names on one certificate/order
|
||||||
ChallengeStrategy string // challenge selection strategy ("random", "http-01", "dns-01", "tls-alpn-01")
|
ChallengeStrategy string // challenge selection strategy ("random", "http-01", "dns-01", "tls-alpn-01")
|
||||||
|
RevokeChance float32 // chance of revoking certificate after issuance, between 0.0 and 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -87,6 +88,7 @@ func main() {
|
||||||
config.RegEmail,
|
config.RegEmail,
|
||||||
config.Plan.Actions,
|
config.Plan.Actions,
|
||||||
config.ChallengeStrategy,
|
config.ChallengeStrategy,
|
||||||
|
config.RevokeChance,
|
||||||
)
|
)
|
||||||
cmd.FailOnError(err, "Failed to create load generator")
|
cmd.FailOnError(err, "Failed to create load generator")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,8 @@ type State struct {
|
||||||
challStrat acme.ChallengeStrategy
|
challStrat acme.ChallengeStrategy
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
|
||||||
|
revokeChance float32
|
||||||
|
|
||||||
reqTotal int64
|
reqTotal int64
|
||||||
respCodes map[int]*respCode
|
respCodes map[int]*respCode
|
||||||
cMu sync.Mutex
|
cMu sync.Mutex
|
||||||
|
|
@ -288,7 +290,8 @@ func New(
|
||||||
latencyPath string,
|
latencyPath string,
|
||||||
userEmail string,
|
userEmail string,
|
||||||
operations []string,
|
operations []string,
|
||||||
challStrat string) (*State, error) {
|
challStrat string,
|
||||||
|
revokeChance float32) (*State, error) {
|
||||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -301,6 +304,9 @@ func New(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if revokeChance > 1 {
|
||||||
|
return nil, errors.New("revokeChance must be between 0.0 and 1.0")
|
||||||
|
}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
|
|
@ -333,6 +339,7 @@ func New(
|
||||||
maxNamesPerCert: maxNamesPerCert,
|
maxNamesPerCert: maxNamesPerCert,
|
||||||
email: userEmail,
|
email: userEmail,
|
||||||
respCodes: make(map[int]*respCode),
|
respCodes: make(map[int]*respCode),
|
||||||
|
revokeChance: revokeChance,
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert operations strings to methods
|
// convert operations strings to methods
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue