11
11
* See the License for the specific language governing permissions and
12
12
* limitations under the License.
13
13
*/
14
- package io .streamnative .pulsar .handlers .mqtt .identitypool ;
15
-
16
- import static io .streamnative .pulsar .handlers .mqtt .identitypool .ExpressionCompiler .DN ;
17
- import static io .streamnative .pulsar .handlers .mqtt .identitypool .ExpressionCompiler .DN_KEYS ;
18
- import static io .streamnative .pulsar .handlers .mqtt .identitypool .ExpressionCompiler .SAN ;
19
- import static io .streamnative .pulsar .handlers .mqtt .identitypool .ExpressionCompiler .SHA1 ;
20
- import static io .streamnative .pulsar .handlers .mqtt .identitypool .ExpressionCompiler .SNID ;
14
+ package io .streamnative .pulsar .handlers .mqtt .authentication ;
15
+
16
+ import static io .streamnative .pulsar .handlers .mqtt .authentication .ExpressionCompiler .DN ;
17
+ import static io .streamnative .pulsar .handlers .mqtt .authentication .ExpressionCompiler .SAN ;
18
+ import static io .streamnative .pulsar .handlers .mqtt .authentication .ExpressionCompiler .SHA1 ;
19
+ import static io .streamnative .pulsar .handlers .mqtt .authentication .ExpressionCompiler .SNID ;
20
+ import com .fasterxml .jackson .core .JsonProcessingException ;
21
+ import com .fasterxml .jackson .databind .ObjectMapper ;
21
22
import com .google .common .annotations .VisibleForTesting ;
22
23
import com .google .common .base .Joiner ;
23
24
import io .streamnative .oidc .broker .common .OIDCPoolResources ;
43
44
import lombok .extern .slf4j .Slf4j ;
44
45
import org .apache .commons .codec .binary .Hex ;
45
46
import org .apache .commons .lang .StringUtils ;
47
+ import org .apache .commons .lang .text .StrBuilder ;
46
48
import org .apache .pulsar .broker .ServiceConfiguration ;
47
49
import org .apache .pulsar .broker .authentication .AuthenticationDataSource ;
48
50
import org .apache .pulsar .broker .authentication .AuthenticationProvider ;
49
51
import org .apache .pulsar .broker .authentication .metrics .AuthenticationMetrics ;
52
+ import org .apache .pulsar .common .util .ObjectMapperFactory ;
50
53
import org .apache .pulsar .metadata .api .MetadataStore ;
51
54
import org .apache .pulsar .metadata .api .MetadataStoreConfig ;
52
55
import org .apache .pulsar .metadata .api .MetadataStoreException ;
@@ -64,6 +67,8 @@ public class AuthenticationProviderMTls implements AuthenticationProvider {
64
67
@ VisibleForTesting
65
68
private OIDCPoolResources poolResources ;
66
69
70
+ private final ObjectMapper objectMapper = ObjectMapperFactory .create ();
71
+
67
72
@ Getter
68
73
@ VisibleForTesting
69
74
private final ConcurrentHashMap <String , ExpressionCompiler > poolMap = new ConcurrentHashMap <>();
@@ -212,7 +217,7 @@ public String authenticate(AuthenticationDataSource authData) throws Authenticat
212
217
final X509Certificate certificate = (X509Certificate ) certs [0 ];
213
218
214
219
// parse DN
215
- Map <String , Object > params ;
220
+ Map <String , String > params ;
216
221
try {
217
222
String subject = certificate .getSubjectX500Principal ().getName ();
218
223
params = parseDN (subject );
@@ -228,20 +233,26 @@ public String authenticate(AuthenticationDataSource authData) throws Authenticat
228
233
// parse SHA1
229
234
params .put (SHA1 , parseSHA1FingerPrint (certificate ));
230
235
231
- String principal = matchPool (params );
232
- if (principal .isEmpty ()) {
236
+ String poolName = matchPool (params );
237
+ if (poolName .isEmpty ()) {
233
238
errorCode = ErrorCode .NO_MATCH_POOL ;
234
239
throw new AuthenticationException ("No matched identity pool from the client certificate" );
235
240
}
241
+ AuthRequest authRequest = new AuthRequest (poolName , params );
242
+ String authRequestJson = objectMapper .writeValueAsString (authRequest );
236
243
metrics .recordSuccess ();
237
- return principal ;
244
+ return authRequestJson ;
238
245
} catch (AuthenticationException e ) {
239
246
metrics .recordFailure (errorCode );
240
247
throw e ;
248
+ } catch (JsonProcessingException e ) {
249
+ log .error ("Failed to serialize the auth request" , e );
250
+ metrics .recordFailure (errorCode );
251
+ throw new AuthenticationException (e .getMessage ());
241
252
}
242
253
}
243
254
244
- public String matchPool (Map <String , Object > params ) throws AuthenticationException {
255
+ public String matchPool (Map <String , String > params ) throws AuthenticationException {
245
256
List <String > principals = new ArrayList <>();
246
257
poolMap .forEach ((poolName , compiler ) -> {
247
258
Boolean matched = false ;
@@ -284,32 +295,38 @@ static String parseSHA1FingerPrint(X509Certificate certificate) {
284
295
}
285
296
}
286
297
287
- static Map <String , Object > parseDN (String dn ) throws InvalidNameException {
288
- Map <String , Object > params = new HashMap <>();
298
+ static Map <String , String > parseDN (String dn ) throws InvalidNameException {
299
+ Map <String , String > params = new HashMap <>();
289
300
if (StringUtils .isEmpty (dn )) {
290
301
return params ;
291
302
}
292
303
params .put (DN , dn );
293
304
LdapName ldapName = new LdapName (dn );
294
305
for (Rdn rdn : ldapName .getRdns ()) {
295
306
String rdnType = rdn .getType ().toUpperCase ();
296
- if (DN_KEYS .contains (rdnType )) {
297
- String value = Rdn .escapeValue (rdn .getValue ());
298
- value = value .replace ("\r " , "\\ 0D" );
299
- value = value .replace ("\n " , "\\ 0A" );
300
- params .put (rdnType , value );
301
- }
307
+ String value = Rdn .escapeValue (rdn .getValue ());
308
+ value = value .replace ("\r " , "\\ 0D" );
309
+ value = value .replace ("\n " , "\\ 0A" );
310
+ params .put (rdnType , value );
302
311
}
303
312
304
313
return params ;
305
314
}
306
315
307
- static void parseSAN (X509Certificate certificate , @ NotNull Map <String , Object > map ) {
316
+ static void parseSAN (X509Certificate certificate , @ NotNull Map <String , String > map ) {
308
317
try {
318
+ // byte[] extensionValue = certificate.getExtensionValue("2.5.29.17");
319
+ // TODO How to get the original extension name
309
320
Collection <List <?>> subjectAlternativeNames = certificate .getSubjectAlternativeNames ();
310
321
if (subjectAlternativeNames != null ) {
311
322
List <String > formattedSANList = subjectAlternativeNames .stream ()
312
- .map (list -> getSanName ((int ) list .get (0 )) + ":" + list .get (1 ))
323
+ .map (list -> {
324
+ String sanName = getSanName ((int ) list .get (0 ));
325
+ String sanValue = (String ) list .get (1 );
326
+ map .put (sanName , sanValue );
327
+ sanName = mapSANNames (sanName , sanValue , map );
328
+ return sanName + ":" + sanValue ;
329
+ })
313
330
.collect (Collectors .toList ());
314
331
String formattedSAN = String .join ("," , formattedSANList );
315
332
map .put (SAN , formattedSAN );
@@ -319,10 +336,27 @@ static void parseSAN(X509Certificate certificate, @NotNull Map<String, Object> m
319
336
}
320
337
}
321
338
339
+ static String mapSANNames (String sanName , String sanValue , @ NotNull Map <String , String > map ) {
340
+ String newSanName = sanName ;
341
+ // "RFC822NAME:aaa" -> "EMAIL:aaa,DEVICE_ID:aaa,RFC822NAME:aaa"
342
+ if (sanName .equals ("DNS" )) {
343
+ StrBuilder strBuilder = new StrBuilder ();
344
+ strBuilder .append ("EMAIL:" ).append (sanValue ).append ("," );
345
+ map .put ("EMAIL" , sanValue );
346
+
347
+ // strBuilder.append("DEVICE_ID:").append(sanValue).append(",");
348
+ // map.put("DEVICE_ID", sanValue);
349
+
350
+ strBuilder .append (sanName );
351
+ newSanName = strBuilder .toString ();
352
+ }
353
+ return newSanName ;
354
+ }
355
+
322
356
private static String getSanName (int type ) {
323
357
return switch (type ) {
324
358
case 0 -> "OTHERNAME" ;
325
- case 1 -> "EMAIL " ;
359
+ case 1 -> "RFC822NAME " ;
326
360
case 2 -> "DNS" ;
327
361
case 3 -> "X400" ;
328
362
case 4 -> "DIR" ;
0 commit comments