45
45
import java .io .IOException ;
46
46
import java .nio .charset .StandardCharsets ;
47
47
import java .time .Clock ;
48
+ import java .time .Duration ;
48
49
import java .time .Instant ;
49
50
import java .time .temporal .ChronoUnit ;
50
51
import java .util .Arrays ;
56
57
57
58
import static org .elasticsearch .xpack .core .security .authz .store .ReservedRolesStore .SUPERUSER_ROLE_DESCRIPTOR ;
58
59
import static org .hamcrest .Matchers .arrayContaining ;
60
+ import static org .hamcrest .Matchers .containsString ;
59
61
import static org .hamcrest .Matchers .equalTo ;
60
62
import static org .hamcrest .Matchers .instanceOf ;
61
63
import static org .hamcrest .Matchers .is ;
@@ -159,25 +161,110 @@ public void testAuthenticationIsSkippedIfLicenseDoesNotAllowIt() throws Exceptio
159
161
assertThat (auth .getUser (), nullValue ());
160
162
}
161
163
162
- public void mockKeyDocument (ApiKeyService service , String id , String key , User user ) throws IOException {
163
- final Authentication authentication = new Authentication (user , new RealmRef ("realm1" , "native" , "node01" ), null , Version .CURRENT );
164
- final XContentBuilder docSource = service .newDocument (new SecureString (key .toCharArray ()), "test" , authentication ,
165
- Collections .singleton (SUPERUSER_ROLE_DESCRIPTOR ), Instant .now (), Instant .now ().plusSeconds (3600 ), null , Version .CURRENT );
164
+ public void testAuthenticationFailureWithInvalidatedApiKey () throws Exception {
165
+ final Settings settings = Settings .builder ().put (XPackSettings .API_KEY_SERVICE_ENABLED_SETTING .getKey (), true ).build ();
166
+ final ApiKeyService service = createApiKeyService (settings );
167
+
168
+ final String id = randomAlphaOfLength (12 );
169
+ final String key = randomAlphaOfLength (16 );
170
+
171
+ mockKeyDocument (service , id , key , new User ("hulk" , "superuser" ), true , Duration .ofSeconds (3600 ));
172
+
173
+ final AuthenticationResult auth = tryAuthenticate (service , id , key );
174
+ assertThat (auth .getStatus (), is (AuthenticationResult .Status .CONTINUE ));
175
+ assertThat (auth .getUser (), nullValue ());
176
+ assertThat (auth .getMessage (), containsString ("invalidated" ));
177
+ }
178
+
179
+ public void testAuthenticationFailureWithInvalidCredentials () throws Exception {
180
+ final Settings settings = Settings .builder ().put (XPackSettings .API_KEY_SERVICE_ENABLED_SETTING .getKey (), true ).build ();
181
+ final ApiKeyService service = createApiKeyService (settings );
182
+
183
+ final String id = randomAlphaOfLength (12 );
184
+ final String realKey = randomAlphaOfLength (16 );
185
+ final String wrongKey = "#" + realKey .substring (1 );
186
+
187
+ mockKeyDocument (service , id , realKey , new User ("hulk" , "superuser" ));
188
+
189
+ final AuthenticationResult auth = tryAuthenticate (service , id , wrongKey );
190
+ assertThat (auth .getStatus (), is (AuthenticationResult .Status .CONTINUE ));
191
+ assertThat (auth .getUser (), nullValue ());
192
+ assertThat (auth .getMessage (), containsString ("invalid credentials" ));
193
+ }
194
+
195
+ public void testAuthenticationFailureWithExpiredKey () throws Exception {
196
+ final Settings settings = Settings .builder ().put (XPackSettings .API_KEY_SERVICE_ENABLED_SETTING .getKey (), true ).build ();
197
+ final ApiKeyService service = createApiKeyService (settings );
166
198
199
+ final String id = randomAlphaOfLength (12 );
200
+ final String key = randomAlphaOfLength (16 );
201
+
202
+ mockKeyDocument (service , id , key , new User ("hulk" , "superuser" ), false , Duration .ofSeconds (-1 ));
203
+
204
+ final AuthenticationResult auth = tryAuthenticate (service , id , key );
205
+ assertThat (auth .getStatus (), is (AuthenticationResult .Status .CONTINUE ));
206
+ assertThat (auth .getUser (), nullValue ());
207
+ assertThat (auth .getMessage (), containsString ("expired" ));
208
+ }
209
+
210
+ /**
211
+ * We cache valid and invalid responses. This test verifies that we handle these correctly.
212
+ */
213
+ public void testMixingValidAndInvalidCredentials () throws Exception {
214
+ final Settings settings = Settings .builder ().put (XPackSettings .API_KEY_SERVICE_ENABLED_SETTING .getKey (), true ).build ();
215
+ final ApiKeyService service = createApiKeyService (settings );
216
+
217
+ final String id = randomAlphaOfLength (12 );
218
+ final String realKey = randomAlphaOfLength (16 );
219
+
220
+ mockKeyDocument (service , id , realKey , new User ("hulk" , "superuser" ));
221
+
222
+ for (int i = 0 ; i < 3 ; i ++) {
223
+ final String wrongKey = "=" + randomAlphaOfLength (14 ) + "@" ;
224
+ AuthenticationResult auth = tryAuthenticate (service , id , wrongKey );
225
+ assertThat (auth .getStatus (), is (AuthenticationResult .Status .CONTINUE ));
226
+ assertThat (auth .getUser (), nullValue ());
227
+ assertThat (auth .getMessage (), containsString ("invalid credentials" ));
228
+
229
+ auth = tryAuthenticate (service , id , realKey );
230
+ assertThat (auth .getStatus (), is (AuthenticationResult .Status .SUCCESS ));
231
+ assertThat (auth .getUser (), notNullValue ());
232
+ assertThat (auth .getUser ().principal (), is ("hulk" ));
233
+ }
234
+ }
235
+
236
+ private void mockKeyDocument (ApiKeyService service , String id , String key , User user ) throws IOException {
237
+ mockKeyDocument (service , id , key , user , false , Duration .ofSeconds (3600 ));
238
+ }
239
+
240
+ private void mockKeyDocument (ApiKeyService service , String id , String key , User user , boolean invalidated ,
241
+ Duration expiry ) throws IOException {
242
+ final Authentication authentication = new Authentication (user , new RealmRef ("realm1" , "native" ,
243
+ "node01" ), null , Version .CURRENT );
244
+ XContentBuilder docSource = service .newDocument (new SecureString (key .toCharArray ()), "test" , authentication ,
245
+ Collections .singleton (SUPERUSER_ROLE_DESCRIPTOR ), Instant .now (), Instant .now ().plus (expiry ), null ,
246
+ Version .CURRENT );
247
+ if (invalidated ) {
248
+ Map <String , Object > map = XContentHelper .convertToMap (BytesReference .bytes (docSource ), true , XContentType .JSON ).v2 ();
249
+ map .put ("api_key_invalidated" , true );
250
+ docSource = XContentBuilder .builder (XContentType .JSON .xContent ()).map (map );
251
+ }
167
252
SecurityMocks .mockGetRequest (client , id , BytesReference .bytes (docSource ));
168
253
}
169
254
170
255
private AuthenticationResult tryAuthenticate (ApiKeyService service , String id , String key ) throws Exception {
171
256
final ThreadContext threadContext = threadPool .getThreadContext ();
172
- final String header = "ApiKey " + Base64 .getEncoder ().encodeToString ((id + ":" + key ).getBytes (StandardCharsets .UTF_8 ));
173
- threadContext .putHeader ("Authorization" , header );
257
+ try (ThreadContext .StoredContext ignore = threadContext .stashContext ()) {
258
+ final String header = "ApiKey " + Base64 .getEncoder ().encodeToString ((id + ":" + key ).getBytes (StandardCharsets .UTF_8 ));
259
+ threadContext .putHeader ("Authorization" , header );
174
260
175
- final PlainActionFuture <AuthenticationResult > future = new PlainActionFuture <>();
176
- service .authenticateWithApiKeyIfPresent (threadContext , future );
261
+ final PlainActionFuture <AuthenticationResult > future = new PlainActionFuture <>();
262
+ service .authenticateWithApiKeyIfPresent (threadContext , future );
177
263
178
- final AuthenticationResult auth = future .get ();
179
- assertThat (auth , notNullValue ());
180
- return auth ;
264
+ final AuthenticationResult auth = future .get ();
265
+ assertThat (auth , notNullValue ());
266
+ return auth ;
267
+ }
181
268
}
182
269
183
270
public void testValidateApiKey () throws Exception {
@@ -186,6 +273,7 @@ public void testValidateApiKey() throws Exception {
186
273
final char [] hash = hasher .hash (new SecureString (apiKey .toCharArray ()));
187
274
188
275
Map <String , Object > sourceMap = new HashMap <>();
276
+ sourceMap .put ("doc_type" , "api_key" );
189
277
sourceMap .put ("api_key_hash" , new String (hash ));
190
278
sourceMap .put ("role_descriptors" , Collections .singletonMap ("a role" , Collections .singletonMap ("cluster" , "all" )));
191
279
sourceMap .put ("limited_by_role_descriptors" , Collections .singletonMap ("limited role" , Collections .singletonMap ("cluster" , "all" )));
@@ -200,7 +288,7 @@ public void testValidateApiKey() throws Exception {
200
288
ApiKeyService .ApiKeyCredentials creds =
201
289
new ApiKeyService .ApiKeyCredentials (randomAlphaOfLength (12 ), new SecureString (apiKey .toCharArray ()));
202
290
PlainActionFuture <AuthenticationResult > future = new PlainActionFuture <>();
203
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
291
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
204
292
AuthenticationResult result = future .get ();
205
293
assertNotNull (result );
206
294
assertTrue (result .isAuthenticated ());
@@ -214,7 +302,7 @@ public void testValidateApiKey() throws Exception {
214
302
215
303
sourceMap .put ("expiration_time" , Clock .systemUTC ().instant ().plus (1L , ChronoUnit .HOURS ).toEpochMilli ());
216
304
future = new PlainActionFuture <>();
217
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
305
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
218
306
result = future .get ();
219
307
assertNotNull (result );
220
308
assertTrue (result .isAuthenticated ());
@@ -228,23 +316,23 @@ public void testValidateApiKey() throws Exception {
228
316
229
317
sourceMap .put ("expiration_time" , Clock .systemUTC ().instant ().minus (1L , ChronoUnit .HOURS ).toEpochMilli ());
230
318
future = new PlainActionFuture <>();
231
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
319
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
232
320
result = future .get ();
233
321
assertNotNull (result );
234
322
assertFalse (result .isAuthenticated ());
235
323
236
324
sourceMap .remove ("expiration_time" );
237
325
creds = new ApiKeyService .ApiKeyCredentials (randomAlphaOfLength (12 ), new SecureString (randomAlphaOfLength (15 ).toCharArray ()));
238
326
future = new PlainActionFuture <>();
239
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
327
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
240
328
result = future .get ();
241
329
assertNotNull (result );
242
330
assertFalse (result .isAuthenticated ());
243
331
244
332
sourceMap .put ("api_key_invalidated" , true );
245
333
creds = new ApiKeyService .ApiKeyCredentials (randomAlphaOfLength (12 ), new SecureString (randomAlphaOfLength (15 ).toCharArray ()));
246
334
future = new PlainActionFuture <>();
247
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
335
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
248
336
result = future .get ();
249
337
assertNotNull (result );
250
338
assertFalse (result .isAuthenticated ());
@@ -344,6 +432,7 @@ public void testApiKeyCache() {
344
432
final char [] hash = hasher .hash (new SecureString (apiKey .toCharArray ()));
345
433
346
434
Map <String , Object > sourceMap = new HashMap <>();
435
+ sourceMap .put ("doc_type" , "api_key" );
347
436
sourceMap .put ("api_key_hash" , new String (hash ));
348
437
sourceMap .put ("role_descriptors" , Collections .singletonMap ("a role" , Collections .singletonMap ("cluster" , "all" )));
349
438
sourceMap .put ("limited_by_role_descriptors" , Collections .singletonMap ("limited role" , Collections .singletonMap ("cluster" , "all" )));
@@ -356,7 +445,7 @@ public void testApiKeyCache() {
356
445
ApiKeyService service = createApiKeyService (Settings .EMPTY );
357
446
ApiKeyCredentials creds = new ApiKeyCredentials (randomAlphaOfLength (12 ), new SecureString (apiKey .toCharArray ()));
358
447
PlainActionFuture <AuthenticationResult > future = new PlainActionFuture <>();
359
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
448
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
360
449
AuthenticationResult result = future .actionGet ();
361
450
assertThat (result .isAuthenticated (), is (true ));
362
451
CachedApiKeyHashResult cachedApiKeyHashResult = service .getFromCache (creds .getId ());
@@ -365,7 +454,7 @@ public void testApiKeyCache() {
365
454
366
455
creds = new ApiKeyCredentials (creds .getId (), new SecureString ("foobar" .toCharArray ()));
367
456
future = new PlainActionFuture <>();
368
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
457
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
369
458
result = future .actionGet ();
370
459
assertThat (result .isAuthenticated (), is (false ));
371
460
final CachedApiKeyHashResult shouldBeSame = service .getFromCache (creds .getId ());
@@ -375,7 +464,7 @@ public void testApiKeyCache() {
375
464
sourceMap .put ("api_key_hash" , new String (hasher .hash (new SecureString ("foobar" .toCharArray ()))));
376
465
creds = new ApiKeyCredentials (randomAlphaOfLength (12 ), new SecureString ("foobar1" .toCharArray ()));
377
466
future = new PlainActionFuture <>();
378
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
467
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
379
468
result = future .actionGet ();
380
469
assertThat (result .isAuthenticated (), is (false ));
381
470
cachedApiKeyHashResult = service .getFromCache (creds .getId ());
@@ -384,15 +473,15 @@ public void testApiKeyCache() {
384
473
385
474
creds = new ApiKeyCredentials (creds .getId (), new SecureString ("foobar2" .toCharArray ()));
386
475
future = new PlainActionFuture <>();
387
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
476
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
388
477
result = future .actionGet ();
389
478
assertThat (result .isAuthenticated (), is (false ));
390
479
assertThat (service .getFromCache (creds .getId ()), not (sameInstance (cachedApiKeyHashResult )));
391
480
assertThat (service .getFromCache (creds .getId ()).success , is (false ));
392
481
393
482
creds = new ApiKeyCredentials (creds .getId (), new SecureString ("foobar" .toCharArray ()));
394
483
future = new PlainActionFuture <>();
395
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
484
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
396
485
result = future .actionGet ();
397
486
assertThat (result .isAuthenticated (), is (true ));
398
487
assertThat (service .getFromCache (creds .getId ()), not (sameInstance (cachedApiKeyHashResult )));
@@ -408,6 +497,7 @@ public void testApiKeyCacheDisabled() {
408
497
.build ();
409
498
410
499
Map <String , Object > sourceMap = new HashMap <>();
500
+ sourceMap .put ("doc_type" , "api_key" );
411
501
sourceMap .put ("api_key_hash" , new String (hash ));
412
502
sourceMap .put ("role_descriptors" , Collections .singletonMap ("a role" , Collections .singletonMap ("cluster" , "all" )));
413
503
sourceMap .put ("limited_by_role_descriptors" , Collections .singletonMap ("limited role" , Collections .singletonMap ("cluster" , "all" )));
@@ -420,7 +510,7 @@ public void testApiKeyCacheDisabled() {
420
510
ApiKeyService service = createApiKeyService (settings );
421
511
ApiKeyCredentials creds = new ApiKeyCredentials (randomAlphaOfLength (12 ), new SecureString (apiKey .toCharArray ()));
422
512
PlainActionFuture <AuthenticationResult > future = new PlainActionFuture <>();
423
- service .validateApiKeyCredentials (sourceMap , creds , Clock .systemUTC (), future );
513
+ service .validateApiKeyCredentials (creds . getId (), sourceMap , creds , Clock .systemUTC (), future );
424
514
AuthenticationResult result = future .actionGet ();
425
515
assertThat (result .isAuthenticated (), is (true ));
426
516
CachedApiKeyHashResult cachedApiKeyHashResult = service .getFromCache (creds .getId ());
0 commit comments