Siyao Meng created HDDS-16917:
---------------------------------

             Summary: SCM that crashes while it applies a root CA rotation 
commit cannot start again, the sub CA directory switch is not completed on start
                 Key: HDDS-16917
                 URL: https://issues.apache.org/jira/browse/HDDS-16917
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
MC-4-complete-interrupted-rotation-commit-on-scm-start.patch, 
TestBugMC4CrashBetweenCommitMoves.java

h3. Mechanism
{{RootCARotationHandlerImpl.rotationCommit}} switches to the new sub CA 
certificate in three separate steps: move {{scm/sub-ca}} to 
{{scm/sub-ca-previous}}, move {{scm/sub-ca-next}} to {{scm/sub-ca}}, write the 
new certificate ID to the VERSION file. SCM start reads only the VERSION file 
and {{scm/sub-ca}} and has no step that completes or reverts a switch that 
stopped in between.

* Stopped after the first move: {{scm/sub-ca}} does not exist. 
{{DefaultCAServer.init}} for the sub CA throws {{IllegalStateException: 
INTERMEDIARY_CA Should not be in Initialize State during startup.}} in the 
{{StorageContainerManager}} constructor, on the primary SCM and on any other 
SCM.
* Stopped after the second move: {{scm/sub-ca}} holds the new certificate, the 
VERSION file names the old one. {{SCMCertificateClient.getCertificate}} returns 
null. On the primary SCM the constructor fails with a {{NullPointerException}} 
in {{persistPrimarySCMCerts}}. On any other SCM the constructor returns without 
a certificate, and {{RootCARotationManager.shouldSkipRootCert}}, the first call 
of the commit when it is applied again, throws a {{NullPointerException}} on 
the missing trust chain. Inside {{SCMStateMachine.applyTransaction}} that 
terminates the SCM (the replay itself is argued, not run).

The commit can never be applied again to finish the switch, because the SCM 
fails before or while it replays the RAFT log.

The same three steps are in the release tags ozone-1.4.0, 1.4.1, 2.0.0, 2.1.0, 
2.1.1, 2.1.2, 2.2.0 and 2.2.1, and SCM start does not look at 
{{scm/sub-ca-previous}} in any of them (read with git grep, not run).

h3. Trigger
Secure SCM HA with {{hdds.x509.ca.rotation.enabled=true}} (default false). An 
SCM process dies (kill, OOM, power loss) between the first directory move and 
the VERSION write of {{rotationCommit}}. The window is two renames and one 
small file write, once per rotation and SCM. With the default 
{{hdds.x509.max.duration}} of P1865D a rotation runs once in about five years.

The second state is also reached without a process death in that window. An SCM 
that is restarted between its prepare ack and the commit terminates after both 
moves and before the VERSION write (HDDS-16913). An {{IOException}} from the 
VERSION write makes the handler call {{scm.shutDown}} on the thread that 
applies the log entry, which leaves the SCM hung until it is killed 
(HDDS-16911), and the next start then finds the second state (argued, not run).

h3. Impact
The SCM fails on every later start until an operator moves the directories or 
edits the VERSION file by hand. No key or certificate is lost: the old pair is 
in {{scm/sub-ca-previous}} and the new pair in {{scm/sub-ca-next}} or 
{{scm/sub-ca}}. {{ozone repair scm cert recover}} does not handle these 
directories.

If the SCM was a follower, the other SCMs finish the rotation and the ring runs 
with one member less. If it was the leader, the other SCMs still apply the 
commit, but the committed request, which only the leader sends and only right 
after the commit, is not sent (read, not run). The rotation is then also left 
unfinished on the other SCMs, see HDDS-16912.

h3. Reproduction
PASS, deterministic, unmodified source. 
[^TestBugMC4CrashBetweenCommitMoves.java] ({{hadoop-ozone/integration-test}}) 
runs {{scm --init}} of a secure SCM with a MiniKdc, stages a new sub CA 
certificate with the real certificate client and a new root CA (the leader that 
signs the request is a mock backed by that CA), performs the directory moves of 
{{rotationCommit}} up to the crash point and then constructs the 
{{StorageContainerManager}} again. Both crash points fail as described above. A 
passing test means the defect is present.

The test SCM is the primary. For an SCM that is not the primary the same states 
were given to the real constructor after the primary SCM ID in the VERSION file 
was changed (run, not part of the attached reproduction): after the first move 
the constructor throws the same {{IllegalStateException}}, after the second 
move it returns with {{getCertificate()}} null and {{shouldSkipRootCert}} 
throws the {{NullPointerException}}.

h3. Patch
[^MC-4-complete-interrupted-rotation-commit-on-scm-start.patch], against 
b9b33851b20fa11baf4b07982de9afb8ea44f85a. It also applies to master at 
5108acff480. The patch is made against that commit alone, other fixes proposed 
for the same code may need a manual merge.

Before the SCM certificate client is created, 
{{StorageContainerManager.initializeCertificateClient}} calls the new 
{{RootCARotationManager.completeInterruptedCommit}}. It acts only when the 
certificate named in the VERSION file is found in {{scm/sub-ca-previous}} and 
is not the one in {{scm/sub-ca}}, which only an unfinished commit leaves 
behind. It then moves {{scm/sub-ca-next}} to {{scm/sub-ca}} if that has not 
happened, writes the ID of the certificate in {{scm/sub-ca}} to the VERSION 
file and deletes {{scm/sub-ca-previous}}. The switch is completed and not 
reverted because the commit entry is already committed in the RAFT log. When 
the commit and committed entries are applied again they are skipped, since the 
SCM already trusts the new root certificate. If {{scm/sub-ca}} is missing and 
nothing is staged in {{scm/sub-ca-next}} the directories are left as they are 
and the start fails as before (run). A certificate that is staged but was never 
committed is not touched.

Covered by four cases of {{testScmStartCompletesInterruptedRotationCommit}} in 
the existing {{TestSecureOzoneCluster}}: the two crash points, a certificate 
that is staged and not committed, and a {{scm/sub-ca-previous}} that is a copy 
of the directory in use. In the last two the SCM must start on its old 
certificate and leave the directories alone. Without the change the two crash 
point cases fail with the two exceptions above. With it the new test, 
{{testSecureScmStartupFailure}} of the same class, 
{{TestRootCARotationManager}} and {{TestScmInvokerCodeGenerator}} pass (18 
tests) and checkstyle is clean. The other tests of {{TestSecureOzoneCluster}} 
were not run. With the patch both cases of the reproduction fail, because the 
SCM starts.

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