Skip to content

Commit 4a485dd

Browse files
authored
KAFKA-17315 Fix the behavior of delegation tokens that expire immediately upon creation in KRaft mode (apache#16858)
In kraft mode, expiring delegation token (`expiryTimePeriodMs` < 0) has following different behavior to zk mode. 1. `ExpiryTimestampMs` is set to "expiryTimePeriodMs" [0] rather than "now" [1] 2. it throws exception directly if the token is expired already [2]. By contrast, zk mode does not. [3] [0] https://github.com/apache/kafka/blob/49fc14f6116a697550339a8804177bd9290d15db/metadata/src/main/java/org/apache/kafka/controller/DelegationTokenControlManager.java#L316 [1] https://github.com/apache/kafka/blob/49fc14f6116a697550339a8804177bd9290d15db/core/src/main/scala/kafka/server/DelegationTokenManagerZk.scala#L292 [2] https://github.com/apache/kafka/blob/49fc14f6116a697550339a8804177bd9290d15db/metadata/src/main/java/org/apache/kafka/controller/DelegationTokenControlManager.java#L305 [3] https://github.com/apache/kafka/blob/49fc14f6116a697550339a8804177bd9290d15db/core/src/main/scala/kafka/server/DelegationTokenManagerZk.scala#L293 Reviewers: Chia-Ping Tsai <[email protected]>
1 parent e750f44 commit 4a485dd

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

core/src/test/scala/integration/kafka/api/SaslSslAdminIntegrationTest.scala

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ import org.apache.kafka.common.acl._
2222
import org.apache.kafka.common.acl.AclOperation.{ALL, ALTER, ALTER_CONFIGS, CLUSTER_ACTION, CREATE, DELETE, DESCRIBE, IDEMPOTENT_WRITE}
2323
import org.apache.kafka.common.acl.AclPermissionType.{ALLOW, DENY}
2424
import org.apache.kafka.common.config.{ConfigResource, SaslConfigs, TopicConfig}
25-
import org.apache.kafka.common.errors.{ClusterAuthorizationException, InvalidRequestException, TopicAuthorizationException, UnknownTopicOrPartitionException}
25+
import org.apache.kafka.common.errors.{ClusterAuthorizationException, DelegationTokenExpiredException, DelegationTokenNotFoundException, InvalidRequestException, TopicAuthorizationException, UnknownTopicOrPartitionException}
2626
import org.apache.kafka.common.resource.PatternType.LITERAL
2727
import org.apache.kafka.common.resource.ResourceType.{GROUP, TOPIC}
2828
import org.apache.kafka.common.resource.{PatternType, Resource, ResourcePattern, ResourcePatternFilter, ResourceType}
2929
import org.apache.kafka.common.security.auth.{KafkaPrincipal, SecurityProtocol}
30+
import org.apache.kafka.common.security.token.delegation.DelegationToken
3031
import org.apache.kafka.security.authorizer.AclEntry.{WILDCARD_HOST, WILDCARD_PRINCIPAL_STRING}
31-
import org.apache.kafka.server.config.{ServerConfigs, ZkConfigs}
32+
import org.apache.kafka.server.config.{DelegationTokenManagerConfigs, ServerConfigs, ZkConfigs}
3233
import org.apache.kafka.metadata.authorizer.StandardAuthorizer
3334
import org.apache.kafka.storage.internals.log.LogConfig
3435
import org.junit.jupiter.api.Assertions._
@@ -67,6 +68,10 @@ class SaslSslAdminIntegrationTest extends BaseAdminIntegrationTest with SaslSetu
6768
this.serverConfig.setProperty(AclAuthorizer.SuperUsersProp, kafkaPrincipal.toString)
6869
}
6970

71+
// Enable delegationTokenControlManager
72+
serverConfig.setProperty(DelegationTokenManagerConfigs.DELEGATION_TOKEN_SECRET_KEY_CONFIG, "123")
73+
serverConfig.setProperty(DelegationTokenManagerConfigs.DELEGATION_TOKEN_MAX_LIFETIME_CONFIG, "5000")
74+
7075
setUpSasl()
7176
super.setUp(testInfo)
7277
setInitialAcls()
@@ -520,6 +525,50 @@ class SaslSslAdminIntegrationTest extends BaseAdminIntegrationTest with SaslSetu
520525
}
521526
}
522527

528+
@ParameterizedTest
529+
@ValueSource(strings = Array("zk", "kraft"))
530+
def testExpireDelegationToken(quorum: String): Unit = {
531+
client = createAdminClient
532+
val createDelegationTokenOptions = new CreateDelegationTokenOptions()
533+
534+
// Test expiration for non-exists token
535+
TestUtils.assertFutureExceptionTypeEquals(
536+
client.expireDelegationToken("".getBytes()).expiryTimestamp(),
537+
classOf[DelegationTokenNotFoundException]
538+
)
539+
540+
// Test expiring the token immediately
541+
val token1 = client.createDelegationToken(createDelegationTokenOptions).delegationToken().get()
542+
TestUtils.retry(maxWaitMs = 1000) { assertTrue(expireTokenOrFailWithAssert(token1, -1) < System.currentTimeMillis()) }
543+
544+
// Test expiring the expired token
545+
val token2 = client.createDelegationToken(createDelegationTokenOptions.maxlifeTimeMs(1000)).delegationToken().get()
546+
// Ensure current time > maxLifeTimeMs of token
547+
Thread.sleep(1000)
548+
TestUtils.assertFutureExceptionTypeEquals(
549+
client.expireDelegationToken(token2.hmac(), new ExpireDelegationTokenOptions().expiryTimePeriodMs(1)).expiryTimestamp(),
550+
classOf[DelegationTokenExpiredException]
551+
)
552+
553+
// Ensure expiring the expired token with negative expiryTimePeriodMs will not throw exception
554+
assertDoesNotThrow(() => expireTokenOrFailWithAssert(token2, -1))
555+
556+
// Test shortening the expiryTimestamp
557+
val token3 = client.createDelegationToken(createDelegationTokenOptions).delegationToken().get()
558+
TestUtils.retry(1000) { assertTrue(expireTokenOrFailWithAssert(token3, 200) < token3.tokenInfo().expiryTimestamp()) }
559+
}
560+
561+
private def expireTokenOrFailWithAssert(token: DelegationToken, expiryTimePeriodMs: Long): Long = {
562+
try {
563+
client.expireDelegationToken(token.hmac(), new ExpireDelegationTokenOptions().expiryTimePeriodMs(expiryTimePeriodMs))
564+
.expiryTimestamp().get()
565+
} catch {
566+
// If metadata is not synced yet, the response will contain an errorCode, causing an exception to be thrown.
567+
// This wrapper is designed to work with TestUtils.retry
568+
case _: ExecutionException => throw new AssertionError("Metadata not sync yet.")
569+
}
570+
}
571+
523572
private def describeConfigs(topic: String): Iterable[ConfigEntry] = {
524573
val topicResource = new ConfigResource(ConfigResource.Type.TOPIC, topic)
525574
var configEntries: Iterable[ConfigEntry] = null

metadata/src/main/java/org/apache/kafka/controller/DelegationTokenControlManager.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -302,20 +302,18 @@ public ControllerResult<ExpireDelegationTokenResponseData> expireDelegationToken
302302
return ControllerResult.atomicOf(records, responseData.setErrorCode(DELEGATION_TOKEN_NOT_FOUND.code()));
303303
}
304304

305-
if (myTokenInformation.maxTimestamp() < now || myTokenInformation.expiryTimestamp() < now) {
306-
return ControllerResult.atomicOf(records, responseData.setErrorCode(DELEGATION_TOKEN_EXPIRED.code()));
307-
}
308-
309305
if (!allowedToRenew(myTokenInformation, context.principal())) {
310306
return ControllerResult.atomicOf(records, responseData.setErrorCode(DELEGATION_TOKEN_OWNER_MISMATCH.code()));
311307
}
312308

313309
if (requestData.expiryTimePeriodMs() < 0) { // expire immediately
314310
responseData
315311
.setErrorCode(NONE.code())
316-
.setExpiryTimestampMs(requestData.expiryTimePeriodMs());
312+
.setExpiryTimestampMs(now);
317313
records.add(new ApiMessageAndVersion(new RemoveDelegationTokenRecord().
318314
setTokenId(myTokenInformation.tokenId()), (short) 0));
315+
} else if (myTokenInformation.maxTimestamp() < now || myTokenInformation.expiryTimestamp() < now) {
316+
responseData.setErrorCode(DELEGATION_TOKEN_EXPIRED.code());
319317
} else {
320318
long expiryTimestamp = Math.min(myTokenInformation.maxTimestamp(),
321319
now + requestData.expiryTimePeriodMs());

0 commit comments

Comments
 (0)