Skip to content

ra/sa: Prevent parallel validation attempts#8838

Open
aarongable wants to merge 1 commit into
mainfrom
validation-processing
Open

ra/sa: Prevent parallel validation attempts#8838
aarongable wants to merge 1 commit into
mainfrom
validation-processing

Conversation

@aarongable

@aarongable aarongable commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Add a new beganProcessing column to the authz2 table, which is a replica of the column of the same name in the orders table. Similarly add a new SA gRPC method which sets this column to true, or returns an error if it already has been set to true. The update is done in a transaction to ensure that multiple attempts to update the field don't all succeed. The error is specifically "conflict", so that the WFE will render it with HTTP status code 409.

In the RA, call the new SA method before kicking off validation. If it returns an error, bail out. Lock this behavior behind a feature flag to ensure it is not enabled before the necessary database changes have been made.

Add an integration test which kicks off two validations simultaneously and ensures that exactly one of them fails with reason "conflict".

Fixes #3036

IN-12842 tracks the corresponding SRE-side database and config changes

CPS Compliance Review: Neither the BRs nor our CP/CPS have anything to say about attempting validation multiple times. We will be in compliance both before and after this feature flag is enabled.

@aarongable
aarongable marked this pull request as ready for review July 3, 2026 00:26
@aarongable
aarongable requested a review from a team as a code owner July 3, 2026 00:26
@aarongable
aarongable requested a review from ezekiel July 3, 2026 00:26
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@aarongable, this PR adds one or more new feature flags: SetAuthzProcessing. As such, this PR must be accompanied by a review of the Let's Encrypt CP/CPS to ensure that our behavior both before and after this flag is flipped is compliant with that document.

Please conduct such a review, then add your findings to the PR description in a paragraph beginning with "CPS Compliance Review:".

@ezekiel
ezekiel requested review from a team and jsha and removed request for a team July 6, 2026 15:12
@beautifulentropy beautifulentropy changed the title Prevent parallel validation attempts ra/sa: Prevent parallel validation attempts Jul 8, 2026
@beautifulentropy
beautifulentropy self-requested a review July 14, 2026 21:29

@jsha jsha left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Notes:

This will increase database load, I think significantly. Every authz that we validate will now generate two writes to the authz2 table instead of 1. We can try it out and see if that's too much, but definitely something to keep an eye on.

This will change our metrics for the /acme/authz endpoint: we'll have fewer 200's and more 409s. We should make sure to note that in the SRE ticket enabling the flag.

For the question of whether to use the beganProcessing idiom (like orders), I wanted to double check why we did it that way in orders. Turns out it was because we decided to synthesize order status on read based on the authz statuses and expires information. There's no "status" field in the order model, but we did need to store the processing bit.

For authzs the story is different: we could set a "processing" status (rewriting it at render time since RFC 8555 doesn't have "processing" for authzs). But there's a different reason to like beganProcessing: since we have an index that includes status, and it's nice to not spend extra writes updating the index.

  KEY `regID_identifier_status_expires_idx` (`registrationID`,`identifierType`,`identifierValue`,`status`,`expires`),

@beautifulentropy beautifulentropy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just some stale comments/log lines, but also a question about doing this work inside of a transaction. Thanks for your work on this!

Comment thread sa/sa.go
if req.Id == 0 {
return nil, errIncompleteRequest
}
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a transaction here. We're performing just one update query, which I should be atomic on its own. The transaction itself adds two round trips to that. I notice that we also do the same in *SQLStorageAuthority.SetOrderProcessing(), so perhaps there's a quality that a transaction confers upon this query that I just don't understand.

Comment thread ra/ra.go
Comment on lines 1511 to 1514
// We log NotFound at a lower level because this is largely due to a
// parallel-validation race: a different validation attempt has already
// updated this authz, so we failed to find a *pending* authz with the
// given ID to update.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a post-SetAuthzProcessing world this comment will become stale. Arguably it's already stale as of this PR. We should consider updating it now or at least leaving a TODO to clean it up. FWIW, in the future, I think the only way we could still end up here is the client concurrently deactivating the authz while in the process of validating.

Comment thread ra/ra.go
// parallel-validation race: a different validation attempt has already
// updated this authz, so we failed to find a *pending* authz with the
// given ID to update.
ra.log.Info(ctx, "Failed to record validation (likely parallel validation race)",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing the same train of thought as above, how about:

Suggested change
ra.log.Info(ctx, "Failed to record validation (authz no longer pending)",

Comment thread ra/ra.go
blog.Error(err),
)
} else {
ra.log.AuditError(ctx, "Failed to record validation (likely parallel validation race)", err,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling to see how we'd get here from a parallel validation race. The race should always resolve as NotFound. Here we should only see uncaught database errors, timeouts, etc.

Suggested change
ra.log.AuditError(ctx, "Failed to record validation", err,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prevent parallel validations

4 participants