This is an automated email from the ASF dual-hosted git repository.
CalvinKirs pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 77893fb6a4f [fix](filesystem) replace throttled kerberos relogin with
proactive TGT refresh to fix flaky GSS failure (#64394)
77893fb6a4f is described below
commit 77893fb6a4f95bb7339c19d4e596443e7cfd8b13
Author: Calvin Kirs <[email protected]>
AuthorDate: Tue Jun 30 10:44:21 2026 +0800
[fix](filesystem) replace throttled kerberos relogin with proactive TGT
refresh to fix flaky GSS failure (#64394)
### What problem does this PR solve?
Issue Number: close #xxx
Related PR: #62023
Problem Summary:
The regression case `test_two_hive_kerberos` fails intermittently with:
```
GSSException: No valid credentials provided
...
Caused by: javax.security.auth.login.LoginException: Cannot read from
System.in
```
Root cause: the `KerberosHadoopAuthenticator` in `fe-filesystem-hdfs`
(introduced in #62023) relies on Hadoop's
`UserGroupInformation.checkTGTAndReloginFromKeytab()`, which has a
hard-coded 60s relogin throttle (`hasSufficientTimeElapsed`). When the
TGT expires inside that throttle window, the relogin is silently
skipped, the subsequent SASL handshake finds no valid ticket, and the
JAAS fallback path tries to prompt on `System.in` — which fails in a
server process.
Fix: replace the throttled reactive relogin with trino's proactive TGT
refresh model (ported 1:1 from trino 435 `KerberosAuthentication` /
`CachingKerberosHadoopAuthentication` / `KerberosTicketUtils`):
- Login eagerly at construction with explicit JAAS options
(`doNotPrompt=true`, `useKeyTab=true`, `storeKey=true`,
`isInitiator=true`) — `doNotPrompt=true` makes the `System.in` prompt
path impossible.
- Compute the next refresh time as ticket start + 80% of ticket
lifetime.
- On each `doAs`, if past the refresh point, perform a fresh keytab
login (no throttle) and swap the new credentials into the existing
`Subject` in place, so the cached UGI stays valid.
- A failed refresh does not advance the refresh time, so the next call
retries immediately; the failure is wrapped as `IOException` to keep the
SPI's checked-exception contract.
The vendored `KerberosTicketUtils` is placed in `fe-foundation`
(`org.apache.doris.foundation.security`) and provided to filesystem
plugins through `fe-filesystem-spi`, which also lets `fe-common`'s
`HadoopKerberosAuthenticator` drop its `io.trino` import.
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [x] Yes. <!-- Explain the behavior change --> Kerberos keytab relogin
in fe-filesystem-hdfs is now proactive (refresh at 80% of TGT lifetime,
unthrottled) instead of Hadoop's reactive relogin with a 60s throttle.
---
fe/fe-common/pom.xml | 5 +
.../HadoopKerberosAuthenticator.java | 3 +-
.../hdfs/KerberosHadoopAuthenticator.java | 136 +++++++++++++++--
.../hdfs/KerberosHadoopAuthenticatorTest.java | 165 +++++++++++++++++++++
fe/fe-filesystem/fe-filesystem-spi/pom.xml | 12 +-
.../foundation/security/KerberosTicketUtils.java | 75 ++++++++++
.../security/KerberosTicketUtilsTest.java | 72 +++++++++
fs_brokers/cdc_client/build.sh | 2 +-
8 files changed, 455 insertions(+), 15 deletions(-)
diff --git a/fe/fe-common/pom.xml b/fe/fe-common/pom.xml
index 3452c3e5967..8dad7e6f3ef 100644
--- a/fe/fe-common/pom.xml
+++ b/fe/fe-common/pom.xml
@@ -67,6 +67,11 @@ under the License.
</profile>
</profiles>
<dependencies>
+ <dependency>
+ <groupId>org.apache.doris</groupId>
+ <artifactId>fe-foundation</artifactId>
+ <version>${revision}</version>
+ </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
diff --git
a/fe/fe-common/src/main/java/org/apache/doris/common/security/authentication/HadoopKerberosAuthenticator.java
b/fe/fe-common/src/main/java/org/apache/doris/common/security/authentication/HadoopKerberosAuthenticator.java
index 4e80fc17a80..1f3d51c2be6 100644
---
a/fe/fe-common/src/main/java/org/apache/doris/common/security/authentication/HadoopKerberosAuthenticator.java
+++
b/fe/fe-common/src/main/java/org/apache/doris/common/security/authentication/HadoopKerberosAuthenticator.java
@@ -17,10 +17,11 @@
package org.apache.doris.common.security.authentication;
+import org.apache.doris.foundation.security.KerberosTicketUtils;
+
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import io.trino.plugin.base.authentication.KerberosTicketUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.security.UserGroupInformation;
diff --git
a/fe/fe-filesystem/fe-filesystem-hdfs/src/main/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticator.java
b/fe/fe-filesystem/fe-filesystem-hdfs/src/main/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticator.java
index 1d498f3a36c..5a38eb85f76 100644
---
a/fe/fe-filesystem/fe-filesystem-hdfs/src/main/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticator.java
+++
b/fe/fe-filesystem/fe-filesystem-hdfs/src/main/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticator.java
@@ -14,11 +14,16 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
+// This file is based on code available under the Apache license here:
+//
https://github.com/trinodb/trino/blob/435/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/authentication/KerberosAuthentication.java
+//
https://github.com/trinodb/trino/blob/435/lib/trino-hdfs/src/main/java/io/trino/hdfs/authentication/CachingKerberosHadoopAuthentication.java
+// and modified by Doris
package org.apache.doris.filesystem.hdfs;
import org.apache.doris.filesystem.spi.HadoopAuthenticator;
import org.apache.doris.filesystem.spi.IOCallable;
+import org.apache.doris.foundation.security.KerberosTicketUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.SecurityUtil;
@@ -29,10 +34,29 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
/**
* Kerberos-based implementation of {@link HadoopAuthenticator}.
- * Logs in from a keytab and executes actions as the Kerberos principal via
UGI.doAs().
+ *
+ * <p>Logs in from a keytab via an explicit JAAS {@code Krb5LoginModule}
configuration
+ * ({@code doNotPrompt=true}) and proactively refreshes the TGT once it passes
80% of
+ * its lifetime, swapping the new credentials into the existing Subject in
place. This
+ * is a port of trino's {@code KerberosAuthentication} +
+ * {@code CachingKerberosHadoopAuthentication}. It deliberately does NOT use
+ * {@code UserGroupInformation.checkTGTAndReloginFromKeytab()}: its hard-coded
+ * 60-second relogin throttle can leave an expired TGT in the Subject, after
which the
+ * SASL/GSS layer falls back to the JVM-default interactive JAAS login and
fails with
+ * "LoginException: Cannot read from System.in".
*
* <p>Note: {@link UserGroupInformation#setConfiguration(Configuration)}
mutates
* JVM-global state — all UGI instances in the process share the same
authentication
@@ -51,7 +75,14 @@ public class KerberosHadoopAuthenticator implements
HadoopAuthenticator {
private final String principal;
private final String keytab;
- private volatile UserGroupInformation ugi;
+
+ // The Subject/UGI pair is created once and never replaced: refreshes swap
new
+ // Kerberos credentials into this same Subject so Hadoop code that caches
the
+ // UGI (e.g. DFSClient) transparently sees the new ticket.
+ private final Subject subject;
+ private final UserGroupInformation ugi;
+ // Guarded by "this" (only touched in the constructor and synchronized
getUGI()).
+ private long nextRefreshTime;
public KerberosHadoopAuthenticator(String principal, String keytab,
Configuration conf) {
this.principal = principal;
@@ -62,10 +93,14 @@ public class KerberosHadoopAuthenticator implements
HadoopAuthenticator {
if (!shouldSkipSetConfiguration(desired)) {
UserGroupInformation.setConfiguration(conf);
}
- this.ugi =
UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, keytab);
+ this.subject = loginSubject();
+ this.ugi =
Objects.requireNonNull(UserGroupInformation.getUGIFromSubject(subject),
+ "getUGIFromSubject returned null");
}
+ this.nextRefreshTime = KerberosTicketUtils.getRefreshTime(
+ KerberosTicketUtils.getTicketGrantingTicket(subject));
LOG.info("Kerberos login succeeded for principal={}", principal);
- } catch (IOException e) {
+ } catch (IOException | RuntimeException e) {
throw new RuntimeException("Failed to login with Kerberos
principal=" + principal
+ ", keytab=" + keytab, e);
}
@@ -98,20 +133,99 @@ public class KerberosHadoopAuthenticator implements
HadoopAuthenticator {
@Override
public <T> T doAs(IOCallable<T> action) throws IOException {
- // Refresh the Kerberos TGT from the keytab if it's close to expiry.
This is
- // a no-op when the ticket is still valid, so it's safe and cheap to
call on
- // every request and avoids long-lived FE processes failing with
- // "GSSException: No valid credentials" after the initial ticket
expires.
+ UserGroupInformation currentUgi;
try {
- ugi.checkTGTAndReloginFromKeytab();
- } catch (IOException e) {
+ currentUgi = getUGI();
+ } catch (IOException | RuntimeException e) {
+ // Keep the SPI's checked-IOException contract: a relogin failure
(unchecked
+ // RuntimeException from the JAAS login) must not escape doAs
unchecked.
throw new IOException("Kerberos relogin failed for principal=" +
principal, e);
}
try {
- return ugi.doAs((PrivilegedExceptionAction<T>) action::call);
+ return currentUgi.doAs((PrivilegedExceptionAction<T>)
action::call);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Kerberos doAs interrupted for principal=" +
principal, e);
}
}
+
+ /**
+ * Returns the cached UGI, first refreshing the TGT if it is past 80% of
its
+ * lifetime. Ported from trino's
+ * {@code CachingKerberosHadoopAuthentication.getUserGroupInformation()} —
note
+ * there is intentionally no relogin throttle.
+ * If the refresh login fails, {@code nextRefreshTime} is not advanced, so
each
+ * subsequent call retries the login until the KDC recovers (no backoff) —
same
+ * trade-off as trino.
+ */
+ private synchronized UserGroupInformation getUGI() throws IOException {
+ if (nextRefreshTime < System.currentTimeMillis()) {
+ Subject newSubject = loginSubject();
+
Objects.requireNonNull(UserGroupInformation.getUGIFromSubject(newSubject),
+ "getUGIFromSubject returned null");
+ // We modify the existing UGI's credentials in-place instead of
returning a new UGI
+ // because some parts of Hadoop code reuse UGI (e.g. DFSClient).
+ // We also need to clear the old credentials because the JDK
assumes that the first
+ // credential is the TGT, which is not always true.
+ subject.getPrincipals().addAll(newSubject.getPrincipals());
+ Set<Object> privateCredentials = subject.getPrivateCredentials();
+ synchronized (privateCredentials) {
+ privateCredentials.clear();
+ privateCredentials.addAll(newSubject.getPrivateCredentials());
+ }
+ Set<Object> publicCredentials = subject.getPublicCredentials();
+ synchronized (publicCredentials) {
+ publicCredentials.clear();
+ publicCredentials.addAll(newSubject.getPublicCredentials());
+ }
+ nextRefreshTime = KerberosTicketUtils.getRefreshTime(
+ KerberosTicketUtils.getTicketGrantingTicket(newSubject));
+ LOG.info("Kerberos ticket refreshed for principal={}, next refresh
time={}",
+ principal, nextRefreshTime);
+ }
+ return ugi;
+ }
+
+ /**
+ * Performs the JAAS keytab login and returns the logged-in Subject. This
is
+ * trino's {@code KerberosAuthentication.getSubject()} delegate boundary,
kept
+ * as a package-private method so tests can substitute fabricated Subjects.
+ */
+ Subject loginSubject() {
+ return getSubject(keytab, principal);
+ }
+
+ private static Subject getSubject(String keytab, String principal) {
+ Subject subject = new Subject(false, Collections.singleton(new
KerberosPrincipal(principal)),
+ Collections.emptySet(), Collections.emptySet());
+ javax.security.auth.login.Configuration conf =
getConfiguration(keytab, principal);
+ try {
+ LoginContext loginContext = new LoginContext("", subject, null,
conf);
+ loginContext.login();
+ return loginContext.getSubject();
+ } catch (LoginException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static javax.security.auth.login.Configuration
getConfiguration(String keytab, String principal) {
+ Map<String, String> optionsBuilder = new LinkedHashMap<>();
+ optionsBuilder.put("doNotPrompt", "true");
+ optionsBuilder.put("isInitiator", "true");
+ optionsBuilder.put("principal", principal);
+ optionsBuilder.put("useKeyTab", "true");
+ optionsBuilder.put("storeKey", "true");
+ optionsBuilder.put("keyTab", keytab);
+ Map<String, String> options =
Collections.unmodifiableMap(optionsBuilder);
+ return new javax.security.auth.login.Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String
name) {
+ return new AppConfigurationEntry[] {
+ new AppConfigurationEntry(
+ "com.sun.security.auth.module.Krb5LoginModule",
+
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ options)};
+ }
+ };
+ }
}
diff --git
a/fe/fe-filesystem/fe-filesystem-hdfs/src/test/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticatorTest.java
b/fe/fe-filesystem/fe-filesystem-hdfs/src/test/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticatorTest.java
new file mode 100644
index 00000000000..2c6d6907f69
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-hdfs/src/test/java/org/apache/doris/filesystem/hdfs/KerberosHadoopAuthenticatorTest.java
@@ -0,0 +1,165 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.filesystem.hdfs;
+
+import org.apache.doris.foundation.security.KerberosTicketUtils;
+
+import org.apache.hadoop.conf.Configuration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Date;
+import java.util.Deque;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosTicket;
+
+/**
+ * Unit tests for the proactive TGT refresh logic. No KDC: the JAAS keytab
login
+ * (package-private {@code loginSubject()} seam, trino's KerberosAuthentication
+ * boundary) is replaced with fabricated Subjects holding real KerberosTicket
+ * objects whose start/end times steer the 80%-lifetime refresh point.
+ */
+class KerberosHadoopAuthenticatorTest {
+
+ private static final KerberosPrincipal CLIENT = new
KerberosPrincipal("doris/[email protected]");
+ private static final KerberosPrincipal TGS = new
KerberosPrincipal("krbtgt/[email protected]");
+
+ /** Canned-login subclass; static state because loginSubject() is called
from the super constructor. */
+ private static final class FakeLoginAuthenticator extends
KerberosHadoopAuthenticator {
+ static final Deque<Subject> LOGINS = new ArrayDeque<>();
+ static int loginCount = 0;
+ static RuntimeException nextLoginFailure = null;
+
+ FakeLoginAuthenticator() {
+ super("doris/[email protected]", "/path/to/doris.keytab", new
Configuration());
+ }
+
+ @Override
+ Subject loginSubject() {
+ loginCount++;
+ if (nextLoginFailure != null) {
+ RuntimeException failure = nextLoginFailure;
+ nextLoginFailure = null;
+ throw failure;
+ }
+ return LOGINS.removeFirst();
+ }
+ }
+
+ private static Subject subjectWithTgt(long startMillis, long endMillis) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(CLIENT);
+ subject.getPrivateCredentials().add(new KerberosTicket(new byte[] {1},
CLIENT, TGS,
+ new byte[] {1}, 1, null, new Date(startMillis), new
Date(startMillis),
+ new Date(endMillis), null, null));
+ return subject;
+ }
+
+ @BeforeEach
+ void resetFakeLogins() {
+ FakeLoginAuthenticator.LOGINS.clear();
+ FakeLoginAuthenticator.loginCount = 0;
+ FakeLoginAuthenticator.nextLoginFailure = null;
+ }
+
+ @Test
+ void constructorLogsInEagerlyAndFreshTicketIsNotRefreshed() throws
IOException {
+ long now = System.currentTimeMillis();
+ // refresh point = now + 0.8 * 600s = now + 480s, far in the future
+ FakeLoginAuthenticator.LOGINS.add(subjectWithTgt(now, now + 600_000));
+
+ KerberosHadoopAuthenticator auth = new FakeLoginAuthenticator();
+ Assertions.assertEquals(1, FakeLoginAuthenticator.loginCount);
+
+ Assertions.assertEquals("ok", auth.doAs(() -> "ok"));
+ auth.doAs(() -> "ok");
+ Assertions.assertEquals(1, FakeLoginAuthenticator.loginCount);
+ }
+
+ @Test
+ void staleTicketIsRefreshedInPlaceWithoutThrottle() throws IOException {
+ long now = System.currentTimeMillis();
+ // initial TGT is past its 80%-lifetime refresh point:
+ // refresh point = (now-100s) + 0.8 * 101s = now - 19.2s < now
+ Subject initial = subjectWithTgt(now - 100_000, now + 1_000);
+ Subject renewed = subjectWithTgt(now, now + 600_000);
+ FakeLoginAuthenticator.LOGINS.add(initial);
+ FakeLoginAuthenticator.LOGINS.add(renewed);
+
+ KerberosHadoopAuthenticator auth = new FakeLoginAuthenticator();
+ Assertions.assertEquals(1, FakeLoginAuthenticator.loginCount);
+
+ // constructor login happened seconds ago; a 60s-throttled
implementation
+ // (checkTGTAndReloginFromKeytab) would skip this refresh — ours must
not
+ Assertions.assertEquals("ok", auth.doAs(() -> "ok"));
+ Assertions.assertEquals(2, FakeLoginAuthenticator.loginCount);
+
+ // in-place swap: the ORIGINAL Subject now holds the renewed TGT
+ KerberosTicket current =
KerberosTicketUtils.getTicketGrantingTicket(initial);
+ Assertions.assertEquals(now + 600_000, current.getEndTime().getTime());
+
+ // renewed ticket is fresh → no further login
+ auth.doAs(() -> "ok");
+ Assertions.assertEquals(2, FakeLoginAuthenticator.loginCount);
+ }
+
+ @Test
+ void doAsPropagatesIOException() {
+ long now = System.currentTimeMillis();
+ FakeLoginAuthenticator.LOGINS.add(subjectWithTgt(now, now + 600_000));
+
+ KerberosHadoopAuthenticator auth = new FakeLoginAuthenticator();
+ IOException thrown = Assertions.assertThrows(IOException.class, () ->
auth.doAs(() -> {
+ throw new IOException("intentional");
+ }));
+ Assertions.assertEquals("intentional", thrown.getMessage());
+ }
+
+ @Test
+ void constructorWrapsLoginFailureWithPrincipalAndKeytab() {
+ FakeLoginAuthenticator.nextLoginFailure =
+ new RuntimeException(new
javax.security.auth.login.LoginException("no keytab"));
+ RuntimeException thrown =
Assertions.assertThrows(RuntimeException.class,
+ FakeLoginAuthenticator::new);
+
Assertions.assertTrue(thrown.getMessage().contains("doris/[email protected]"));
+
Assertions.assertTrue(thrown.getMessage().contains("/path/to/doris.keytab"));
+ }
+
+ @Test
+ void doAsWrapsRefreshLoginFailureAsIOException() throws IOException {
+ long now = System.currentTimeMillis();
+ // TGT already past its 80% refresh point, so the first doAs attempts
a relogin
+ FakeLoginAuthenticator.LOGINS.add(subjectWithTgt(now - 100_000, now +
1_000));
+
+ KerberosHadoopAuthenticator auth = new FakeLoginAuthenticator();
+ FakeLoginAuthenticator.nextLoginFailure =
+ new RuntimeException(new
javax.security.auth.login.LoginException("kdc down"));
+
+ IOException thrown = Assertions.assertThrows(IOException.class, () ->
auth.doAs(() -> "ok"));
+ Assertions.assertTrue(thrown.getMessage().contains("Kerberos relogin
failed"));
+
Assertions.assertTrue(thrown.getMessage().contains("doris/[email protected]"));
+
+ // a later doAs retries the login (nextRefreshTime was not advanced)
and succeeds
+ FakeLoginAuthenticator.LOGINS.add(subjectWithTgt(now, now + 600_000));
+ Assertions.assertEquals("ok", auth.doAs(() -> "ok"));
+ }
+}
diff --git a/fe/fe-filesystem/fe-filesystem-spi/pom.xml
b/fe/fe-filesystem/fe-filesystem-spi/pom.xml
index a3cc2432820..10c5f2223c9 100644
--- a/fe/fe-filesystem/fe-filesystem-spi/pom.xml
+++ b/fe/fe-filesystem/fe-filesystem-spi/pom.xml
@@ -36,8 +36,8 @@ under the License.
Service Provider Interface (SPI) for Doris FE filesystem abstraction.
Contains FileSystemProvider (ServiceLoader SPI entry point), the
ObjStorage SPI layer
for object-storage backends, HadoopAuthenticator, and their supporting
value types.
- Zero third-party external dependencies — only JDK and Doris internal
SPI interfaces.
- This is the ONLY filesystem artifact compiled into fe-core.
+ Depends only on JDK and Doris internal modules (fe-filesystem-api,
fe-extension-spi,
+ fe-foundation). This is the ONLY filesystem artifact compiled into
fe-core.
</description>
<dependencies>
@@ -56,6 +56,14 @@ under the License.
<artifactId>fe-extension-spi</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- fe-foundation: bottom-layer shared utilities (e.g.
KerberosTicketUtils) provided
+ to all filesystem provider plugins through the SPI platform, so
individual
+ plugins do not declare it themselves. -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>fe-foundation</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
diff --git
a/fe/fe-foundation/src/main/java/org/apache/doris/foundation/security/KerberosTicketUtils.java
b/fe/fe-foundation/src/main/java/org/apache/doris/foundation/security/KerberosTicketUtils.java
new file mode 100644
index 00000000000..c17ba91c1dc
--- /dev/null
+++
b/fe/fe-foundation/src/main/java/org/apache/doris/foundation/security/KerberosTicketUtils.java
@@ -0,0 +1,75 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+//
https://github.com/trinodb/trino/blob/435/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/authentication/KerberosTicketUtils.java
+// and modified by Doris
+
+package org.apache.doris.foundation.security;
+
+import java.util.Set;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosTicket;
+
+public final class KerberosTicketUtils {
+ private static final float TICKET_RENEW_WINDOW = 0.80f;
+
+ private KerberosTicketUtils() {
+ }
+
+ public static KerberosTicket getTicketGrantingTicket(Subject subject) {
+ Set<KerberosTicket> tickets =
subject.getPrivateCredentials(KerberosTicket.class);
+ for (KerberosTicket ticket : tickets) {
+ if (isOriginalTicketGrantingTicket(ticket)) {
+ return ticket;
+ }
+ }
+ throw new IllegalArgumentException("kerberos ticket not found in " +
subject);
+ }
+
+ public static long getRefreshTime(KerberosTicket ticket) {
+ long start = ticket.getStartTime().getTime();
+ long end = ticket.getEndTime().getTime();
+ return start + (long) ((end - start) * TICKET_RENEW_WINDOW);
+ }
+
+ /**
+ * Check whether the server principal is the TGS's principal
+ *
+ * @param ticket the original TGT (the ticket that is obtained when a
+ * kinit is done)
+ * @return true or false
+ */
+ public static boolean isOriginalTicketGrantingTicket(KerberosTicket
ticket) {
+ return isTicketGrantingServerPrincipal(ticket.getServer());
+ }
+
+ /**
+ * TGS must have the server principal of the form "krbtgt/FOO@FOO".
+ *
+ * @return true or false
+ */
+ private static boolean isTicketGrantingServerPrincipal(KerberosPrincipal
principal) {
+ if (principal == null) {
+ return false;
+ }
+ if (principal.getName().equals("krbtgt/" + principal.getRealm() + "@"
+ principal.getRealm())) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git
a/fe/fe-foundation/src/test/java/org/apache/doris/foundation/security/KerberosTicketUtilsTest.java
b/fe/fe-foundation/src/test/java/org/apache/doris/foundation/security/KerberosTicketUtilsTest.java
new file mode 100644
index 00000000000..68c6b0ce5f2
--- /dev/null
+++
b/fe/fe-foundation/src/test/java/org/apache/doris/foundation/security/KerberosTicketUtilsTest.java
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.foundation.security;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosTicket;
+
+class KerberosTicketUtilsTest {
+
+ private static final KerberosPrincipal CLIENT = new
KerberosPrincipal("doris/[email protected]");
+ private static final KerberosPrincipal TGS = new
KerberosPrincipal("krbtgt/[email protected]");
+ private static final KerberosPrincipal SERVICE = new
KerberosPrincipal("hive/[email protected]");
+
+ static KerberosTicket ticket(KerberosPrincipal server, long startMillis,
long endMillis) {
+ return new KerberosTicket(new byte[] {1}, CLIENT, server, new byte[]
{1}, 1, null,
+ new Date(startMillis), new Date(startMillis), new
Date(endMillis), null, null);
+ }
+
+ @Test
+ void getRefreshTimeIsAt80PercentOfTicketLifetime() {
+ long start = 1_000_000L;
+ long end = start + 100_000L;
+ Assertions.assertEquals(start + 80_000L,
+ KerberosTicketUtils.getRefreshTime(ticket(TGS, start, end)));
+ }
+
+ @Test
+ void getTicketGrantingTicketSelectsTgtAmongOtherTickets() {
+ KerberosTicket serviceTicket = ticket(SERVICE, 0, 100_000);
+ KerberosTicket tgt = ticket(TGS, 0, 100_000);
+ Subject subject = new Subject();
+ subject.getPrivateCredentials().add(serviceTicket);
+ subject.getPrivateCredentials().add(tgt);
+ Assertions.assertSame(tgt,
KerberosTicketUtils.getTicketGrantingTicket(subject));
+ }
+
+ @Test
+ void getTicketGrantingTicketThrowsWhenAbsent() {
+ Subject subject = new Subject();
+ subject.getPrivateCredentials().add(ticket(SERVICE, 0, 100_000));
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> KerberosTicketUtils.getTicketGrantingTicket(subject));
+ }
+
+ @Test
+ void isOriginalTicketGrantingTicketChecksServerPrincipalForm() {
+ Assertions.assertTrue(
+ KerberosTicketUtils.isOriginalTicketGrantingTicket(ticket(TGS,
0, 100_000)));
+ Assertions.assertFalse(
+
KerberosTicketUtils.isOriginalTicketGrantingTicket(ticket(SERVICE, 0,
100_000)));
+ }
+}
diff --git a/fs_brokers/cdc_client/build.sh b/fs_brokers/cdc_client/build.sh
index 2e50b76523f..593ca9f5a30 100755
--- a/fs_brokers/cdc_client/build.sh
+++ b/fs_brokers/cdc_client/build.sh
@@ -27,7 +27,7 @@ export CDC_CLIENT_HOME="${ROOT}"
bash "${DORIS_HOME}"/generated-source.sh noclean
cd "${DORIS_HOME}/fe"
-"${MVN_CMD}" -Pflatten install -pl fe-common -Dskip.doc=true -DskipTests
-Dmaven.build.cache.enabled=false
+"${MVN_CMD}" -Pflatten install -pl fe-common -am -Dskip.doc=true -DskipTests
-Dmaven.build.cache.enabled=false
echo "Install cdc client..."
cd "${CDC_CLIENT_HOME}"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]