load-generator: remove acme v1 support. (#4132)
We don't intend to load test the legacy WFE implementation in the future and if we need to we can always revive this code from git. Removing it will make refactoring the ACME v2 code to be closer to RFC 8555 easier.
This commit is contained in:
parent
f61242e751
commit
de30d22303
|
|
@ -889,17 +889,12 @@ def run_chisel(test_case_filter):
|
|||
value()
|
||||
|
||||
def run_loadtest():
|
||||
"""Run the load generator for v1 and v2."""
|
||||
"""Run the ACME v2 load generator."""
|
||||
latency_data_file = "%s/integration-test-latency.json" % tempdir
|
||||
run("./bin/load-generator \
|
||||
-config test/load-generator/config/integration-test-config.json\
|
||||
-results %s" % latency_data_file)
|
||||
|
||||
latency_data_file = "%s/v2-integration-test-latency.json" % tempdir
|
||||
run("./bin/load-generator \
|
||||
-config test/load-generator/config/v2-integration-test-config.json\
|
||||
-results %s" % latency_data_file)
|
||||
|
||||
def check_balance():
|
||||
"""Verify that gRPC load balancing across backends is working correctly.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||

|
||||
|
||||
`load-generator` is a load generator for the Boulder WFE which emulates user workflows.
|
||||
`load-generator` is a load generator for RFC 8555 which emulates user workflows.
|
||||
|
|
|
|||
|
|
@ -27,18 +27,8 @@ import (
|
|||
|
||||
var (
|
||||
// stringToOperation maps a configured plan action to a function that can
|
||||
// operate on a state/context. V2 and V1 operations can **not** be intermixed
|
||||
// in the same plan.
|
||||
// operate on a state/context.
|
||||
stringToOperation = map[string]func(*State, *context) error{
|
||||
/* ACME v1 Operations */
|
||||
"newRegistration": newRegistration,
|
||||
"getRegistration": getRegistration,
|
||||
"newAuthorization": newAuthorization,
|
||||
"solveHTTPOne": solveHTTPOne,
|
||||
"newCertificate": newCertificate,
|
||||
"revokeCertificate": revokeCertificate,
|
||||
|
||||
/* ACME v2 Operations */
|
||||
"newAccount": newAccount,
|
||||
"getAccount": getAccount,
|
||||
"newOrder": newOrder,
|
||||
|
|
@ -93,23 +83,6 @@ func getAccount(s *State, ctx *context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getRegistration takes an existing v1 account from `state.regs` and puts it
|
||||
// into `ctx.reg`. The context `nonceSource` is also populated as convenience.
|
||||
func getRegistration(s *State, ctx *context) error {
|
||||
s.rMu.RLock()
|
||||
defer s.rMu.RUnlock()
|
||||
|
||||
// There must be an existing v1 registration in the state
|
||||
if len(s.regs) == 0 {
|
||||
return errors.New("no registrations to return")
|
||||
}
|
||||
|
||||
// Select a random registration from the state and put it into the context
|
||||
ctx.reg = s.regs[mrand.Intn(len(s.regs))]
|
||||
ctx.ns = &nonceSource{s: s}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newAccount puts a V2 account into the provided context. If the state provided
|
||||
// has too many accounts already (based on `state.NumAccts` and `state.maxRegs`)
|
||||
// then `newAccount` puts an existing account from the state into the context,
|
||||
|
|
@ -193,111 +166,6 @@ func newAccount(s *State, ctx *context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newRegistration puts a V1 registration into the provided context. If the
|
||||
// state provided has too many registrations already (based on `state.NumAccts`
|
||||
// and `state.maxRegs`) then `newRegistration` puts an existing registration
|
||||
// from the state into the context otherwise it creates a new registration and
|
||||
// puts it into both the state and the context.
|
||||
func newRegistration(s *State, ctx *context) error {
|
||||
// if we have generated the max number of registrations just become getRegistration
|
||||
if s.maxRegs != 0 && s.numRegs() >= s.maxRegs {
|
||||
return getRegistration(s, ctx)
|
||||
}
|
||||
signKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ns := &nonceSource{s: s}
|
||||
ctx.ns = ns
|
||||
signer, err := jose.NewSigner(
|
||||
jose.SigningKey{
|
||||
Key: signKey,
|
||||
Algorithm: jose.ES256,
|
||||
},
|
||||
&jose.SignerOptions{
|
||||
NonceSource: ns,
|
||||
EmbedJWK: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the registration object
|
||||
var regStr []byte
|
||||
if s.email != "" {
|
||||
regStr = []byte(fmt.Sprintf(`{"resource":"new-reg","contact":["mailto:%s"]}`, s.email))
|
||||
} else {
|
||||
regStr = []byte(`{"resource":"new-reg"}`)
|
||||
}
|
||||
// build the JWS object
|
||||
requestPayload, err := s.signWithNonce(regStr, signer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("/acme/new-reg, sign failed: %s", err)
|
||||
}
|
||||
|
||||
nStarted := time.Now()
|
||||
resp, err := s.post(fmt.Sprintf("%s%s", s.apiBase, newRegPath), requestPayload, ctx.ns)
|
||||
nFinished := time.Now()
|
||||
nState := "good"
|
||||
defer func() { s.callLatency.Add("POST /acme/new-reg", nStarted, nFinished, nState) }()
|
||||
if err != nil {
|
||||
nState = "error"
|
||||
return fmt.Errorf("/acme/new-reg, post failed: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 201 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
nState = "error"
|
||||
if err != nil {
|
||||
return fmt.Errorf("/acme/new-reg, bad response: %s", body)
|
||||
}
|
||||
return fmt.Errorf("/acme/new-reg, bad response status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// get terms
|
||||
links := resp.Header[http.CanonicalHeaderKey("link")]
|
||||
terms := ""
|
||||
for _, l := range links {
|
||||
if strings.HasSuffix(l, ">;rel=\"terms-of-service\"") {
|
||||
terms = l[1 : len(l)-len(">;rel=\"terms-of-service\"")]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// agree to terms
|
||||
regStr = []byte(fmt.Sprintf(`{"resource":"reg","agreement":"%s"}`, terms))
|
||||
|
||||
// build the JWS object
|
||||
requestPayload, err = s.signWithNonce(regStr, signer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("/acme/reg, sign failed: %s", err)
|
||||
}
|
||||
|
||||
tStarted := time.Now()
|
||||
resp, err = s.post(resp.Header.Get("Location"), requestPayload, ctx.ns)
|
||||
tFinished := time.Now()
|
||||
tState := "good"
|
||||
defer func() { s.callLatency.Add("POST /acme/reg/{ID}", tStarted, tFinished, tState) }()
|
||||
if err != nil {
|
||||
tState = "error"
|
||||
return fmt.Errorf("/acme/reg, post failed: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
tState = "error"
|
||||
return err
|
||||
}
|
||||
tState = "error"
|
||||
return fmt.Errorf("/acme/reg, bad response status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
ctx.reg = ®istration{key: signKey, signer: signer}
|
||||
s.addRegistration(ctx.reg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// randDomain generates a random(-ish) domain name as a subdomain of the
|
||||
// provided base domain.
|
||||
func randDomain(base string) string {
|
||||
|
|
@ -387,61 +255,6 @@ func newOrder(s *State, ctx *context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newAuthorization creates a new authz for a random domain name using the
|
||||
// context's registration. The resulting pending authorization is stored in the
|
||||
// context's list of pending authorizations.
|
||||
func newAuthorization(s *State, ctx *context) error {
|
||||
// generate a random(-ish) domain name, will cause some multiples but not enough to make rate limits annoying!
|
||||
randomDomain := randDomain(s.domainBase)
|
||||
|
||||
// create the new-authz object
|
||||
initAuth := fmt.Sprintf(`{"resource":"new-authz","identifier":{"type":"dns","value":"%s"}}`, randomDomain)
|
||||
|
||||
// build the JWS object
|
||||
requestPayload, err := s.signWithNonce([]byte(initAuth), ctx.reg.signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
started := time.Now()
|
||||
resp, err := s.post(fmt.Sprintf("%s/acme/new-authz", s.apiBase), requestPayload, ctx.ns)
|
||||
finished := time.Now()
|
||||
state := "good"
|
||||
defer func() { s.callLatency.Add("POST /acme/new-authz", started, finished, state) }()
|
||||
if err != nil {
|
||||
state = "error"
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 201 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
state = "error"
|
||||
return err
|
||||
}
|
||||
state = "error"
|
||||
return fmt.Errorf("bad response, status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
state = "error"
|
||||
return err
|
||||
}
|
||||
|
||||
var authz core.Authorization
|
||||
err = json.Unmarshal(body, &authz)
|
||||
if err != nil {
|
||||
state = "error"
|
||||
return err
|
||||
}
|
||||
// populate authz ID from location header because we strip it
|
||||
paths := strings.Split(resp.Header.Get("Location"), "/")
|
||||
authz.ID = paths[len(paths)-1]
|
||||
|
||||
ctx.pendingAuthz = append(ctx.pendingAuthz, &authz)
|
||||
return nil
|
||||
}
|
||||
|
||||
// popPendingOrder *removes* a random pendingOrder from the context, returning
|
||||
// it.
|
||||
func popPendingOrder(ctx *context) *OrderJSON {
|
||||
|
|
@ -628,103 +441,6 @@ func fulfillOrder(s *State, ctx *context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// popPending **removes** a random pending authorization from the context,
|
||||
// returning it.
|
||||
func popPending(ctx *context) *core.Authorization {
|
||||
authzIndex := mrand.Intn(len(ctx.pendingAuthz))
|
||||
authz := ctx.pendingAuthz[authzIndex]
|
||||
ctx.pendingAuthz = append(ctx.pendingAuthz[:authzIndex], ctx.pendingAuthz[authzIndex+1:]...)
|
||||
return authz
|
||||
}
|
||||
|
||||
// solveHTTPOne solves a pending authorization's HTTP-01 challenge. It polls the
|
||||
// authorization waiting for the status to change to valid.
|
||||
func solveHTTPOne(s *State, ctx *context) error {
|
||||
if len(ctx.pendingAuthz) == 0 {
|
||||
return errors.New("no pending authorizations to complete")
|
||||
}
|
||||
authz := popPending(ctx)
|
||||
var chall *core.Challenge
|
||||
for _, c := range authz.Challenges {
|
||||
if c.Type == "http-01" {
|
||||
chall = &c
|
||||
break
|
||||
}
|
||||
}
|
||||
if chall == nil {
|
||||
return errors.New("no http-01 challenges to complete")
|
||||
}
|
||||
|
||||
jwk := &jose.JSONWebKey{Key: &ctx.reg.key.PublicKey}
|
||||
thumbprint, err := jwk.Thumbprint(crypto.SHA256)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authStr := fmt.Sprintf("%s.%s", chall.Token, base64.RawURLEncoding.EncodeToString(thumbprint))
|
||||
s.challSrv.AddHTTPOneChallenge(chall.Token, authStr)
|
||||
defer s.challSrv.DeleteHTTPOneChallenge(chall.Token)
|
||||
|
||||
update := fmt.Sprintf(`{"resource":"challenge","keyAuthorization":"%s"}`, authStr)
|
||||
requestPayload, err := s.signWithNonce([]byte(update), ctx.reg.signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cStarted := time.Now()
|
||||
resp, err := s.post(chall.URI, requestPayload, ctx.ns)
|
||||
cFinished := time.Now()
|
||||
cState := "good"
|
||||
defer func() { s.callLatency.Add("POST /acme/challenge/{ID}", cStarted, cFinished, cState) }()
|
||||
if err != nil {
|
||||
cState = "error"
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
cState = "error"
|
||||
return fmt.Errorf("Unexpected error code")
|
||||
}
|
||||
// Sit and spin until status valid or invalid, replicating Certbot behavior
|
||||
ident := ""
|
||||
for i := 0; i < 3; i++ {
|
||||
aStarted := time.Now()
|
||||
resp, err = s.get(fmt.Sprintf("%s/acme/authz/%s", s.apiBase, authz.ID))
|
||||
aFinished := time.Now()
|
||||
aState := "good"
|
||||
defer func() { s.callLatency.Add("GET /acme/authz/{ID}", aStarted, aFinished, aState) }()
|
||||
if err != nil {
|
||||
aState = "error"
|
||||
return fmt.Errorf("/acme/authz bad response: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
aState = "error"
|
||||
return err
|
||||
}
|
||||
var newAuthz core.Authorization
|
||||
err = json.Unmarshal(body, &newAuthz)
|
||||
if err != nil {
|
||||
aState = "error"
|
||||
return fmt.Errorf("/acme/authz bad response: %s", body)
|
||||
}
|
||||
if newAuthz.Status == "valid" {
|
||||
ident = newAuthz.Identifier.Value
|
||||
break
|
||||
}
|
||||
if newAuthz.Status == "invalid" {
|
||||
return fmt.Errorf("HTTP-01 challenge invalid: %s", string(body))
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
if ident == "" {
|
||||
return errors.New("HTTP-01 challenge validation timed out")
|
||||
}
|
||||
|
||||
ctx.finalizedAuthz = append(ctx.finalizedAuthz, ident)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOrder GETs an order by URL, returning an OrderJSON object. It tracks the
|
||||
// latency of the GET operation in the provided state.
|
||||
func getOrder(s *State, url string) (*OrderJSON, error) {
|
||||
|
|
@ -898,114 +614,3 @@ func min(a, b int) int {
|
|||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// newCertificate POST's the v1 new-cert endpoint with a CSR for a random subset
|
||||
// of domains that have finalized authz's in the context (Up to
|
||||
// `state.maxNamesPerCert` domains). The CSR's private key is the
|
||||
// `state.certKey`. The context's `certs` list is updated with the URL of the
|
||||
// certificate produced.
|
||||
func newCertificate(s *State, ctx *context) error {
|
||||
authsLen := len(ctx.finalizedAuthz)
|
||||
num := min(mrand.Intn(authsLen), s.maxNamesPerCert)
|
||||
dnsNames := []string{}
|
||||
for i := 0; i <= num; i++ {
|
||||
dnsNames = append(dnsNames, ctx.finalizedAuthz[mrand.Intn(authsLen)])
|
||||
}
|
||||
csr, err := x509.CreateCertificateRequest(
|
||||
rand.Reader,
|
||||
&x509.CertificateRequest{DNSNames: dnsNames},
|
||||
s.certKey,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := fmt.Sprintf(
|
||||
`{"resource":"new-cert","csr":"%s"}`,
|
||||
base64.URLEncoding.EncodeToString(csr),
|
||||
)
|
||||
|
||||
// build the JWS object
|
||||
requestPayload, err := s.signWithNonce([]byte(request), ctx.reg.signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
started := time.Now()
|
||||
resp, err := s.post(fmt.Sprintf("%s%s", s.apiBase, newCertPath), requestPayload, ctx.ns)
|
||||
finished := time.Now()
|
||||
state := "good"
|
||||
defer func() { s.callLatency.Add("POST /acme/new-cert", started, finished, state) }()
|
||||
if err != nil {
|
||||
state = "error"
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 201 {
|
||||
state = "error"
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("bad response, status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
if certLoc := resp.Header.Get("Location"); certLoc != "" {
|
||||
ctx.certs = append(ctx.certs, certLoc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// revokeCertificate revokes a random certificate from the context's list of
|
||||
// certificates. Presently it always uses the context's registration and the V1
|
||||
// style of revocation. The certificate is removed from the context's `certs`
|
||||
// list.
|
||||
//
|
||||
// TODO(@cpu): Write a V2 version of `revokeCertificate` that uses the context's
|
||||
// account and a key ID JWS.
|
||||
func revokeCertificate(s *State, ctx *context) error {
|
||||
// randomly select a cert to revoke
|
||||
if len(ctx.certs) == 0 {
|
||||
return errors.New("no certificates to revoke")
|
||||
}
|
||||
|
||||
index := mrand.Intn(len(ctx.certs))
|
||||
resp, err := s.get(ctx.certs[index])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := fmt.Sprintf(`{"resource":"revoke-cert","certificate":"%s"}`, base64.URLEncoding.EncodeToString(body))
|
||||
requestPayload, err := s.signWithNonce([]byte(request), ctx.reg.signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
started := time.Now()
|
||||
resp, err = s.post(fmt.Sprintf("%s%s", s.apiBase, revokeCertPath), requestPayload, ctx.ns)
|
||||
finished := time.Now()
|
||||
state := "good"
|
||||
defer func() { s.callLatency.Add("POST /acme/revoke-cert", started, finished, state) }()
|
||||
if err != nil {
|
||||
state = "error"
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
state = "error"
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("bad response, status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
ctx.certs = append(ctx.certs[:index], ctx.certs[index+1:]...)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"plan": {
|
||||
"actions": [
|
||||
"newRegistration",
|
||||
"newAuthorization",
|
||||
"solveHTTPOne",
|
||||
"newCertificate"
|
||||
],
|
||||
"rate": 5,
|
||||
"runtime": "5m",
|
||||
"rateDelta": "5/1m"
|
||||
},
|
||||
"apiBase": "http://localhost:4000",
|
||||
"domainBase": "com",
|
||||
"httpOneAddr": "localhost:5002",
|
||||
"tlsOneAddr": "localhost:5001",
|
||||
"regKeySize": 2048,
|
||||
"certKeySize": 2048,
|
||||
"regEmail": "loadtesting@letsencrypt.org",
|
||||
"results": "example-latency.json"
|
||||
}
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
{
|
||||
"plan": {
|
||||
"actions": [
|
||||
"newRegistration",
|
||||
"newAuthorization",
|
||||
"solveHTTPOne",
|
||||
"newCertificate"
|
||||
"newAccount",
|
||||
"newOrder",
|
||||
"fulfillOrder",
|
||||
"finalizeOrder"
|
||||
],
|
||||
"rate": 1,
|
||||
"runtime": "10s",
|
||||
"rateDelta": "5/1m"
|
||||
},
|
||||
"apiBase": "http://boulder:4000",
|
||||
"apiBase": "http://boulder:4001",
|
||||
"domainBase": "com",
|
||||
"httpOneAddr": "localhost:5002",
|
||||
"tlsOneAddr": "localhost:5001",
|
||||
"regKeySize": 2048,
|
||||
"certKeySize": 2048,
|
||||
"regEmail": "loadtesting@letsencrypt.org"
|
||||
"regEmail": "loadtesting@letsencrypt.org",
|
||||
"maxRegs": 20,
|
||||
"maxNamesPerCert": 20,
|
||||
"dontSaveState": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"plan": {
|
||||
"actions": [
|
||||
"newAccount",
|
||||
"newOrder",
|
||||
"fulfillOrder",
|
||||
"finalizeOrder"
|
||||
],
|
||||
"rate": 1,
|
||||
"runtime": "10s",
|
||||
"rateDelta": "5/1m"
|
||||
},
|
||||
"apiBase": "http://boulder:4001",
|
||||
"domainBase": "com",
|
||||
"httpOneAddr": "localhost:5002",
|
||||
"regKeySize": 2048,
|
||||
"certKeySize": 2048,
|
||||
"regEmail": "loadtesting@letsencrypt.org",
|
||||
"maxRegs": 20,
|
||||
"maxNamesPerCert": 20,
|
||||
"dontSaveState": true
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@ import (
|
|||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/challtestsrv"
|
||||
)
|
||||
|
||||
|
|
@ -37,15 +36,6 @@ type RatePeriod struct {
|
|||
Rate int64
|
||||
}
|
||||
|
||||
// registration is an ACME v1 registration resource
|
||||
type registration struct {
|
||||
key *ecdsa.PrivateKey
|
||||
signer jose.Signer
|
||||
finalizedAuthz []string
|
||||
certs []string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// account is an ACME v2 account resource. It does not have a `jose.Signer`
|
||||
// because we need to set the Signer options per-request with the URL being
|
||||
// POSTed and must construct it on the fly from the `key`. Accounts are
|
||||
|
|
@ -69,26 +59,7 @@ func (acct *account) update(finalizedOrders, certs []string) {
|
|||
acct.certs = append(acct.certs, certs...)
|
||||
}
|
||||
|
||||
// update locks a registration resource's mutx and sets the `finalizedAuthz` and
|
||||
// `certs` fields to the provided values.
|
||||
func (r *registration) update(finalizedAuthz, certs []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.finalizedAuthz = append(r.finalizedAuthz, finalizedAuthz...)
|
||||
r.certs = append(r.certs, certs...)
|
||||
}
|
||||
|
||||
type context struct {
|
||||
/* ACME V1 Context */
|
||||
// The current V1 registration (may be nil for V2 load generation)
|
||||
reg *registration
|
||||
// Pending authorizations waiting for challenge validation
|
||||
pendingAuthz []*core.Authorization
|
||||
// IDs of finalized authorizations in valid status
|
||||
finalizedAuthz []string
|
||||
|
||||
/* ACME V2 Context */
|
||||
// The current V2 account (may be nil for legacy load generation)
|
||||
acct *account
|
||||
// Pending orders waiting for authorization challenge validation
|
||||
|
|
@ -98,7 +69,6 @@ type context struct {
|
|||
// Finalized orders that have certificates
|
||||
finalizedOrders []string
|
||||
|
||||
/* Shared Context */
|
||||
// A list of URLs for issued certificates
|
||||
certs []string
|
||||
// The nonce source for JWS signature nonce headers
|
||||
|
|
@ -208,8 +178,6 @@ type State struct {
|
|||
|
||||
rMu sync.RWMutex
|
||||
|
||||
// regs holds V1 registration objects
|
||||
regs []*registration
|
||||
// accts holds V2 account objects
|
||||
accts []*account
|
||||
|
||||
|
|
@ -225,12 +193,6 @@ type State struct {
|
|||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
type rawRegistration struct {
|
||||
Certs []string `json:"certs"`
|
||||
FinalizedAuthz []string `json:"finalizedAuthz"`
|
||||
RawKey []byte `json:"rawKey"`
|
||||
}
|
||||
|
||||
type rawAccount struct {
|
||||
FinalizedOrders []string `json:"finalizedOrders"`
|
||||
Certs []string `json:"certs"`
|
||||
|
|
@ -239,8 +201,7 @@ type rawAccount struct {
|
|||
}
|
||||
|
||||
type snapshot struct {
|
||||
Registrations []rawRegistration
|
||||
Accounts []rawAccount
|
||||
Accounts []rawAccount
|
||||
}
|
||||
|
||||
func (s *State) numAccts() int {
|
||||
|
|
@ -249,28 +210,10 @@ func (s *State) numAccts() int {
|
|||
return len(s.accts)
|
||||
}
|
||||
|
||||
func (s *State) numRegs() int {
|
||||
s.rMu.RLock()
|
||||
defer s.rMu.RUnlock()
|
||||
return len(s.regs)
|
||||
}
|
||||
|
||||
// Snapshot will save out generated registrations and accounts
|
||||
// Snapshot will save out generated accounts
|
||||
func (s *State) Snapshot(filename string) error {
|
||||
fmt.Printf("[+] Saving registrations/accounts to %s\n", filename)
|
||||
fmt.Printf("[+] Saving accounts to %s\n", filename)
|
||||
snap := snapshot{}
|
||||
// assume rMu lock operations aren't happening right now
|
||||
for _, reg := range s.regs {
|
||||
k, err := x509.MarshalECPrivateKey(reg.key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snap.Registrations = append(snap.Registrations, rawRegistration{
|
||||
Certs: reg.certs,
|
||||
FinalizedAuthz: reg.finalizedAuthz,
|
||||
RawKey: k,
|
||||
})
|
||||
}
|
||||
for _, acct := range s.accts {
|
||||
k, err := x509.MarshalECPrivateKey(acct.key)
|
||||
if err != nil {
|
||||
|
|
@ -290,9 +233,9 @@ func (s *State) Snapshot(filename string) error {
|
|||
return ioutil.WriteFile(filename, cont, os.ModePerm)
|
||||
}
|
||||
|
||||
// Restore previously generated registrations and accounts
|
||||
// Restore previously generated accounts
|
||||
func (s *State) Restore(filename string) error {
|
||||
fmt.Printf("[+] Loading registrations/accounts from %s\n", filename)
|
||||
fmt.Printf("[+] Loading accounts from %s\n", filename)
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -302,27 +245,6 @@ func (s *State) Restore(filename string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range snap.Registrations {
|
||||
key, err := x509.ParseECPrivateKey(r.RawKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
signer, err := jose.NewSigner(jose.SigningKey{
|
||||
Key: key,
|
||||
Algorithm: jose.RS256,
|
||||
}, &jose.SignerOptions{
|
||||
NonceSource: &nonceSource{s: s},
|
||||
EmbedJWK: true,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.regs = append(s.regs, ®istration{
|
||||
key: key,
|
||||
signer: signer,
|
||||
certs: r.Certs,
|
||||
})
|
||||
}
|
||||
for _, a := range snap.Accounts {
|
||||
key, err := x509.ParseECPrivateKey(a.RawKey)
|
||||
if err != nil {
|
||||
|
|
@ -571,9 +493,6 @@ func (s *State) get(path string) (*http.Response, error) {
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// Nonce utils, these methods are used to generate/store/retrieve the nonces
|
||||
// required for JWS in V1 ACME requests
|
||||
|
||||
// signWithNonce signs the provided message with the provided signer, returning
|
||||
// the raw JWS bytes or an error. signWithNonce is not compatible with ACME v2
|
||||
func (s *State) signWithNonce(payload []byte, signer jose.Signer) ([]byte, error) {
|
||||
|
|
@ -639,14 +558,6 @@ func (s *State) addAccount(acct *account) {
|
|||
s.accts = append(s.accts, acct)
|
||||
}
|
||||
|
||||
// addRegistration adds the provided registration to the state's list of regs
|
||||
func (s *State) addRegistration(reg *registration) {
|
||||
s.rMu.Lock()
|
||||
defer s.rMu.Unlock()
|
||||
|
||||
s.regs = append(s.regs, reg)
|
||||
}
|
||||
|
||||
func (s *State) sendCall() {
|
||||
defer s.wg.Done()
|
||||
ctx := &context{}
|
||||
|
|
@ -659,10 +570,6 @@ func (s *State) sendCall() {
|
|||
break
|
||||
}
|
||||
}
|
||||
// If the context's V1 registration
|
||||
if ctx.reg != nil {
|
||||
ctx.reg.update(ctx.finalizedAuthz, ctx.certs)
|
||||
}
|
||||
// If the context's V2 account isn't nil, update it based on the context's
|
||||
// finalizedOrders and certs.
|
||||
if ctx.acct != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue