Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set random percentages through admin wfe at runtime #313

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,21 @@ The endpoint returns the information as a JSON object:
"Status": "Revoked"
}

#### Updates to configuration at runtime

A couple of Pebble's configurables can be modified after Pebble has started by sending
a `PATCH` request to `https://localhost:15000/runtimeConfig` whose request body is a
json object with one or more of the following properties:

| Property | Type | Analogous Env Variable |
|-------------------|----------------|-------------------------
| AuthzReusePercent | integer, 0-100 | PEBBLE_AUTHZREUSE |
| NonceErrPercent | integer, 0-100 | PEBBLE_WFE_NONCEREJECT |

The endpoint returns HTTP 202 Accepted if the request was successful.

Changing these percentages can simplify creating test suites where certain tests exercise
authorization reuse or nonce error handling.

### OCSP Responder URL

Expand Down
58 changes: 49 additions & 9 deletions wfe/wfe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math/big"
Expand All @@ -23,6 +24,7 @@ import (
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"unicode"

Expand Down Expand Up @@ -61,6 +63,7 @@ const (
intermediateCertPath = "/intermediates/"
intermediateKeyPath = "/intermediate-keys/"
certStatusBySerial = "/cert-status-by-serial/"
runtimeConfig = "/runtimeConfig"

// How long do pending authorizations last before expiring?
pendingAuthzExpire = time.Hour
Expand Down Expand Up @@ -104,7 +107,7 @@ const (

// The default value when PEBBLE_WFE_AUTHZREUSE is not set, how often to try
// and reuse valid authorizations.
defaultAuthzReuse = 50
defaultAuthzReuse = int32(50)

// ordersPerPageEnvVar defines the environment variable name used to provide
// the number of orders to show per page. To have the WFE show 15 orders per
Expand Down Expand Up @@ -148,8 +151,8 @@ type WebFrontEndImpl struct {
log *log.Logger
db *db.MemoryStore
nonce *nonceMap
nonceErrPercent int
authzReusePercent int
nonceErrPercent int32
authzReusePercent int32
ordersPerPage int
va *va.VAImpl
ca *ca.CAImpl
Expand All @@ -168,12 +171,12 @@ func New(
// Read the % of good nonces that should be rejected as bad nonces from the
// environment
nonceErrPercentVal := os.Getenv(badNonceEnvVar)
var nonceErrPercent int
var nonceErrPercent int32

// Parse the env var value as a base 10 int - if there isn't an error, use it
// as the wfe nonceErrPercent
if val, err := strconv.ParseInt(nonceErrPercentVal, 10, 0); err == nil {
nonceErrPercent = int(val)
nonceErrPercent = int32(val)
} else {
// Otherwise just use the default
nonceErrPercent = defaultNonceReject
Expand All @@ -191,7 +194,7 @@ func New(
authzReusePercent := defaultAuthzReuse
if val, err := strconv.ParseInt(os.Getenv(authzReuseEnvVar), 10, 0); err == nil &&
val >= 0 && val <= 100 {
authzReusePercent = int(val)
authzReusePercent = int32(val)
}
log.Printf("Configured to attempt authz reuse for each identifier %d%% of the time",
authzReusePercent)
Expand Down Expand Up @@ -484,6 +487,42 @@ func (wfe *WebFrontEndImpl) handleCertStatusBySerial(
}
}

func (wfe *WebFrontEndImpl) updateRuntimeConfig(
ctx context.Context,
response http.ResponseWriter,
request *http.Request) {

type requestSchema struct {
AuthzReusePercent *uint8 `json:",omitempty"`
NonceErrPercent *uint8 `json:",omitempty"`
}

if request.Body == nil {
response.WriteHeader(http.StatusBadRequest)
return
}
defer request.Body.Close()

var maxBodyBytes int64 = 2048
d := json.NewDecoder(io.LimitReader(request.Body, maxBodyBytes))
requestObj := &requestSchema{}
err := d.Decode(requestObj)
if err != nil {
response.WriteHeader(http.StatusBadRequest)
_, _ = response.Write([]byte(err.Error()))
return
}

if requestObj.AuthzReusePercent != nil {
atomic.StoreInt32(&wfe.authzReusePercent, int32(*requestObj.AuthzReusePercent))
}
if requestObj.NonceErrPercent != nil {
atomic.StoreInt32(&wfe.nonceErrPercent, int32(*requestObj.NonceErrPercent))
}

response.WriteHeader(http.StatusAccepted)
}

func (wfe *WebFrontEndImpl) Handler() http.Handler {
m := http.NewServeMux()
// GET & POST handlers
Expand Down Expand Up @@ -517,6 +556,7 @@ func (wfe *WebFrontEndImpl) ManagementHandler() http.Handler {
wfe.HandleManagementFunc(m, intermediateCertPath, wfe.handleCert(wfe.ca.GetIntermediateCert, intermediateCertPath))
wfe.HandleManagementFunc(m, intermediateKeyPath, wfe.handleKey(wfe.ca.GetIntermediateKey, intermediateKeyPath))
wfe.HandleManagementFunc(m, certStatusBySerial, wfe.handleCertStatusBySerial)
wfe.HandleManagementFunc(m, runtimeConfig, wfe.updateRuntimeConfig)
return m
}

Expand Down Expand Up @@ -889,10 +929,10 @@ func (wfe *WebFrontEndImpl) verifyJWS(
}

// Roll a random number between 0 and 100.
nonceRoll := rand.Intn(100)
nonceRoll := rand.Int31n(100)
// If the nonce is not valid OR if the nonceRoll was less than the
// nonceErrPercent, fail with an error
if !wfe.nonce.validNonce(nonce) || nonceRoll < wfe.nonceErrPercent {
if !wfe.nonce.validNonce(nonce) || nonceRoll < atomic.LoadInt32(&wfe.nonceErrPercent) {
return nil, acme.BadNonceProblem(fmt.Sprintf(
"JWS has an invalid anti-replay nonce: %s", nonce))
}
Expand Down Expand Up @@ -1486,7 +1526,7 @@ func (wfe *WebFrontEndImpl) makeAuthorizations(order *core.Order, request *http.
// If there is an existing valid authz for this identifier, we can reuse it
authz := wfe.db.FindValidAuthorization(order.AccountID, ident)
// Otherwise create a new pending authz (and randomly not)
if authz == nil || rand.Intn(100) > wfe.authzReusePercent {
if authz == nil || rand.Int31n(100) > atomic.LoadInt32(&wfe.authzReusePercent) {
authz = &core.Authorization{
ID: newToken(),
ExpiresDate: expires,
Expand Down