Skip to content

Commit e124408

Browse files
committed
This implements more sleeps when clients are expected to poll
Specifically this adds two additional delay points: - Between completing validation but before updating the order state - After signing the certificate and before marking it as ready The first is controlled by the existing PEBBLE_VA_* environment variables, and the second is controlled by equivalent PEBBLE_CA_* environment variables. It also corrects a minor typo in the README.md (default is 5s, not 15s), and removes shadowing of the internal `len` function.
1 parent 44cfa62 commit e124408

3 files changed

Lines changed: 95 additions & 24 deletions

File tree

README.md

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,17 @@ services:
130130
- 15000:15000 # Management port
131131
environment:
132132
- PEBBLE_VA_NOSLEEP=1
133+
- PEBBLE_CA_NOSLEEP=1
133134
volumes:
134135
- ./my-pebble-config.json:/test/my-pebble-config.json
135136
```
136137
137138
With a Docker command:
138139
139140
```bash
140-
docker run -e "PEBBLE_VA_NOSLEEP=1" letsencrypt/pebble
141+
docker run -e "PEBBLE_VA_NOSLEEP=1" -e "PEBBLE_CA_NOSLEEP=1" letsencrypt/pebble
141142
# or
142-
docker run -e "PEBBLE_VA_NOSLEEP=1" --mount src=$(pwd)/my-pebble-config.json,target=/test/my-pebble-config.json,type=bind letsencrypt/pebble pebble -config /test/my-pebble-config.json
143+
docker run -e "PEBBLE_VA_NOSLEEP=1" -e "PEBBLE_CA_NOSLEEP=1" --mount src=$(pwd)/my-pebble-config.json,target=/test/my-pebble-config.json,type=bind letsencrypt/pebble pebble -config /test/my-pebble-config.json
143144
```
144145

145146
**Note**: The Pebble dockerfile uses [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) and requires Docker CE 17.05.0-ce or newer.
@@ -197,19 +198,33 @@ for more information.
197198

198199
### Testing at full speed
199200

200-
By default Pebble will sleep a random number of seconds (from 0 to 15) between
201-
individual challenge validation attempts. This ensures clients don't make
202-
assumptions about when the challenge is solved from the CA side by observing
203-
a single request for a challenge response. Instead clients must poll the
204-
challenge to observe the state since the CA may send many validation requests.
201+
By default Pebble will sleep a random number of seconds (from 0 to 5) during
202+
specific stages of the workflow. This ensures clients don't make assumptions
203+
about the state of the system based on observing external behavior. Instead,
204+
clients must poll at the appropriate time.
205205

206-
To test issuance "at full speed" with no artificial sleeps set the environment
207-
variable `PEBBLE_VA_NOSLEEP` to `1`. E.g.
206+
These sleeps occur at the following 3 points:
208207

209-
`PEBBLE_VA_NOSLEEP=1 pebble -config ./test/config/pebble-config.json`
208+
1. Before attempting to perform a validation
209+
2. Between validating and updating the order state
210+
3. After creating the certificate and marking it as ready
210211

211-
The maximal number of seconds to sleep can be configured by defining
212-
`PEBBLE_VA_SLEEPTIME`. It must be set to a positive integer.
212+
To test "at full speed" with no artificial sleeps, set the following environment
213+
variables:
214+
215+
- `PEBBLE_VA_NOSLEEP` set to `1` to disable #1 and #2
216+
- `PEBBLE_CA_NOSLEEP` set to `1` to disable #3
217+
218+
The maximum duration can also be controlled via the following environment variables,
219+
both of which are specified in seconds:
220+
221+
- `PEBBLE_VA_SLEEPTIME` for #1 and #2
222+
- `PEBBLE_CA_SLEEPTIME` for #3
223+
224+
For example, to disable sleeping in validation, and cap the certificate issuance
225+
time to 5 seconds, use:
226+
227+
`PEBBLE_VA_NOSLEEP=1 PEBBLE_CA_SLEEPTIME=5 pebble -config ./test/config/pebble-config.json`
213228

214229
### Skipping Validation
215230

ca/ca.go

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package ca
22

33
import (
44
"crypto"
5-
"crypto/rand"
5+
crand "crypto/rand"
66
"crypto/rsa"
77
"crypto/sha1"
88
"crypto/x509"
@@ -13,7 +13,10 @@ import (
1313
"log"
1414
"math"
1515
"math/big"
16+
mrand "math/rand"
1617
"net"
18+
"os"
19+
"strconv"
1720
"strings"
1821
"time"
1922

@@ -25,12 +28,33 @@ import (
2528
const (
2629
rootCAPrefix = "Pebble Root CA "
2730
intermediateCAPrefix = "Pebble Intermediate CA "
31+
32+
// noSleepEnvVar defines the environment variable name used to signal that the
33+
// CA should *not* sleep before transitioning an order from processing to ready.
34+
// Set this to 1 when you invoke Pebble if you wish completion to be done at full
35+
// speed, e.g.:
36+
// PEBBLE_CA_NOSLEEP=1 pebble
37+
noSleepEnvVar = "PEBBLE_CA_NOSLEEP"
38+
39+
// sleepTimeEnvVar defines the environment variable name used to set the time
40+
// the CA should sleep between transitioning an order from processing to ready
41+
// (if not disabled). Set this e.g. to 5 when you invoke Pebble if you wish the
42+
// delays to be between 0 and 10 seconds (instead of between 0 and 5 seconds):
43+
// PEBBLE_CA_SLEEPTIME=5 pebble
44+
sleepTimeEnvVar = "PEBBLE_CA_SLEEPTIME"
45+
46+
// defaultSleepTime defines the default sleep time (in seconds) between
47+
// transitioning an order from processing to ready.
48+
// variables PEBBLE_CA_NOSLEEP resp. PEBBLE_CA_SLEEPTIME (see above).
49+
defaultSleepTime = 5
2850
)
2951

3052
type CAImpl struct {
3153
log *log.Logger
3254
db *db.MemoryStore
3355
ocspResponderURL string
56+
sleep bool
57+
sleepTime int
3458

3559
chains []*chain
3660
}
@@ -57,7 +81,7 @@ type issuer struct {
5781
}
5882

5983
func makeSerial() *big.Int {
60-
serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
84+
serial, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
6185
if err != nil {
6286
panic(fmt.Sprintf("unable to create random serial number: %s", err.Error()))
6387
}
@@ -92,7 +116,7 @@ func makeSubjectKeyID(key crypto.PublicKey) ([]byte, error) {
92116

93117
// makeKey creates a new 2048 bit RSA private key and a Subject Key Identifier
94118
func makeKey() (*rsa.PrivateKey, []byte, error) {
95-
key, err := rsa.GenerateKey(rand.Reader, 2048)
119+
key, err := rsa.GenerateKey(crand.Reader, 2048)
96120
if err != nil {
97121
return nil, nil, err
98122
}
@@ -133,7 +157,7 @@ func (ca *CAImpl) makeRootCert(
133157
parent = template
134158
}
135159

136-
der, err := x509.CreateCertificate(rand.Reader, template, parent, subjectKey.Public(), signerKey)
160+
der, err := x509.CreateCertificate(crand.Reader, template, parent, subjectKey.Public(), signerKey)
137161
if err != nil {
138162
return nil, err
139163
}
@@ -307,7 +331,7 @@ func (ca *CAImpl) newCertificate(domains []string, ips []net.IP, key crypto.Publ
307331
template.OCSPServer = []string{ca.ocspResponderURL}
308332
}
309333

310-
der, err := x509.CreateCertificate(rand.Reader, template, issuer.cert.Cert, key, issuer.key)
334+
der, err := x509.CreateCertificate(crand.Reader, template, issuer.cert.Cert, key, issuer.key)
311335
if err != nil {
312336
return nil, err
313337
}
@@ -342,8 +366,26 @@ func (ca *CAImpl) newCertificate(domains []string, ips []net.IP, key crypto.Publ
342366

343367
func New(log *log.Logger, db *db.MemoryStore, ocspResponderURL string, alternateRoots int, chainLength int) *CAImpl {
344368
ca := &CAImpl{
345-
log: log,
346-
db: db,
369+
log: log,
370+
db: db,
371+
sleep: true,
372+
sleepTime: defaultSleepTime,
373+
}
374+
375+
// Read the PEBBLE_CA_NOSLEEP environment variable string
376+
noSleep := os.Getenv(noSleepEnvVar)
377+
// If it is set to something true-like, then the CA shouldn't sleep
378+
switch strings.ToLower(noSleep) {
379+
case "1", "true":
380+
ca.sleep = false
381+
ca.log.Printf("Disabling random CA sleeps")
382+
}
383+
384+
sleepTime := os.Getenv(sleepTimeEnvVar)
385+
sleepTimeInt, err := strconv.Atoi(sleepTime)
386+
if err == nil && ca.sleep && sleepTimeInt >= 1 {
387+
ca.sleepTime = sleepTimeInt
388+
ca.log.Printf("Setting maximum random CA sleep time to %d seconds", ca.sleepTime)
347389
}
348390

349391
if ocspResponderURL != "" {
@@ -398,6 +440,12 @@ func (ca *CAImpl) CompleteOrder(order *core.Order) {
398440
}
399441
ca.log.Printf("Issued certificate serial %s for order %s\n", cert.ID, order.ID)
400442

443+
if ca.sleep {
444+
sleepLen := time.Duration(mrand.Intn(ca.sleepTime))
445+
ca.log.Printf("Sleeping for %s seconds before marking complete", time.Second*sleepLen)
446+
time.Sleep(time.Second * sleepLen)
447+
}
448+
401449
// Lock and update the order to store the issued certificate
402450
order.Lock()
403451
order.CertificateObject = cert

va/va.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/miekg/dns"
2525

2626
"github.com/letsencrypt/challtestsrv"
27+
2728
"github.com/letsencrypt/pebble/acme"
2829
"github.com/letsencrypt/pebble/core"
2930
)
@@ -132,8 +133,8 @@ func New(
132133
// Read the PEBBLE_VA_NOSLEEP environment variable string
133134
noSleep := os.Getenv(noSleepEnvVar)
134135
// If it is set to something true-like, then the VA shouldn't sleep
135-
switch noSleep {
136-
case "1", "true", "True", "TRUE":
136+
switch strings.ToLower(noSleep) {
137+
case "1", "true":
137138
va.sleep = false
138139
va.log.Printf("Disabling random VA sleeps")
139140
}
@@ -251,6 +252,13 @@ func (va VAImpl) process(task *vaTask) {
251252
}
252253

253254
err := va.firstError(results)
255+
256+
if va.sleep {
257+
sleepLen := time.Duration(rand.Intn(va.sleepTime))
258+
va.log.Printf("Sleeping for %s seconds before marking valid", time.Second*sleepLen)
259+
time.Sleep(time.Second * sleepLen)
260+
}
261+
254262
// If one of the results was an error, the challenge fails
255263
if err != nil {
256264
va.setAuthzInvalid(authz, chal, err)
@@ -268,9 +276,9 @@ func (va VAImpl) process(task *vaTask) {
268276
func (va VAImpl) performValidation(task *vaTask, results chan<- *core.ValidationRecord) {
269277
if va.sleep {
270278
// Sleep for a random amount of time between 0 and va.sleepTime seconds
271-
len := time.Duration(rand.Intn(va.sleepTime))
272-
va.log.Printf("Sleeping for %s seconds before validating", time.Second*len)
273-
time.Sleep(time.Second * len)
279+
sleepLen := time.Duration(rand.Intn(va.sleepTime))
280+
va.log.Printf("Sleeping for %s seconds before attempting validation", time.Second*sleepLen)
281+
time.Sleep(time.Second * sleepLen)
274282
}
275283

276284
// If `alwaysValid` is true then return a validation record immediately

0 commit comments

Comments
 (0)