[Security] Handle cache expiry in token service (elastic/x-pack-elasticsearch#3565)

* [Security] Handle cache expiry in token service

The keyCache on TokenService.KeyAndCache has a 60 minute expiry.
If the token service was idle for more than 60 minutes, the current
key would be expired and it would then fail to generate user tokens.

Original commit: elastic/x-pack-elasticsearch@fd98130a27
This commit is contained in:
Tim Vernum 2018-01-17 13:04:59 +10:00 committed by GitHub
parent 29663c1f38
commit b0552e1c6e
2 changed files with 37 additions and 18 deletions

View File

@ -427,7 +427,11 @@ public final class TokenService extends AbstractComponent {
private Cipher getEncryptionCipher(byte[] iv, KeyAndCache keyAndCache) throws GeneralSecurityException { private Cipher getEncryptionCipher(byte[] iv, KeyAndCache keyAndCache) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(ENCRYPTION_CIPHER); Cipher cipher = Cipher.getInstance(ENCRYPTION_CIPHER);
BytesKey salt = keyAndCache.getSalt(); BytesKey salt = keyAndCache.getSalt();
cipher.init(Cipher.ENCRYPT_MODE, keyAndCache.getKey(salt), new GCMParameterSpec(128, iv), secureRandom); try {
cipher.init(Cipher.ENCRYPT_MODE, keyAndCache.getOrComputeKey(salt), new GCMParameterSpec(128, iv), secureRandom);
} catch (ExecutionException e) {
throw new ElasticsearchSecurityException("Failed to compute secret key for active salt", e);
}
cipher.updateAAD(currentVersionBytes); cipher.updateAAD(currentVersionBytes);
cipher.updateAAD(salt.bytes); cipher.updateAAD(salt.bytes);
return cipher; return cipher;
@ -776,6 +780,13 @@ public final class TokenService extends AbstractComponent {
}); });
} }
/**
* For testing
*/
void clearActiveKeyCache() {
this.keyCache.activeKeyCache.keyCache.invalidateAll();
}
static final class KeyAndTimestamp implements Writeable { static final class KeyAndTimestamp implements Writeable {
private final SecureString key; private final SecureString key;
private final long timestamp; private final long timestamp;

View File

@ -43,8 +43,10 @@ import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.function.Consumer; import java.util.function.Consumer;
import static java.time.Clock.systemUTC;
import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes; import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
@ -99,8 +101,7 @@ public class TokenServiceTests extends ESTestCase {
} }
public void testAttachAndGetToken() throws Exception { public void testAttachAndGetToken() throws Exception {
TokenService tokenService = TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
final UserToken token = tokenService.createUserToken(authentication); final UserToken token = tokenService.createUserToken(authentication);
assertNotNull(token); assertNotNull(token);
@ -117,7 +118,7 @@ public class TokenServiceTests extends ESTestCase {
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
// verify a second separate token service with its own salt can also verify // verify a second separate token service with its own salt can also verify
TokenService anotherService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService
, clusterService); , clusterService);
anotherService.refreshMetaData(tokenService.getTokenMetaData()); anotherService.refreshMetaData(tokenService.getTokenMetaData());
PlainActionFuture<UserToken> future = new PlainActionFuture<>(); PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -128,8 +129,7 @@ public class TokenServiceTests extends ESTestCase {
} }
public void testRotateKey() throws Exception { public void testRotateKey() throws Exception {
TokenService tokenService = TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
final UserToken token = tokenService.createUserToken(authentication); final UserToken token = tokenService.createUserToken(authentication);
assertNotNull(token); assertNotNull(token);
@ -175,13 +175,12 @@ public class TokenServiceTests extends ESTestCase {
} }
public void testKeyExchange() throws Exception { public void testKeyExchange() throws Exception {
TokenService tokenService = TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService);
int numRotations = 0;randomIntBetween(1, 5); int numRotations = 0;randomIntBetween(1, 5);
for (int i = 0; i < numRotations; i++) { for (int i = 0; i < numRotations; i++) {
rotateKeys(tokenService); rotateKeys(tokenService);
} }
TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService,
clusterService); clusterService);
otherTokenService.refreshMetaData(tokenService.getTokenMetaData()); otherTokenService.refreshMetaData(tokenService.getTokenMetaData());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
@ -210,8 +209,7 @@ public class TokenServiceTests extends ESTestCase {
} }
public void testPruneKeys() throws Exception { public void testPruneKeys() throws Exception {
TokenService tokenService = TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
final UserToken token = tokenService.createUserToken(authentication); final UserToken token = tokenService.createUserToken(authentication);
assertNotNull(token); assertNotNull(token);
@ -267,8 +265,7 @@ public class TokenServiceTests extends ESTestCase {
} }
public void testPassphraseWorks() throws Exception { public void testPassphraseWorks() throws Exception {
TokenService tokenService = TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
final UserToken token = tokenService.createUserToken(authentication); final UserToken token = tokenService.createUserToken(authentication);
assertNotNull(token); assertNotNull(token);
@ -285,7 +282,7 @@ public class TokenServiceTests extends ESTestCase {
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
// verify a second separate token service with its own passphrase cannot verify // verify a second separate token service with its own passphrase cannot verify
TokenService anotherService = new TokenService(Settings.EMPTY, Clock.systemUTC(), client, lifecycleService, TokenService anotherService = new TokenService(Settings.EMPTY, systemUTC(), client, lifecycleService,
clusterService); clusterService);
PlainActionFuture<UserToken> future = new PlainActionFuture<>(); PlainActionFuture<UserToken> future = new PlainActionFuture<>();
anotherService.getAndValidateToken(requestContext, future); anotherService.getAndValidateToken(requestContext, future);
@ -293,10 +290,21 @@ public class TokenServiceTests extends ESTestCase {
} }
} }
public void testGetTokenWhenKeyCacheHasExpired() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
UserToken token = tokenService.createUserToken(authentication);
assertThat(tokenService.getUserTokenString(token), notNullValue());
tokenService.clearActiveKeyCache();
assertThat(tokenService.getUserTokenString(token), notNullValue());
}
public void testInvalidatedToken() throws Exception { public void testInvalidatedToken() throws Exception {
when(lifecycleService.isSecurityIndexExisting()).thenReturn(true); when(lifecycleService.isSecurityIndexExisting()).thenReturn(true);
TokenService tokenService = TokenService tokenService =
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService); new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
final UserToken token = tokenService.createUserToken(authentication); final UserToken token = tokenService.createUserToken(authentication);
assertNotNull(token); assertNotNull(token);
@ -382,7 +390,7 @@ public class TokenServiceTests extends ESTestCase {
TokenService tokenService = new TokenService(Settings.builder() TokenService tokenService = new TokenService(Settings.builder()
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false) .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
.build(), .build(),
Clock.systemUTC(), client, lifecycleService, clusterService); systemUTC(), client, lifecycleService, clusterService);
IllegalStateException e = expectThrows(IllegalStateException.class, () -> tokenService.createUserToken(null)); IllegalStateException e = expectThrows(IllegalStateException.class, () -> tokenService.createUserToken(null));
assertEquals("tokens are not enabled", e.getMessage()); assertEquals("tokens are not enabled", e.getMessage());
@ -424,7 +432,7 @@ public class TokenServiceTests extends ESTestCase {
final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32); final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32);
final byte[] randomBytes = new byte[numBytes]; final byte[] randomBytes = new byte[numBytes];
random().nextBytes(randomBytes); random().nextBytes(randomBytes);
TokenService tokenService = new TokenService(Settings.EMPTY, Clock.systemUTC(), client, lifecycleService, clusterService); TokenService tokenService = new TokenService(Settings.EMPTY, systemUTC(), client, lifecycleService, clusterService);
ThreadContext requestContext = new ThreadContext(Settings.EMPTY); ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(randomBytes)); requestContext.putHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(randomBytes));
@ -438,7 +446,7 @@ public class TokenServiceTests extends ESTestCase {
public void testIndexNotAvailable() throws Exception { public void testIndexNotAvailable() throws Exception {
TokenService tokenService = TokenService tokenService =
new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService); new TokenService(tokenServiceEnabledSettings, systemUTC(), client, lifecycleService, clusterService);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
final UserToken token = tokenService.createUserToken(authentication); final UserToken token = tokenService.createUserToken(authentication);
assertNotNull(token); assertNotNull(token);