20
20
21
21
import java .io .File ;
22
22
import java .io .IOException ;
23
+ import java .nio .charset .StandardCharsets ;
23
24
24
25
import org .slf4j .Logger ;
25
26
import org .slf4j .LoggerFactory ;
27
+
26
28
import org .apache .commons .io .FileUtils ;
27
29
import org .apache .hadoop .classification .VisibleForTesting ;
30
+ import org .apache .hadoop .conf .Configuration ;
28
31
import org .apache .hadoop .thirdparty .com .google .common .base .Strings ;
29
32
import org .apache .hadoop .util .Preconditions ;
30
33
34
+ import static org .apache .hadoop .fs .azurebfs .constants .AbfsHttpConstants .EMPTY_STRING ;
35
+
31
36
/**
32
37
* Provides tokens based on Azure AD Workload Identity.
33
38
*/
@@ -38,11 +43,73 @@ public class WorkloadIdentityTokenProvider extends AccessTokenProvider {
38
43
private static final String EMPTY_TOKEN_FILE_ERROR = "Empty token file found at specified path: " ;
39
44
private static final String TOKEN_FILE_READ_ERROR = "Error reading token file at specified path: " ;
40
45
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
+
41
79
private final String authEndpoint ;
42
80
private final String clientId ;
43
- private final String tokenFile ;
81
+ private final ClientAssertionProvider clientAssertionProvider ;
44
82
private long tokenFetchTime = -1 ;
45
83
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
+ */
46
113
public WorkloadIdentityTokenProvider (final String authority , final String tenantId ,
47
114
final String clientId , final String tokenFile ) {
48
115
Preconditions .checkNotNull (authority , "authority" );
@@ -52,13 +119,13 @@ public WorkloadIdentityTokenProvider(final String authority, final String tenant
52
119
53
120
this .authEndpoint = authority + tenantId + OAUTH2_TOKEN_PATH ;
54
121
this .clientId = clientId ;
55
- this .tokenFile = tokenFile ;
122
+ this .clientAssertionProvider = new FileBasedClientAssertionProvider ( tokenFile ) ;
56
123
}
57
124
58
125
@ Override
59
126
protected AzureADToken refreshToken () throws IOException {
60
127
LOG .debug ("AADToken: refreshing token from JWT Assertion" );
61
- String clientAssertion = getClientAssertion ();
128
+ String clientAssertion = clientAssertionProvider . getClientAssertion ();
62
129
AzureADToken token = getTokenUsingJWTAssertion (clientAssertion );
63
130
tokenFetchTime = System .currentTimeMillis ();
64
131
return token ;
@@ -90,31 +157,6 @@ protected boolean isTokenAboutToExpire() {
90
157
return expiring ;
91
158
}
92
159
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
-
118
160
/**
119
161
* Gets the Azure AD token from a client assertion in JWT format.
120
162
* This method exists to make unit testing possible.
0 commit comments