Siyao Meng created HDDS-16959:
---------------------------------

             Summary: OM drops persisted delegation tokens from memory when the 
secret key fetch from SCM fails during token load
                 Key: HDDS-16959
                 URL: https://issues.apache.org/jira/browse/HDDS-16959
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-4-keep-persisted-delegation-tokens-when-secret-key-fetch-fails.patch, 
TestBugCR4OmRestartDuringScmKeyInit.java

h3. Mechanism
The OM DB stores only the identifier and the renew date of a delegation token. 
When {{OzoneDelegationTokenSecretManager}} is created, {{loadTokenSecretState}} 
calls {{addPersistedDelegationToken}} for every stored token, which fetches the 
token's secret key from SCM with {{SecretKeyClient.getSecretKey}} and 
recomputes the password. {{loadTokenSecretState}} catches every exception from 
that call, logs "exception while loading delegation token from DB... ignored to 
continue startup" and goes on to the next token.

HDDS-13234 added that catch for a secret key that SCM has already removed, in 
which case {{getSecretKey}} returns null and the token can never be verified 
again. A fetch that fails ({{SCMSecurityException}} "Error fetching secret key 
...") ends in the same catch. Before HDDS-13234 the same failure aborted the OM 
start. That change is in 2.1.0 and later releases (its fix versions are 2.0.1 
and 2.1.0). The token is then missing from {{currentTokens}} although it is 
unexpired, still in the DB, and its key still exists on SCM. Nothing loads it 
later, so the OM rejects it until the OM process is restarted.

Two things make the failed fetch reachable during a normal start:
* The token load runs in the {{OzoneManager}} constructor 
({{instantiateServices}}), before {{startSecretManager}} starts the signer 
client. Only the signer client's initial fetch waits for SCM to leave 
{{SECRET_KEY_NOT_INITIALIZED}}.
* {{SecretKeyProtocolClientSideTranslatorPB.handleError}} turns the error 
status into an exception after the RPC has returned, so the SCM failover retry 
policy does not retry it.

h3. Trigger
# Secure cluster, at least one delegation token issued.
# An SCM is restarted and becomes leader. Until {{SecretKeyManagerService}} has 
run {{SecretKeyManager.checkAndInitialize}} on it, the secret key RPC answers 
{{SECRET_KEY_NOT_INITIALIZED}}.
# An OM starts in that window. The window is normally short: SCM initializes 
the keys as soon as it is leader ready, and if that attempt fails the next one 
comes with the next rotation check ({{hdds.secret.key.rotate.check.duration}}, 
default 10m). Its natural width was not measured.
# The token is renewed or used for authentication on that OM.

Any other failure of the fetch at load time has the same effect, for example 
SCM not reachable within the client retries (same catch, not run). 
{{OzoneManager.reloadOMState}} (Ratis snapshot installation on a follower) and 
{{OzoneManager.restart}} build the secret manager the same way, so they are 
exposed too (read from the code, not run).

h3. Impact
* Every token loaded while the fetch fails is rejected by that OM: renewal 
fails with "Delegation token renewal failed" and the OM logs "token ... can't 
be found in cache", {{retrievePassword}} throws {{InvalidToken}} with the same 
message, so token authentication fails. Jobs that hold delegation tokens fail 
although their tokens are valid.
* The state lasts until that OM is restarted while SCM can serve the key. The 
only trace at load time is one ERROR line per token.
* In OM HA every OM loads its tokens on its own, and tokens are only checked on 
the leader. An OM that lost its tokens as a follower shows it when it becomes 
leader (read from the code, the reproduction uses one OM).

h3. Reproduction
PASS in about one minute, unmodified source. 
[^TestBugCR4OmRestartDuringScmKeyInit.java] ({{hadoop-ozone/integration-test}}) 
runs a Kerberos mini cluster with three SCMs and one OM. A client gets a 
delegation token, a follower SCM is restarted, leadership is transferred to it 
and the OM is restarted before that SCM has initialized its secret keys. The 
test holds the monitor of the new leader's {{SecretKeyManager}} during the OM 
restart, which only delays the synchronized {{checkAndInitialize}} so that the 
window is hit on every run. Afterwards the token is unexpired, present in the 
OM DB and its key is served by SCM, but {{renewDelegationToken}} fails and 
{{retrievePassword}} throws {{InvalidToken}}. After a second OM restart the 
same token renews. A passing test means the defect is present.

h3. Patch
[^CR-4-keep-persisted-delegation-tokens-when-secret-key-fetch-fails.patch], 
against 025266b52a6dcaf39508607cad736799c40907e8. It also applies to master at 
a65a32e7c4b.

{{addPersistedDelegationToken}} now tells the two cases apart. If the fetch 
throws {{SCMSecurityException}}, the token is kept in {{currentTokens}} as an 
entry of its own type ({{TokenInfoWithoutPassword}}) and one WARN line without 
a stack trace is logged. {{validateToken}} computes the password of such an 
entry from its secret key when the token is used, and throws {{InvalidToken}} 
if the key still cannot be fetched or SCM reports it as gone. Only the load 
creates such an entry, and every later update of the token replaces it with a 
regular one. The value {{validateToken}} computes is the one the existing 
signature check requires the stored password to have, so the accepted tokens 
are the same as after a successful load. The HDDS-13234 behaviour for a key 
that SCM no longer has (null result) is unchanged, and OM startup does not wait 
for SCM any longer than today.

Retrying the fetch during the load was considered. It would hold the OM 
constructor (and a snapshot installation) for as long as SCM does not answer, 
and would still need a decision on what to do when the retries run out.

Covered by {{testReloadTokenWhenSecretKeyFetchFails}} in the existing 
{{TestOzoneDelegationTokenSecretManager}}: the fetch fails during the reload 
and on first use, then SCM reports the key as missing, then serves it. Without 
the change the test fails because the token "can't be found in cache". With the 
change {{TestOzoneDelegationTokenSecretManager}} (17 tests, including 
{{testExpiredSecretKey}} from HDDS-13234), the token identifier, delegation 
token request and response and double buffer unit tests (19 tests), 
{{TestDelegationToken}} (2 tests), {{TestSecureOzoneCluster}} (9 tests) and 
{{TestSecretKeySnapshot}} pass and checkstyle is clean. With the patch the 
reproduction fails at the assertion that expects the renewal to fail, because 
the renewal succeeds.

Found by code review of the SCM secret key rotation and distribution paths, as 
part of the TLA+ verification effort under HDDS-15926, on commit 
025266b52a6dcaf39508607cad736799c40907e8. 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