2727import jakarta .servlet .http .HttpServletResponse ;
2828import java .io .IOException ;
2929import java .lang .invoke .MethodHandles ;
30- import java .nio .ByteBuffer ;
3130import java .security .InvalidKeyException ;
3231import java .security .Principal ;
3332import java .security .PublicKey ;
3433import java .security .SignatureException ;
3534import java .time .Instant ;
3635import java .util .Base64 ;
37- import java .util .List ;
3836import java .util .Map ;
3937import java .util .Optional ;
40- import java .util .Set ;
4138import java .util .concurrent .ConcurrentHashMap ;
4239import java .util .concurrent .TimeUnit ;
4340import java .util .function .BiConsumer ;
4744import org .apache .solr .common .params .ModifiableSolrParams ;
4845import org .apache .solr .common .util .ExecutorUtil ;
4946import org .apache .solr .common .util .NamedList ;
50- import org .apache .solr .common .util .StrUtils ;
5147import org .apache .solr .common .util .SuppressForbidden ;
5248import org .apache .solr .core .CoreContainer ;
5349import org .apache .solr .request .SolrRequestInfo ;
6056public class PKIAuthenticationPlugin extends AuthenticationPlugin
6157 implements HttpClientBuilderPlugin {
6258
63- public static final String ACCEPT_VERSIONS = "solr.pki.acceptVersions" ;
64- public static final String SEND_VERSION = "solr.pki.sendVersion" ;
65-
6659 /**
6760 * Mark the current thread as a server thread and set a flag in SolrRequestInfo to indicate you
6861 * want to send a request as the server identity instead of as the authenticated user.
@@ -80,24 +73,15 @@ public static void withServerIdentity(final boolean enabled) {
8073
8174 private static final Logger log = LoggerFactory .getLogger (MethodHandles .lookup ().lookupClass ());
8275
83- /** If a number has less than this number of digits, it'll not be considered a timestamp. */
84- private static final int MIN_TIMESTAMP_DIGITS = 10 ; // a timestamp of 9999999999 is year 1970
85-
86- /** If a number has more than this number of digits, it'll not be considered a timestamp. */
87- private static final int MAX_TIMESTAMP_DIGITS = 13 ; // a timestamp of 9999999999999 is year 2286
88-
8976 private final Map <String , PublicKey > keyCache = new ConcurrentHashMap <>();
9077 private final PublicKeyHandler publicKeyHandler ;
9178 private final CoreContainer cores ;
9279 private final LoadingCache <String , PKIHeaderData > validatedHeaderCache ;
93- private final LoadingCache <String , String > generatedV1TokenCache ;
9480 private final LoadingCache <String , String > generatedV2TokenCache ;
9581 private static final int MAX_VALIDITY = Integer .getInteger ("pkiauth.ttl" , 10000 );
9682 private final String myNodeName ;
9783 private boolean interceptorRegistered = false ;
9884
99- private boolean acceptPkiV1 = false ;
100-
10185 public boolean isInterceptorRegistered () {
10286 return interceptorRegistered ;
10387 }
@@ -130,34 +114,12 @@ public PKIAuthenticationPlugin(
130114 // runway for requests to come in to trigger an asynchronous-refresh before expiry causes a
131115 // synchronous-refresh.
132116 long shouldRefreshTime = Math .max (1 , expireAfterTime / 2 );
133- generatedV1TokenCache =
134- Caffeine .newBuilder ()
135- .maximumSize (100 )
136- .refreshAfterWrite (shouldRefreshTime , TimeUnit .MILLISECONDS )
137- .expireAfterWrite (expireAfterTime , TimeUnit .MILLISECONDS )
138- .build (this ::generateToken );
139117 generatedV2TokenCache =
140118 Caffeine .newBuilder ()
141119 .maximumSize (100 )
142120 .refreshAfterWrite (shouldRefreshTime , TimeUnit .MILLISECONDS )
143121 .expireAfterWrite (expireAfterTime , TimeUnit .MILLISECONDS )
144122 .build (this ::generateTokenV2 );
145-
146- Set <String > knownPkiVersions = Set .of ("v1" , "v2" );
147- // We always accept v2 even if it is not specified
148- String [] versions = System .getProperty (ACCEPT_VERSIONS , "v2" ).split ("," );
149- for (String version : versions ) {
150- if (knownPkiVersions .contains (version ) == false ) {
151- log .warn ("Unknown protocol version [{}] specified in {}" , version , ACCEPT_VERSIONS );
152- }
153- if ("v1" .equals (version )) {
154- log .warn (
155- "System setting {} includes the deprecated v1, which should only be used for compatibility during rolling upgrades. "
156- + "After all servers have been upgraded, consider disabling this compatability layer." ,
157- ACCEPT_VERSIONS );
158- acceptPkiV1 = true ;
159- }
160- }
161123 }
162124
163125 @ Override
@@ -171,35 +133,25 @@ public boolean doAuthenticate(
171133 // Getting the received time must be the first thing we do, processing the request can take time
172134 long receivedTime = System .currentTimeMillis ();
173135
174- PKIHeaderData headerData = null ;
175136 String headerV2 = request .getHeader (HEADER_V2 );
176- String headerV1 = request .getHeader (HEADER );
177- if (headerV1 == null && headerV2 == null ) {
178- return sendError (response , true , "No PKI auth header was provided" );
179- } else if (headerV2 != null ) {
180- // Try V2 first
181- int nodeNameEnd = headerV2 .indexOf (' ' );
182- if (nodeNameEnd <= 0 ) {
183- // Do not log the value as it is likely gibberish
184- return sendError (response , true , "Could not parse node name from SolrAuthV2 header." );
185- }
137+ if (headerV2 == null ) {
138+ return sendError (response , "No PKI auth header was provided" );
139+ }
186140
187- headerData = validatedHeaderCache .get (headerV2 );
188- } else if (headerV1 != null && acceptPkiV1 ) {
189- List <String > authInfo = StrUtils .splitWS (headerV1 , false );
190- if (authInfo .size () != 2 ) {
191- // We really shouldn't be logging and returning this, but we did it before so keep that
192- return sendError (response , false , "Invalid SolrAuth header: " + headerV1 );
193- }
194- headerData = decipherHeader (authInfo .get (0 ), authInfo .get (1 ));
141+ int nodeNameEnd = headerV2 .indexOf (' ' );
142+ if (nodeNameEnd <= 0 ) {
143+ // Do not log the value as it is likely gibberish
144+ return sendError (response , "Could not parse node name from SolrAuthV2 header." );
195145 }
196146
147+ PKIHeaderData headerData = validatedHeaderCache .get (headerV2 );
148+
197149 if (headerData == null ) {
198- return sendError (response , true , "Could not validate PKI header." );
150+ return sendError (response , "Could not validate PKI header." );
199151 }
200152 long elapsed = receivedTime - headerData .timestamp ;
201153 if (elapsed > MAX_VALIDITY ) {
202- return sendError (response , true , "Expired key request timestamp, elapsed=" + elapsed );
154+ return sendError (response , "Expired key request timestamp, elapsed=" + elapsed );
203155 }
204156
205157 final Principal principal =
@@ -217,16 +169,14 @@ public boolean doAuthenticate(
217169 * authentication
218170 *
219171 * @param response the response to set error status with
220- * @param v2 whether this authentication used the v1 or v2 header (true if v2)
221- * @param message the message to log and send back to client. do not include anyhting sensitive
172+ * @param message the message to log and send back to client. do not include anything sensitive
222173 * here about server state
223174 * @return false to chain with calls from authenticate
224175 */
225- private boolean sendError (HttpServletResponse response , boolean v2 , String message )
226- throws IOException {
176+ private boolean sendError (HttpServletResponse response , String message ) throws IOException {
227177 numErrors .inc ();
228178 log .error (message );
229- response .setHeader (HttpHeader .WWW_AUTHENTICATE .asString (), v2 ? HEADER_V2 : HEADER );
179+ response .setHeader (HttpHeader .WWW_AUTHENTICATE .asString (), HEADER_V2 );
230180 response .sendError (HttpServletResponse .SC_UNAUTHORIZED , message );
231181 return false ;
232182 }
@@ -304,53 +254,6 @@ private PKIHeaderData validateSignature(String data, byte[] sig, PublicKey key,
304254 }
305255 }
306256
307- private PKIHeaderData decipherHeader (String nodeName , String cipherBase64 ) {
308- PublicKey key = getOrFetchPublicKey (nodeName );
309-
310- PKIHeaderData header = parseCipher (cipherBase64 , key , false );
311- if (header == null ) {
312- log .warn ("Failed to decrypt header, trying after refreshing the key " );
313- key = fetchPublicKeyFromRemote (nodeName );
314- return parseCipher (cipherBase64 , key , true );
315- } else {
316- return header ;
317- }
318- }
319-
320- @ VisibleForTesting
321- static PKIHeaderData parseCipher (String cipher , PublicKey key , boolean isRetry ) {
322- byte [] bytes ;
323- try {
324- bytes = CryptoKeys .decryptRSA (Base64 .getDecoder ().decode (cipher ), key );
325- } catch (Exception e ) {
326- if (isRetry ) {
327- log .error ("Decryption failed on retry, key must be wrong" , e );
328- } else {
329- log .info ("Decryption failed on first attempt, will retry" , e );
330- }
331- return null ;
332- }
333- String s = new String (bytes , UTF_8 ).trim ();
334- int splitPoint = s .lastIndexOf (' ' );
335- int timestampDigits = s .length () - 1 - splitPoint ;
336- if (splitPoint == -1
337- || timestampDigits < MIN_TIMESTAMP_DIGITS
338- || timestampDigits > MAX_TIMESTAMP_DIGITS ) {
339- log .warn ("Invalid cipher {} deciphered data {}" , cipher , s );
340- return null ;
341- }
342- PKIHeaderData headerData = new PKIHeaderData ();
343- try {
344- headerData .timestamp = Long .parseLong (s .substring (splitPoint + 1 ));
345- headerData .userName = s .substring (0 , splitPoint );
346- log .debug ("Successfully decrypted header {} {}" , headerData .userName , headerData .timestamp );
347- return headerData ;
348- } catch (NumberFormatException e ) {
349- log .warn ("Invalid cipher {}" , cipher );
350- return null ;
351- }
352- }
353-
354257 private boolean isInLiveNodes (String nodeName ) {
355258 return cores
356259 .getZkController ()
@@ -439,16 +342,10 @@ public void onBegin(Request request) {
439342 log .trace ("onBegin: {}" , request );
440343
441344 final Optional <String > preFetchedUser = getUserFromJettyRequest (request );
442- if ("v1" .equals (System .getProperty (SEND_VERSION ))) {
443- preFetchedUser
444- .map (generatedV1TokenCache ::get )
445- .ifPresent (token -> request .headers (httpFields -> httpFields .add (HEADER , token )));
446- } else {
447- preFetchedUser
448- .map (generatedV2TokenCache ::get )
449- .ifPresent (
450- token -> request .headers (httpFields -> httpFields .add (HEADER_V2 , token )));
451- }
345+ preFetchedUser
346+ .map (generatedV2TokenCache ::get )
347+ .ifPresent (
348+ token -> request .headers (httpFields -> httpFields .add (HEADER_V2 , token )));
452349 }
453350
454351 private void cachePreFetchedUserOnJettyRequest (Request request ) {
@@ -492,17 +389,6 @@ private Optional<String> getUser() {
492389 }
493390 }
494391
495- @ SuppressForbidden (reason = "Needs currentTimeMillis to set current time in header" )
496- private String generateToken (String usr ) {
497- assert usr != null ;
498- String s = usr + " " + System .currentTimeMillis ();
499- byte [] payload = s .getBytes (UTF_8 );
500- byte [] payloadCipher = publicKeyHandler .getKeyPair ().encrypt (ByteBuffer .wrap (payload ));
501- String base64Cipher = Base64 .getEncoder ().encodeToString (payloadCipher );
502- log .trace ("generateToken: usr={} token={}" , usr , base64Cipher );
503- return myNodeName + " " + base64Cipher ;
504- }
505-
506392 private String generateTokenV2 (String user ) {
507393 assert user != null ;
508394 String s = myNodeName + " " + user + " " + Instant .now ().toEpochMilli ();
@@ -515,15 +401,9 @@ private String generateTokenV2(String user) {
515401
516402 @ VisibleForTesting
517403 void setHeader (BiConsumer <String , String > httpRequest ) {
518- if ("v1" .equals (System .getProperty (SEND_VERSION ))) {
519- getUser ()
520- .map (generatedV1TokenCache ::get )
521- .ifPresent (token -> httpRequest .accept (HEADER , token ));
522- } else {
523- getUser ()
524- .map (generatedV2TokenCache ::get )
525- .ifPresent (token -> httpRequest .accept (HEADER_V2 , token ));
526- }
404+ getUser ()
405+ .map (generatedV2TokenCache ::get )
406+ .ifPresent (token -> httpRequest .accept (HEADER_V2 , token ));
527407 }
528408
529409 boolean isSolrThread () {
@@ -545,7 +425,6 @@ public String getPublicKey() {
545425 return publicKeyHandler .getKeyPair ().getPublicKeyStr ();
546426 }
547427
548- public static final String HEADER = "SolrAuth" ;
549428 public static final String HEADER_V2 = "SolrAuthV2" ;
550429 public static final String NODE_IS_USER = "$" ;
551430 // special principal to denote the cluster member
0 commit comments