Siyao Meng created HDDS-16915:
---------------------------------

             Summary: SCM follower that installs a checkpoint taken after a 
root CA rotation commit stays on the old root certificate
                 Key: HDDS-16915
                 URL: https://issues.apache.org/jira/browse/HDDS-16915
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
MC-6-complete-prepared-rotation-after-checkpoint-install.patch, 
TestBugMC6CheckpointSkipsRotation.java

h3. Mechanism
{{RootCARotationHandlerImpl.rotationCommit}} and {{rotationCommitted}} are 
replicated requests, but what they change is local to each SCM: they move 
{{scm/sub-ca-next}} to {{scm/sub-ca}}, write the new certificate ID to the 
VERSION file and reload the certificate client. None of this is in the SCM DB. 
When a follower catches up by installing a checkpoint, 
{{SCMHAManagerImpl.installCheckpoint}} replaces the DB and reloads the DB 
backed managers. The applied index moves past the two requests and the follower 
never applies them. It keeps its sub CA certificate under the old root, and the 
certificate it prepared for the new root stays in {{scm/sub-ca-next}}.

h3. Trigger
# Secure SCM HA with {{hdds.x509.ca.rotation.enabled=true}} and 
{{ozone.scm.ha.ratis.log.purge.enabled=true}} (both false by default).
# A root CA rotation starts. Every SCM prepares a new sub CA certificate and 
acks.
# One follower stops receiving from the leader before the {{rotationCommit}} 
request reaches it.
# The other SCMs commit the rotation. The leader takes a snapshot and purges 
its log past the rotation requests.
# The follower is reachable again and installs the checkpoint.

The follower has to drop out between its ack and the commit and stay away until 
the leader has purged. By code reading, with the default segment size (64MB) 
and purge gap (1000000) that needs a long outage. Without log purging the 
leader keeps the requests and the follower applies them from the log (code 
reading, not run).

h3. Impact
Observed on a mini cluster, where Ratis runs without TLS. Wherever the follower 
becomes leader below, the test moved leadership to it: with the admin command 
after the polling interval, and with a Ratis leadership transfer inside it, 
because the admin command is refused there.
* The follower keeps following the log, with its replicated state past the 
rotation and its certificates on the old root. Until it is restarted it trusts 
only the old root.
* A restart does not repair it. After one restart it also trusts the new root. 
After a second restart {{SCMCertificateClient.getCACertificate}} returns the 
new root, while its own certificate chain, the VERSION file and {{scm/sub-ca}} 
are still on the old one.
* If it becomes leader inside {{hdds.x509.rootca.certificate.polling.interval}} 
after the rotation (run after one restart), it terminates at leader ready with 
"Root CA certificate ID 7 in stateful storage is bigger than current scm's root 
CA certificate ID 1". Another SCM took over 10s later.
* If it becomes leader later (run without a restart; after one restart by code 
reading, because {{getCACertificate}} still returns the old root then), 
{{RootCARotationManager.MonitorTask}} sees the old root inside its grace period 
and starts another rotation. All SCMs end on a third root and the root that was 
just introduced is replaced. This is the only way the SCM recovers. The 
rotation started at once in the test. By code reading it is scheduled for the 
next {{hdds.x509.ca.rotation.time-of-day}} when the old root is still valid at 
that time.
* If it becomes leader later and was restarted twice (run), no rotation starts. 
It stays leader and signs with the sub CA certificate under the old root.

Argued, not run: the sub CA certificate of the follower expires with the old 
root, at most twice {{hdds.x509.renew.grace.duration}} after the rotation, and 
nothing but a rotation replaces an SCM sub CA certificate. With 
{{hdds.grpc.tls.enabled=true}}, Ratis between SCMs and the checkpoint download 
({{InterSCMGrpcClient}}, {{InterSCMGrpcProtocolService}}) both use the trust 
manager of the certificate client and require a client certificate. A follower 
that was cut off and still trusts only the old root can then neither accept 
requests from the leader nor download the checkpoint, so step 5 of the trigger 
needs a restart of the follower first, which makes it fetch the new root at 
startup. That order was not run, with or without the patch.

By code reading, not run: the release tags ozone-1.4.0 to ozone-2.2.1 carry the 
same {{rotationCommit}} and the same checkpoint install.

h3. Reproduction
PASS, unmodified source. [^TestBugMC6CheckpointSkipsRotation.java] 
({{hadoop-ozone/integration-test}}) starts a secure mini cluster with three 
SCMs. Certificate lifetimes are shortened so that the leader rotates the root 
CA three minutes after SCM init, and log purging is enabled with a small 
segment size, purge gap and snapshot threshold. The only intervention is a 
partition: the Ratis test hooks of {{RaftServerImpl.appendEntries}} and 
{{RaftServerImpl.installSnapshot}} reject what is sent to the first follower 
that is offered the {{rotationCommit}} entry, until the leader has purged that 
entry. After the follower has installed the checkpoint, the new root is in its 
certificate store while its root certificate and VERSION file are the old ones 
and {{scm/sub-ca-next}} is still present. A passing test means the defect is 
present. It takes about three minutes.

h3. Patch
[^MC-6-complete-prepared-rotation-after-checkpoint-install.patch], against 
b9b33851b20fa11baf4b07982de9afb8ea44f85a. It also applies to master at 
5108acff480.

After a checkpoint install, {{RootCARotationManager.completePreparedRotation}} 
looks at the certificate prepared in {{scm/sub-ca-next}}. If its root is newer 
than the current one and is present in the certificate store, the rotation to 
it is committed, because the leader stores a new root there only after 
{{rotationCommitted}}. The SCM then runs the commit steps locally, through a 
{{RootCARotationHandlerImpl}} that is not wrapped in the Ratis proxy. Otherwise 
nothing changes.

A failure while reading the prepared certificate or deciding is logged and 
leaves the SCM as it is today. A failed commit step is not caught, so the SCM 
never keeps running with its directories half switched. At this commit the 
handler stops the SCM itself, as it does for a commit applied from the log 
(run: no further step is executed and the directories are untouched; the stop 
does not finish when it is called on this thread, which is the existing 
behaviour of the handler, HDDS-16911, and not changed here). An exception from 
the handler fails {{installCheckpoint}} and {{SCMStateMachine.reinitialize}}, 
Ratis closes the server and the SCM terminates (run with the handler changed to 
throw).

Covered by {{testCompletePreparedRotation}} and 
{{testCompletePreparedRotationThrowsFailedCommitStep}} in the existing 
{{TestRootCARotationManager}}, for a prepared rotation that is not committed, 
for one that is, and for a commit step that fails. The call from 
{{installCheckpoint}} is exercised by the attached reproduction only, because 
no existing suite combines a secure SCM HA cluster with a root CA rotation. The 
reproduction with its last three assertions inverted can serve as an 
integration test if one is wanted. With the patch the SCM HA and root CA 
rotation unit suites of {{hadoop-hdds/server-scm}} (114 tests), 
{{TestSCMInstallSnapshot}}, {{TestSCMInstallSnapshotWithHA}} and 
{{TestSecretKeySnapshot}} pass and checkstyle is clean. The reproduction then 
fails at the assertion that expects the old root: the follower logs "Rotation 
to root certificate 7 is committed, switching to the prepared sub CA 
certificate 10" during the install and ends on the new root with 
{{scm/sub-ca-next}} gone. In a further run it became leader inside the polling 
interval and kept running without starting a rotation.

Not covered by the patch: an SCM that is already in this state is not repaired 
at startup. Nothing happens when the new root is not yet, or never, in the 
certificate store of the installed checkpoint, that is a checkpoint taken 
before the leader has stored it, or a leader that failed before storing it. A 
follower that applied {{rotationCommit}} but not {{rotationCommitted}} before 
it was cut off is not completed either.

Found by TLA+ model checking and code review of the SCM root CA rotation paths 
under HDDS-15926, on commit b9b33851b20fa11baf4b07982de9afb8ea44f85a. Checked 
against HDDS issues and apache/ozone pull requests for duplicates before 
filing. The attached patch is a proposal for review. Generated with Specula 
(Claude Opus 5.5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to