2020
2121import java .io .File ;
2222import java .io .IOException ;
23+ import java .nio .charset .StandardCharsets ;
2324
2425import org .slf4j .Logger ;
2526import org .slf4j .LoggerFactory ;
27+
2628import org .apache .commons .io .FileUtils ;
2729import org .apache .hadoop .classification .VisibleForTesting ;
30+ import org .apache .hadoop .conf .Configuration ;
2831import org .apache .hadoop .thirdparty .com .google .common .base .Strings ;
2932import org .apache .hadoop .util .Preconditions ;
3033
34+ import static org .apache .hadoop .fs .azurebfs .constants .AbfsHttpConstants .EMPTY_STRING ;
35+
3136/**
3237 * Provides tokens based on Azure AD Workload Identity.
3338 */
@@ -38,11 +43,73 @@ public class WorkloadIdentityTokenProvider extends AccessTokenProvider {
3843 private static final String EMPTY_TOKEN_FILE_ERROR = "Empty token file found at specified path: " ;
3944 private static final String TOKEN_FILE_READ_ERROR = "Error reading token file at specified path: " ;
4045
46+ /**
47+ * Internal implementation of ClientAssertionProvider for file-based token reading.
48+ * This provides backward compatibility for the file-based constructor.
49+ */
50+ private static class FileBasedClientAssertionProvider implements ClientAssertionProvider {
51+ private final String tokenFile ;
52+
53+ FileBasedClientAssertionProvider (String tokenFile ) {
54+ this .tokenFile = tokenFile ;
55+ }
56+
57+ @ Override
58+ public void initialize (Configuration configuration , String accountName ) throws IOException {
59+ // No initialization needed for file-based provider
60+ }
61+
62+ @ Override
63+ public String getClientAssertion () throws IOException {
64+ String clientAssertion = EMPTY_STRING ;
65+ try {
66+ File file = new File (tokenFile );
67+ clientAssertion = FileUtils .readFileToString (file , StandardCharsets .UTF_8 );
68+ } catch (Exception e ) {
69+ throw new IOException (TOKEN_FILE_READ_ERROR + tokenFile , e );
70+ }
71+ clientAssertion = clientAssertion .trim ();
72+ if (Strings .isNullOrEmpty (clientAssertion )) {
73+ throw new IOException (EMPTY_TOKEN_FILE_ERROR + tokenFile );
74+ }
75+ return clientAssertion ;
76+ }
77+ }
78+
4179 private final String authEndpoint ;
4280 private final String clientId ;
43- private final String tokenFile ;
81+ private final ClientAssertionProvider clientAssertionProvider ;
4482 private long tokenFetchTime = -1 ;
4583
84+ /**
85+ * Constructor with custom ClientAssertionProvider.
86+ * Use this for custom token retrieval mechanisms like Kubernetes Token Request API.
87+ *
88+ * @param authority OAuth authority URL
89+ * @param tenantId Azure AD tenant ID
90+ * @param clientId Azure AD client ID
91+ * @param clientAssertionProvider Custom provider for client assertions
92+ */
93+ public WorkloadIdentityTokenProvider (final String authority , final String tenantId ,
94+ final String clientId , ClientAssertionProvider clientAssertionProvider ) {
95+ Preconditions .checkNotNull (authority , "authority" );
96+ Preconditions .checkNotNull (tenantId , "tenantId" );
97+ Preconditions .checkNotNull (clientId , "clientId" );
98+ Preconditions .checkNotNull (clientAssertionProvider , "clientAssertionProvider" );
99+
100+ this .authEndpoint = authority + tenantId + OAUTH2_TOKEN_PATH ;
101+ this .clientId = clientId ;
102+ this .clientAssertionProvider = clientAssertionProvider ;
103+ }
104+
105+ /**
106+ * Constructor with file-based token reading (backward compatibility).
107+ *
108+ * @param authority OAuth authority URL
109+ * @param tenantId Azure AD tenant ID
110+ * @param clientId Azure AD client ID
111+ * @param tokenFile Path to file containing the JWT token
112+ */
46113 public WorkloadIdentityTokenProvider (final String authority , final String tenantId ,
47114 final String clientId , final String tokenFile ) {
48115 Preconditions .checkNotNull (authority , "authority" );
@@ -52,13 +119,13 @@ public WorkloadIdentityTokenProvider(final String authority, final String tenant
52119
53120 this .authEndpoint = authority + tenantId + OAUTH2_TOKEN_PATH ;
54121 this .clientId = clientId ;
55- this .tokenFile = tokenFile ;
122+ this .clientAssertionProvider = new FileBasedClientAssertionProvider ( tokenFile ) ;
56123 }
57124
58125 @ Override
59126 protected AzureADToken refreshToken () throws IOException {
60127 LOG .debug ("AADToken: refreshing token from JWT Assertion" );
61- String clientAssertion = getClientAssertion ();
128+ String clientAssertion = clientAssertionProvider . getClientAssertion ();
62129 AzureADToken token = getTokenUsingJWTAssertion (clientAssertion );
63130 tokenFetchTime = System .currentTimeMillis ();
64131 return token ;
@@ -90,31 +157,6 @@ protected boolean isTokenAboutToExpire() {
90157 return expiring ;
91158 }
92159
93- /**
94- * Gets the client assertion from the token file.
95- * The token file should contain the client assertion in JWT format.
96- * It should be a String containing Base64Url encoded JSON Web Token (JWT).
97- * See <a href="https://azure.github.io/azure-workload-identity/docs/faq.html#does-workload-identity-work-in-disconnected-environments">
98- * Azure Workload Identity FAQ</a>.
99- *
100- * @return the client assertion.
101- * @throws IOException if the token file is empty.
102- */
103- private String getClientAssertion ()
104- throws IOException {
105- String clientAssertion = "" ;
106- try {
107- File file = new File (tokenFile );
108- clientAssertion = FileUtils .readFileToString (file , "UTF-8" );
109- } catch (Exception e ) {
110- throw new IOException (TOKEN_FILE_READ_ERROR + tokenFile , e );
111- }
112- if (Strings .isNullOrEmpty (clientAssertion )) {
113- throw new IOException (EMPTY_TOKEN_FILE_ERROR + tokenFile );
114- }
115- return clientAssertion ;
116- }
117-
118160 /**
119161 * Gets the Azure AD token from a client assertion in JWT format.
120162 * This method exists to make unit testing possible.
0 commit comments