Add CryptoService to support signing and encryption operations
This changes the SignatureService into a CryptoService that now supports encryption operations in addition to the signing operations. The encryption leverages the existing system key and uses it with AES. Closes elastic/elasticsearch#805 Original commit: elastic/x-pack-elasticsearch@a792ed4a54
This commit is contained in:
parent
39f587a497
commit
bff95d9ca1
|
@ -14,7 +14,7 @@ import org.elasticsearch.shield.authc.AuthenticationModule;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationModule;
|
import org.elasticsearch.shield.authz.AuthorizationModule;
|
||||||
import org.elasticsearch.shield.license.LicenseModule;
|
import org.elasticsearch.shield.license.LicenseModule;
|
||||||
import org.elasticsearch.shield.rest.ShieldRestModule;
|
import org.elasticsearch.shield.rest.ShieldRestModule;
|
||||||
import org.elasticsearch.shield.signature.SignatureModule;
|
import org.elasticsearch.shield.crypto.CryptoModule;
|
||||||
import org.elasticsearch.shield.ssl.SSLModule;
|
import org.elasticsearch.shield.ssl.SSLModule;
|
||||||
import org.elasticsearch.shield.support.AbstractShieldModule;
|
import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||||
import org.elasticsearch.shield.transport.ShieldTransportModule;
|
import org.elasticsearch.shield.transport.ShieldTransportModule;
|
||||||
|
@ -42,13 +42,13 @@ public class ShieldModule extends AbstractShieldModule.Spawn {
|
||||||
|
|
||||||
return ImmutableList.<Module>of(
|
return ImmutableList.<Module>of(
|
||||||
new LicenseModule(settings),
|
new LicenseModule(settings),
|
||||||
|
new CryptoModule(settings),
|
||||||
new AuthenticationModule(settings),
|
new AuthenticationModule(settings),
|
||||||
new AuthorizationModule(settings),
|
new AuthorizationModule(settings),
|
||||||
new AuditTrailModule(settings),
|
new AuditTrailModule(settings),
|
||||||
new ShieldRestModule(settings),
|
new ShieldRestModule(settings),
|
||||||
new ShieldActionModule(settings),
|
new ShieldActionModule(settings),
|
||||||
new ShieldTransportModule(settings),
|
new ShieldTransportModule(settings),
|
||||||
new SignatureModule(settings),
|
|
||||||
new SSLModule(settings));
|
new SSLModule(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.shield.authz.store.FileRolesStore;
|
import org.elasticsearch.shield.authz.store.FileRolesStore;
|
||||||
import org.elasticsearch.shield.license.LicenseService;
|
import org.elasticsearch.shield.license.LicenseService;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
import org.elasticsearch.shield.transport.filter.IPFilter;
|
import org.elasticsearch.shield.transport.filter.IPFilter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -67,7 +67,7 @@ public class ShieldPlugin extends AbstractPlugin {
|
||||||
@Override
|
@Override
|
||||||
public Collection<Class<? extends LifecycleComponent>> services() {
|
public Collection<Class<? extends LifecycleComponent>> services() {
|
||||||
return enabled && !clientMode ?
|
return enabled && !clientMode ?
|
||||||
ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, FileRolesStore.class, Realms.class, InternalSignatureService.class, IPFilter.class) :
|
ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, InternalCryptoService.class, FileRolesStore.class, Realms.class, IPFilter.class) :
|
||||||
ImmutableList.<Class<? extends LifecycleComponent>>of();
|
ImmutableList.<Class<? extends LifecycleComponent>>of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ import org.elasticsearch.shield.authz.AuthorizationService;
|
||||||
import org.elasticsearch.shield.authz.Privilege;
|
import org.elasticsearch.shield.authz.Privilege;
|
||||||
import org.elasticsearch.shield.license.LicenseEventsNotifier;
|
import org.elasticsearch.shield.license.LicenseEventsNotifier;
|
||||||
import org.elasticsearch.shield.license.LicenseService;
|
import org.elasticsearch.shield.license.LicenseService;
|
||||||
import org.elasticsearch.shield.signature.SignatureException;
|
import org.elasticsearch.shield.crypto.SignatureException;
|
||||||
import org.elasticsearch.shield.signature.SignatureService;
|
import org.elasticsearch.shield.crypto.CryptoService;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -41,19 +41,19 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
|
||||||
|
|
||||||
private final AuthenticationService authcService;
|
private final AuthenticationService authcService;
|
||||||
private final AuthorizationService authzService;
|
private final AuthorizationService authzService;
|
||||||
private final SignatureService signatureService;
|
private final CryptoService cryptoService;
|
||||||
private final AuditTrail auditTrail;
|
private final AuditTrail auditTrail;
|
||||||
private final ShieldActionMapper actionMapper;
|
private final ShieldActionMapper actionMapper;
|
||||||
|
|
||||||
private volatile boolean licenseEnabled = true;
|
private volatile boolean licenseEnabled = true;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, SignatureService signatureService,
|
public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, CryptoService cryptoService,
|
||||||
AuditTrail auditTrail, LicenseEventsNotifier licenseEventsNotifier, ShieldActionMapper actionMapper) {
|
AuditTrail auditTrail, LicenseEventsNotifier licenseEventsNotifier, ShieldActionMapper actionMapper) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.authcService = authcService;
|
this.authcService = authcService;
|
||||||
this.authzService = authzService;
|
this.authzService = authzService;
|
||||||
this.signatureService = signatureService;
|
this.cryptoService = cryptoService;
|
||||||
this.auditTrail = auditTrail;
|
this.auditTrail = auditTrail;
|
||||||
this.actionMapper = actionMapper;
|
this.actionMapper = actionMapper;
|
||||||
licenseEventsNotifier.register(new LicenseEventsNotifier.Listener() {
|
licenseEventsNotifier.register(new LicenseEventsNotifier.Listener() {
|
||||||
|
@ -122,7 +122,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
|
||||||
if (request instanceof SearchScrollRequest) {
|
if (request instanceof SearchScrollRequest) {
|
||||||
SearchScrollRequest scrollRequest = (SearchScrollRequest) request;
|
SearchScrollRequest scrollRequest = (SearchScrollRequest) request;
|
||||||
String scrollId = scrollRequest.scrollId();
|
String scrollId = scrollRequest.scrollId();
|
||||||
scrollRequest.scrollId(signatureService.unsignAndVerify(scrollId));
|
scrollRequest.scrollId(cryptoService.unsignAndVerify(scrollId));
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
|
||||||
List<String> signedIds = clearScrollRequest.scrollIds();
|
List<String> signedIds = clearScrollRequest.scrollIds();
|
||||||
List<String> unsignedIds = new ArrayList<>(signedIds.size());
|
List<String> unsignedIds = new ArrayList<>(signedIds.size());
|
||||||
for (String signedId : signedIds) {
|
for (String signedId : signedIds) {
|
||||||
unsignedIds.add(signatureService.unsignAndVerify(signedId));
|
unsignedIds.add(cryptoService.unsignAndVerify(signedId));
|
||||||
}
|
}
|
||||||
clearScrollRequest.scrollIds(unsignedIds);
|
clearScrollRequest.scrollIds(unsignedIds);
|
||||||
}
|
}
|
||||||
|
@ -153,8 +153,8 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
|
||||||
if (response instanceof SearchResponse) {
|
if (response instanceof SearchResponse) {
|
||||||
SearchResponse searchResponse = (SearchResponse) response;
|
SearchResponse searchResponse = (SearchResponse) response;
|
||||||
String scrollId = searchResponse.getScrollId();
|
String scrollId = searchResponse.getScrollId();
|
||||||
if (scrollId != null && !signatureService.signed(scrollId)) {
|
if (scrollId != null && !cryptoService.signed(scrollId)) {
|
||||||
searchResponse.scrollId(signatureService.sign(scrollId));
|
searchResponse.scrollId(cryptoService.sign(scrollId));
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.rest.RestRequest;
|
import org.elasticsearch.rest.RestRequest;
|
||||||
import org.elasticsearch.shield.User;
|
import org.elasticsearch.shield.User;
|
||||||
import org.elasticsearch.shield.audit.AuditTrail;
|
import org.elasticsearch.shield.audit.AuditTrail;
|
||||||
import org.elasticsearch.shield.signature.SignatureService;
|
import org.elasticsearch.shield.crypto.CryptoService;
|
||||||
import org.elasticsearch.transport.TransportMessage;
|
import org.elasticsearch.transport.TransportMessage;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -36,18 +36,18 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||||
|
|
||||||
private final Realms realms;
|
private final Realms realms;
|
||||||
private final AuditTrail auditTrail;
|
private final AuditTrail auditTrail;
|
||||||
private final SignatureService signatureService;
|
private final CryptoService cryptoService;
|
||||||
private final boolean signUserHeader;
|
private final boolean signUserHeader;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final User anonymouseUser;
|
private final User anonymouseUser;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, SignatureService signatureService) {
|
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.realms = realms;
|
this.realms = realms;
|
||||||
this.auditTrail = auditTrail;
|
this.auditTrail = auditTrail;
|
||||||
this.signatureService = signatureService;
|
this.cryptoService = cryptoService;
|
||||||
this.signUserHeader = componentSettings.getAsBoolean("sign_user_header", true);
|
this.signUserHeader = componentSettings.getAsBoolean("sign_user_header", true);
|
||||||
anonymouseUser = resolveAnonymouseUser(componentSettings);
|
anonymouseUser = resolveAnonymouseUser(componentSettings);
|
||||||
}
|
}
|
||||||
|
@ -84,13 +84,13 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||||
String header = (String) message.getHeader(USER_KEY);
|
String header = (String) message.getHeader(USER_KEY);
|
||||||
if (header != null) {
|
if (header != null) {
|
||||||
if (signUserHeader) {
|
if (signUserHeader) {
|
||||||
header = signatureService.unsignAndVerify(header);
|
header = cryptoService.unsignAndVerify(header);
|
||||||
}
|
}
|
||||||
user = decodeUser(header);
|
user = decodeUser(header);
|
||||||
}
|
}
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
user = authenticateWithRealms(action, message, fallbackUser);
|
user = authenticateWithRealms(action, message, fallbackUser);
|
||||||
header = signUserHeader ? signatureService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
header = signUserHeader ? cryptoService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
||||||
message.putHeader(USER_KEY, header);
|
message.putHeader(USER_KEY, header);
|
||||||
}
|
}
|
||||||
message.putInContext(USER_KEY, user);
|
message.putInContext(USER_KEY, user);
|
||||||
|
@ -104,13 +104,13 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
||||||
}
|
}
|
||||||
User userFromContext = message.getFromContext(USER_KEY);
|
User userFromContext = message.getFromContext(USER_KEY);
|
||||||
if (userFromContext != null) {
|
if (userFromContext != null) {
|
||||||
String userHeader = signUserHeader ? signatureService.sign(encodeUser(userFromContext, logger)) : encodeUser(userFromContext, logger);
|
String userHeader = signUserHeader ? cryptoService.sign(encodeUser(userFromContext, logger)) : encodeUser(userFromContext, logger);
|
||||||
message.putHeader(USER_KEY, userHeader);
|
message.putHeader(USER_KEY, userHeader);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.putInContext(USER_KEY, user);
|
message.putInContext(USER_KEY, user);
|
||||||
String userHeader = signUserHeader ? signatureService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
String userHeader = signUserHeader ? cryptoService.sign(encodeUser(user, logger)) : encodeUser(user, logger);
|
||||||
message.putHeader(USER_KEY, userHeader);
|
message.putHeader(USER_KEY, userHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import java.util.Arrays;
|
||||||
*/
|
*/
|
||||||
public class CharArrays {
|
public class CharArrays {
|
||||||
|
|
||||||
static char[] utf8BytesToChars(byte[] utf8Bytes) {
|
public static char[] utf8BytesToChars(byte[] utf8Bytes) {
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(utf8Bytes);
|
ByteBuffer byteBuffer = ByteBuffer.wrap(utf8Bytes);
|
||||||
CharBuffer charBuffer = Charsets.UTF_8.decode(byteBuffer);
|
CharBuffer charBuffer = Charsets.UTF_8.decode(byteBuffer);
|
||||||
char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
|
char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.signature;
|
package org.elasticsearch.shield.crypto;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.shield.support.AbstractShieldModule;
|
import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||||
|
@ -11,15 +11,15 @@ import org.elasticsearch.shield.support.AbstractShieldModule;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SignatureModule extends AbstractShieldModule.Node {
|
public class CryptoModule extends AbstractShieldModule.Node {
|
||||||
|
|
||||||
public SignatureModule(Settings settings) {
|
public CryptoModule(Settings settings) {
|
||||||
super(settings);
|
super(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureNode() {
|
protected void configureNode() {
|
||||||
bind(InternalSignatureService.class).asEagerSingleton();
|
bind(InternalCryptoService.class).asEagerSingleton();
|
||||||
bind(SignatureService.class).to(InternalSignatureService.class).asEagerSingleton();
|
bind(CryptoService.class).to(InternalCryptoService.class).asEagerSingleton();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.shield.crypto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides cryptographic methods based on a shared system key
|
||||||
|
*/
|
||||||
|
public interface CryptoService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the given text and returns the signed text (original text + signature)
|
||||||
|
*/
|
||||||
|
String sign(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsigns the given signed text, verifies the original text with the attached signature and if valid returns
|
||||||
|
* the unsigned (original) text. If signature verification fails a {@link SignatureException} is thrown.
|
||||||
|
*/
|
||||||
|
String unsignAndVerify(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given text is signed.
|
||||||
|
*/
|
||||||
|
boolean signed(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the provided char array and returns the encrypted values in a Base64 encoded char array
|
||||||
|
* @param chars the characters to encrypt
|
||||||
|
* @return Base64 character array representing the encrypted data
|
||||||
|
* @throws UnsupportedOperationException if the system key is not present
|
||||||
|
*/
|
||||||
|
char[] encrypt(char[] chars);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the provided byte array and returns the encrypted value
|
||||||
|
* @param bytes the data to encrypt
|
||||||
|
* @return encrypted data
|
||||||
|
* @throws UnsupportedOperationException if the system key is not present
|
||||||
|
*/
|
||||||
|
byte[] encrypt(byte[] bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the provided char array and returns the plain-text chars
|
||||||
|
* @param chars the Base64 encoded data to decrypt
|
||||||
|
* @return plaintext chars
|
||||||
|
* @throws UnsupportedOperationException if the system key is not present
|
||||||
|
*/
|
||||||
|
char[] decrypt(char[] chars);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the provided byte array and returns the unencrypted bytes
|
||||||
|
* @param bytes the bytes to decrypt
|
||||||
|
* @return plaintext bytes
|
||||||
|
* @throws UnsupportedOperationException if the system key is not present
|
||||||
|
*/
|
||||||
|
byte[] decrypt(byte[] bytes);
|
||||||
|
}
|
|
@ -0,0 +1,365 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.shield.crypto;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.elasticsearch.ElasticsearchException;
|
||||||
|
import org.elasticsearch.common.base.Charsets;
|
||||||
|
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.shield.ShieldException;
|
||||||
|
import org.elasticsearch.shield.ShieldPlugin;
|
||||||
|
import org.elasticsearch.shield.ShieldSettingsException;
|
||||||
|
import org.elasticsearch.shield.authc.support.CharArrays;
|
||||||
|
import org.elasticsearch.watcher.FileChangesListener;
|
||||||
|
import org.elasticsearch.watcher.FileWatcher;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.elasticsearch.shield.authc.support.SecuredString.constantTimeEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class InternalCryptoService extends AbstractLifecycleComponent<InternalCryptoService> implements CryptoService {
|
||||||
|
|
||||||
|
public static final String FILE_SETTING = "shield.system_key.file";
|
||||||
|
public static final String KEY_ALGO = "HmacSHA512";
|
||||||
|
public static final int KEY_SIZE = 1024;
|
||||||
|
|
||||||
|
static final String FILE_NAME = "system_key";
|
||||||
|
static final String HMAC_ALGO = "HmacSHA1";
|
||||||
|
static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES/CTR/NoPadding";
|
||||||
|
static final String DEFAULT_KEY_ALGORITH = "AES";
|
||||||
|
static final int DEFAULT_KEY_LENGTH = 128;
|
||||||
|
|
||||||
|
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$.+");
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
private final ResourceWatcherService watcherService;
|
||||||
|
private final Listener listener;
|
||||||
|
private final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
private final String encryptionAlgorithm;
|
||||||
|
private final String keyAlgorithm;
|
||||||
|
private final int keyLength;
|
||||||
|
private final int ivLength;
|
||||||
|
|
||||||
|
private Path keyFile;
|
||||||
|
|
||||||
|
private volatile SecretKey encryptionKey;
|
||||||
|
private volatile SecretKey systemKey;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||||
|
this(settings, env, watcherService, Listener.NOOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
InternalCryptoService(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
|
||||||
|
super(settings);
|
||||||
|
this.env = env;
|
||||||
|
this.watcherService = watcherService;
|
||||||
|
this.listener = listener;
|
||||||
|
this.encryptionAlgorithm = settings.get("shield.encryption.algorithm", DEFAULT_ENCRYPTION_ALGORITHM);
|
||||||
|
this.keyLength = settings.getAsInt("shield.encryption_key.length", DEFAULT_KEY_LENGTH);
|
||||||
|
if (keyLength % 8 != 0) {
|
||||||
|
throw new ShieldSettingsException("invalid key length [" + keyLength + "]. value must be a multiple of 8");
|
||||||
|
}
|
||||||
|
this.ivLength = keyLength / 8;
|
||||||
|
this.keyAlgorithm = settings.get("shield.encryption_key.algorithm", DEFAULT_KEY_ALGORITH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStart() throws ElasticsearchException {
|
||||||
|
keyFile = resolveSystemKey(settings, env);
|
||||||
|
systemKey = readSystemKey(keyFile);
|
||||||
|
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
|
||||||
|
FileWatcher watcher = new FileWatcher(keyFile.getParent().toFile());
|
||||||
|
watcher.addListener(new FileListener(listener));
|
||||||
|
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStop() throws ElasticsearchException {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doClose() throws ElasticsearchException {}
|
||||||
|
|
||||||
|
public static byte[] generateKey() throws Exception {
|
||||||
|
KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGO);
|
||||||
|
generator.init(KEY_SIZE);
|
||||||
|
return generator.generateKey().getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path resolveSystemKey(Settings settings, Environment env) {
|
||||||
|
String location = settings.get(FILE_SETTING);
|
||||||
|
if (location == null) {
|
||||||
|
return ShieldPlugin.resolveConfigFile(env, FILE_NAME);
|
||||||
|
}
|
||||||
|
return Paths.get(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SecretKey readSystemKey(Path file) {
|
||||||
|
if (!Files.exists(file)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
byte[] bytes = Files.readAllBytes(file);
|
||||||
|
return new SecretKeySpec(bytes, KEY_ALGO);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ShieldException("could not read secret key", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sign(String text) {
|
||||||
|
SecretKey key = this.systemKey;
|
||||||
|
if (key == null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
String sigStr = signInternal(text);
|
||||||
|
return "$$" + sigStr.length() + "$$" + sigStr + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String unsignAndVerify(String signedText) {
|
||||||
|
SecretKey key = this.systemKey;
|
||||||
|
if (key == null) {
|
||||||
|
return signedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signedText.startsWith("$$") || signedText.length() < 2) {
|
||||||
|
throw new SignatureException("tampered signed text");
|
||||||
|
}
|
||||||
|
|
||||||
|
String text;
|
||||||
|
String receivedSignature;
|
||||||
|
try {
|
||||||
|
// $$34$$sigtext
|
||||||
|
int i = signedText.indexOf("$$", 2);
|
||||||
|
int length = Integer.parseInt(signedText.substring(2, i));
|
||||||
|
receivedSignature = signedText.substring(i + 2, i + 2 + length);
|
||||||
|
text = signedText.substring(i + 2 + length);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error("error occurred while parsing signed text", t);
|
||||||
|
throw new SignatureException("tampered signed text");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String sig = signInternal(text);
|
||||||
|
if (constantTimeEquals(sig, receivedSignature)) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error("error occurred while verifying signed text", t);
|
||||||
|
throw new SignatureException("error while verifying the signed text");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SignatureException("tampered signed text");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean signed(String text) {
|
||||||
|
return SIG_PATTERN.matcher(text).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] encrypt(char[] chars) {
|
||||||
|
SecretKey key = this.encryptionKey;
|
||||||
|
if (key == null) {
|
||||||
|
throw new UnsupportedOperationException("encryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
|
||||||
|
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] charBytes = CharArrays.toUtf8Bytes(chars);
|
||||||
|
return Base64.encodeBase64String(encryptInternal(charBytes, key)).toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encrypt(byte[] bytes) {
|
||||||
|
SecretKey key = this.encryptionKey;
|
||||||
|
if (key == null) {
|
||||||
|
throw new UnsupportedOperationException("encryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
|
||||||
|
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
|
||||||
|
}
|
||||||
|
return encryptInternal(bytes, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] decrypt(char[] chars) {
|
||||||
|
SecretKey key = this.encryptionKey;
|
||||||
|
if (key == null) {
|
||||||
|
throw new UnsupportedOperationException("decryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
|
||||||
|
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = Base64.decodeBase64(new String(chars));
|
||||||
|
byte[] decrypted = decryptInternal(bytes, key);
|
||||||
|
return CharArrays.utf8BytesToChars(decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] decrypt(byte[] bytes) {
|
||||||
|
SecretKey key = this.encryptionKey;
|
||||||
|
if (key == null) {
|
||||||
|
throw new UnsupportedOperationException("decryption cannot be performed without a system key. please run bin/shield/syskeygen on one node and copy\n"
|
||||||
|
+ "the file [" + ShieldPlugin.resolveConfigFile(env, FILE_NAME) + "] to all nodes and the key will be loaded automatically.");
|
||||||
|
}
|
||||||
|
return decryptInternal(bytes, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] encryptInternal(byte[] bytes, SecretKey key) {
|
||||||
|
byte[] iv = new byte[ivLength];
|
||||||
|
secureRandom.nextBytes(iv);
|
||||||
|
Cipher cipher = cipher(Cipher.ENCRYPT_MODE, encryptionAlgorithm, key, iv);
|
||||||
|
try {
|
||||||
|
byte[] encrypted = cipher.doFinal(bytes);
|
||||||
|
byte[] output = new byte[iv.length + encrypted.length];
|
||||||
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
||||||
|
System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
|
||||||
|
return output;
|
||||||
|
} catch (BadPaddingException |IllegalBlockSizeException e) {
|
||||||
|
throw new ShieldException("error encrypting data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] decryptInternal(byte[] bytes, SecretKey key) {
|
||||||
|
if (bytes.length < ivLength) {
|
||||||
|
logger.error("received data for decryption with size [{}] that is less than IV length [{}]", bytes.length, ivLength);
|
||||||
|
throw new ShieldException("invalid data to decrypt");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] iv = new byte[ivLength];
|
||||||
|
System.arraycopy(bytes, 0, iv, 0, ivLength);
|
||||||
|
byte[] data = new byte[bytes.length - ivLength];
|
||||||
|
System.arraycopy(bytes, ivLength, data, 0, bytes.length - ivLength);
|
||||||
|
|
||||||
|
Cipher cipher = cipher(Cipher.DECRYPT_MODE, encryptionAlgorithm, key, iv);
|
||||||
|
try {
|
||||||
|
return cipher.doFinal(data);
|
||||||
|
} catch (BadPaddingException|IllegalBlockSizeException e) {
|
||||||
|
throw new ShieldException("error decrypting data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Mac createMac(SecretKey key) {
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance(HMAC_ALGO);
|
||||||
|
mac.init(key);
|
||||||
|
return mac;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ElasticsearchException("could not initialize mac", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String signInternal(String text) {
|
||||||
|
Mac mac = createMac(systemKey);
|
||||||
|
byte[] sig = mac.doFinal(text.getBytes(Charsets.UTF_8));
|
||||||
|
return Base64.encodeBase64URLSafeString(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Cipher cipher(int mode, String encryptionAlgorithm, SecretKey key, byte[] initializationVector) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
|
||||||
|
cipher.init(mode, key, new IvParameterSpec(initializationVector));
|
||||||
|
return cipher;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ShieldException("error creating cipher", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SecretKey encryptionKey(SecretKey systemKey, int keyLength, String algorithm) {
|
||||||
|
if (systemKey == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = systemKey.getEncoded();
|
||||||
|
if ((bytes.length * 8) < keyLength) {
|
||||||
|
throw new ShieldException("at least " + keyLength +" bits should be provided as key data");
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] digest = messageDigest.digest(bytes);
|
||||||
|
assert digest.length == (256 / 8);
|
||||||
|
|
||||||
|
if ((digest.length * 8) < keyLength) {
|
||||||
|
throw new ShieldException("requested key length is too large");
|
||||||
|
}
|
||||||
|
byte[] truncatedDigest = Arrays.copyOfRange(digest, 0, (keyLength / 8));
|
||||||
|
|
||||||
|
return new SecretKeySpec(truncatedDigest, algorithm);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new ShieldException("error getting encryption key", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileListener extends FileChangesListener {
|
||||||
|
|
||||||
|
private final Listener listener;
|
||||||
|
|
||||||
|
private FileListener(Listener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileCreated(File file) {
|
||||||
|
if (file.equals(keyFile.toFile())) {
|
||||||
|
systemKey = readSystemKey(file.toPath());
|
||||||
|
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
|
||||||
|
logger.info("system key [{}] has been loaded", file.getAbsolutePath());
|
||||||
|
listener.onKeyRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileDeleted(File file) {
|
||||||
|
if (file.equals(keyFile.toFile())) {
|
||||||
|
logger.error("system key file was removed! as long as the system key file is missing, elasticsearch " +
|
||||||
|
"won't function as expected for some requests (e.g. scroll/scan) and won't be able to decrypt\n" +
|
||||||
|
"previously encrypted values without the original key");
|
||||||
|
systemKey = null;
|
||||||
|
encryptionKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileChanged(File file) {
|
||||||
|
if (file.equals(keyFile.toFile())) {
|
||||||
|
logger.warn("system key file changed! previously encrypted values cannot be successfully decrypted with a different key");
|
||||||
|
systemKey = readSystemKey(file.toPath());
|
||||||
|
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
|
||||||
|
listener.onKeyRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static interface Listener {
|
||||||
|
|
||||||
|
final Listener NOOP = new Listener() {
|
||||||
|
@Override
|
||||||
|
public void onKeyRefresh() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void onKeyRefresh();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.signature;
|
package org.elasticsearch.shield.crypto;
|
||||||
|
|
||||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.signature.tool;
|
package org.elasticsearch.shield.crypto.tool;
|
||||||
|
|
||||||
import org.elasticsearch.common.cli.CheckFileCommand;
|
import org.elasticsearch.common.cli.CheckFileCommand;
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
import org.elasticsearch.common.cli.CliTool;
|
||||||
|
@ -13,7 +13,7 @@ import org.elasticsearch.common.cli.commons.CommandLine;
|
||||||
import org.elasticsearch.common.collect.Sets;
|
import org.elasticsearch.common.collect.Sets;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -78,7 +78,7 @@ public class SystemKeyTool extends CliTool {
|
||||||
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
|
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
|
||||||
Path path = this.path;
|
Path path = this.path;
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
path = InternalSignatureService.resolveFile(settings, env);
|
path = InternalCryptoService.resolveSystemKey(settings, env);
|
||||||
}
|
}
|
||||||
return new Path[] { path };
|
return new Path[] { path };
|
||||||
}
|
}
|
||||||
|
@ -88,10 +88,10 @@ public class SystemKeyTool extends CliTool {
|
||||||
Path path = this.path;
|
Path path = this.path;
|
||||||
try {
|
try {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
path = InternalSignatureService.resolveFile(settings, env);
|
path = InternalCryptoService.resolveSystemKey(settings, env);
|
||||||
}
|
}
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "generating...");
|
terminal.println(Terminal.Verbosity.VERBOSE, "generating...");
|
||||||
byte[] key = InternalSignatureService.generateKey();
|
byte[] key = InternalCryptoService.generateKey();
|
||||||
terminal.println("Storing generated key in [%s]...", path.toAbsolutePath());
|
terminal.println("Storing generated key in [%s]...", path.toAbsolutePath());
|
||||||
Files.write(path, key, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
Files.write(path, key, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
|
@ -1,220 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.shield.signature;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.common.base.Charsets;
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.shield.ShieldException;
|
|
||||||
import org.elasticsearch.shield.ShieldPlugin;
|
|
||||||
import org.elasticsearch.watcher.FileChangesListener;
|
|
||||||
import org.elasticsearch.watcher.FileWatcher;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static org.elasticsearch.shield.authc.support.SecuredString.constantTimeEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InternalSignatureService extends AbstractLifecycleComponent<InternalSignatureService> implements SignatureService {
|
|
||||||
|
|
||||||
public static final String FILE_SETTING = "shield.system_key.file";
|
|
||||||
public static final String KEY_ALGO = "HmacSHA512";
|
|
||||||
public static final int KEY_SIZE = 1024;
|
|
||||||
|
|
||||||
static final String FILE_NAME = "system_key";
|
|
||||||
static final String HMAC_ALGO = "HmacSHA1";
|
|
||||||
|
|
||||||
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$.+");
|
|
||||||
private final Environment env;
|
|
||||||
private final ResourceWatcherService watcherService;
|
|
||||||
private final Listener listener;
|
|
||||||
|
|
||||||
private Path keyFile;
|
|
||||||
|
|
||||||
private volatile SecretKey key;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public InternalSignatureService(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
|
||||||
this(settings, env, watcherService, Listener.NOOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
InternalSignatureService(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
|
|
||||||
super(settings);
|
|
||||||
this.env = env;
|
|
||||||
this.watcherService = watcherService;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] generateKey() throws Exception {
|
|
||||||
KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGO);
|
|
||||||
generator.init(KEY_SIZE);
|
|
||||||
return generator.generateKey().getEncoded();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Path resolveFile(Settings settings, Environment env) {
|
|
||||||
String location = settings.get(FILE_SETTING);
|
|
||||||
if (location == null) {
|
|
||||||
return ShieldPlugin.resolveConfigFile(env, FILE_NAME);
|
|
||||||
}
|
|
||||||
return Paths.get(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
static SecretKey readKey(Path file) {
|
|
||||||
if (!Files.exists(file)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
byte[] bytes = Files.readAllBytes(file);
|
|
||||||
return new SecretKeySpec(bytes, KEY_ALGO);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ShieldException("could not read secret key", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String sign(String text) {
|
|
||||||
SecretKey key = this.key;
|
|
||||||
if (key == null) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
String sigStr = signInternal(text);
|
|
||||||
return "$$" + sigStr.length() + "$$" + sigStr + text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String unsignAndVerify(String signedText) {
|
|
||||||
SecretKey key = this.key;
|
|
||||||
if (key == null) {
|
|
||||||
return signedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!signedText.startsWith("$$") || signedText.length() < 2) {
|
|
||||||
throw new SignatureException("tampered signed text");
|
|
||||||
}
|
|
||||||
|
|
||||||
String text;
|
|
||||||
String receivedSignature;
|
|
||||||
try {
|
|
||||||
// $$34$$sigtext
|
|
||||||
int i = signedText.indexOf("$$", 2);
|
|
||||||
int length = Integer.parseInt(signedText.substring(2, i));
|
|
||||||
receivedSignature = signedText.substring(i + 2, i + 2 + length);
|
|
||||||
text = signedText.substring(i + 2 + length);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
logger.error("error occurred while parsing signed text", t);
|
|
||||||
throw new SignatureException("tampered signed text");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String sig = signInternal(text);
|
|
||||||
if (constantTimeEquals(sig, receivedSignature)) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
logger.error("error occurred while verifying signed text", t);
|
|
||||||
throw new SignatureException("error while verifying the signed text");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SignatureException("tampered signed text");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean signed(String text) {
|
|
||||||
return SIG_PATTERN.matcher(text).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Mac createMac(SecretKey key) {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance(HMAC_ALGO);
|
|
||||||
mac.init(key);
|
|
||||||
return mac;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ElasticsearchException("could not initialize mac", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String signInternal(String text) {
|
|
||||||
Mac mac = createMac(key);
|
|
||||||
byte[] sig = mac.doFinal(text.getBytes(Charsets.UTF_8));
|
|
||||||
return Base64.encodeBase64URLSafeString(sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws ElasticsearchException {
|
|
||||||
keyFile = resolveFile(settings, env);
|
|
||||||
key = readKey(keyFile);
|
|
||||||
FileWatcher watcher = new FileWatcher(keyFile.getParent().toFile());
|
|
||||||
watcher.addListener(new FileListener(listener));
|
|
||||||
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() throws ElasticsearchException {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() throws ElasticsearchException {}
|
|
||||||
|
|
||||||
private class FileListener extends FileChangesListener {
|
|
||||||
|
|
||||||
private final Listener listener;
|
|
||||||
|
|
||||||
private FileListener(Listener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileCreated(File file) {
|
|
||||||
if (file.equals(keyFile.toFile())) {
|
|
||||||
key = readKey(file.toPath());
|
|
||||||
listener.onKeyRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileDeleted(File file) {
|
|
||||||
if (file.equals(keyFile.toFile())) {
|
|
||||||
logger.error("system key file was removed! as long as the system key file is missing, elasticsearch " +
|
|
||||||
"won't function as expected for some requests (e.g. scroll/scan)");
|
|
||||||
key = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileChanged(File file) {
|
|
||||||
if (file.equals(keyFile.toFile())) {
|
|
||||||
key = readKey(file.toPath());
|
|
||||||
listener.onKeyRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static interface Listener {
|
|
||||||
|
|
||||||
final Listener NOOP = new Listener() {
|
|
||||||
@Override
|
|
||||||
public void onKeyRefresh() {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void onKeyRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.shield.signature;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface SignatureService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the given text and returns the signed text (original text + signature)
|
|
||||||
*/
|
|
||||||
String sign(String text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsigns the given signed text, verifies the original text with the attached signature and if valid returns
|
|
||||||
* the unsigned (original) text. If signature verification fails a {@link SignatureException} is thrown.
|
|
||||||
*/
|
|
||||||
String unsignAndVerify(String text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the given text is signed.
|
|
||||||
*/
|
|
||||||
boolean signed(String text);
|
|
||||||
|
|
||||||
}
|
|
|
@ -10,10 +10,9 @@ import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
import org.elasticsearch.shield.signature.SignatureService;
|
import org.elasticsearch.shield.crypto.CryptoService;
|
||||||
import org.elasticsearch.test.ShieldIntegrationTest;
|
import org.elasticsearch.test.ShieldIntegrationTest;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -104,8 +103,8 @@ public class ScrollIdSigningTests extends ShieldIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSigned(String scrollId) {
|
private void assertSigned(String scrollId) {
|
||||||
SignatureService signatureService = internalCluster().getDataNodeInstance(InternalSignatureService.class);
|
CryptoService cryptoService = internalCluster().getDataNodeInstance(InternalCryptoService.class);
|
||||||
String message = String.format(Locale.ROOT, "Expected scrollId [%s] to be signed, but was not", scrollId);
|
String message = String.format(Locale.ROOT, "Expected scrollId [%s] to be signed, but was not", scrollId);
|
||||||
assertThat(message, signatureService.signed(scrollId), is(true));
|
assertThat(message, cryptoService.signed(scrollId), is(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import org.elasticsearch.shield.authc.AuthenticationService;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||||
import org.elasticsearch.shield.license.LicenseEventsNotifier;
|
import org.elasticsearch.shield.license.LicenseEventsNotifier;
|
||||||
import org.elasticsearch.shield.signature.SignatureException;
|
import org.elasticsearch.shield.crypto.SignatureException;
|
||||||
import org.elasticsearch.shield.signature.SignatureService;
|
import org.elasticsearch.shield.crypto.CryptoService;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -34,7 +34,7 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
private AuthenticationService authcService;
|
private AuthenticationService authcService;
|
||||||
private AuthorizationService authzService;
|
private AuthorizationService authzService;
|
||||||
private SignatureService signatureService;
|
private CryptoService cryptoService;
|
||||||
private AuditTrail auditTrail;
|
private AuditTrail auditTrail;
|
||||||
private LicenseEventsNotifier licenseEventsNotifier;
|
private LicenseEventsNotifier licenseEventsNotifier;
|
||||||
private ShieldActionFilter filter;
|
private ShieldActionFilter filter;
|
||||||
|
@ -43,10 +43,10 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
authcService = mock(AuthenticationService.class);
|
authcService = mock(AuthenticationService.class);
|
||||||
authzService = mock(AuthorizationService.class);
|
authzService = mock(AuthorizationService.class);
|
||||||
signatureService = mock(SignatureService.class);
|
cryptoService = mock(CryptoService.class);
|
||||||
auditTrail = mock(AuditTrail.class);
|
auditTrail = mock(AuditTrail.class);
|
||||||
licenseEventsNotifier = new MockLicenseEventsNotifier();
|
licenseEventsNotifier = new MockLicenseEventsNotifier();
|
||||||
filter = new ShieldActionFilter(ImmutableSettings.EMPTY, authcService, authzService, signatureService, auditTrail, licenseEventsNotifier, new ShieldActionMapper());
|
filter = new ShieldActionFilter(ImmutableSettings.EMPTY, authcService, authzService, cryptoService, auditTrail, licenseEventsNotifier, new ShieldActionMapper());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,8 +83,8 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
|
||||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||||
User user = mock(User.class);
|
User user = mock(User.class);
|
||||||
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
||||||
when(signatureService.signed("signed_scroll_id")).thenReturn(true);
|
when(cryptoService.signed("signed_scroll_id")).thenReturn(true);
|
||||||
when(signatureService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
|
when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
|
||||||
filter.apply("_action", request, listener, chain);
|
filter.apply("_action", request, listener, chain);
|
||||||
assertThat(request.scrollId(), equalTo("scroll_id"));
|
assertThat(request.scrollId(), equalTo("scroll_id"));
|
||||||
verify(authzService).authorize(user, "_action", request);
|
verify(authzService).authorize(user, "_action", request);
|
||||||
|
@ -99,8 +99,8 @@ public class ShieldActionFilterTests extends ElasticsearchTestCase {
|
||||||
SignatureException sigException = new SignatureException("bad bad boy");
|
SignatureException sigException = new SignatureException("bad bad boy");
|
||||||
User user = mock(User.class);
|
User user = mock(User.class);
|
||||||
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
||||||
when(signatureService.signed("scroll_id")).thenReturn(true);
|
when(cryptoService.signed("scroll_id")).thenReturn(true);
|
||||||
doThrow(sigException).when(signatureService).unsignAndVerify("scroll_id");
|
doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id");
|
||||||
filter.apply("_action", request, listener, chain);
|
filter.apply("_action", request, listener, chain);
|
||||||
verify(listener).onFailure(isA(AuthorizationException.class));
|
verify(listener).onFailure(isA(AuthorizationException.class));
|
||||||
verify(auditTrail).tamperedRequest(user, "_action", request);
|
verify(auditTrail).tamperedRequest(user, "_action", request);
|
||||||
|
|
|
@ -18,7 +18,7 @@ import org.elasticsearch.shield.User;
|
||||||
import org.elasticsearch.shield.audit.AuditTrail;
|
import org.elasticsearch.shield.audit.AuditTrail;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.shield.signature.SignatureService;
|
import org.elasticsearch.shield.crypto.CryptoService;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.elasticsearch.transport.TransportMessage;
|
import org.elasticsearch.transport.TransportMessage;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -49,7 +49,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
Realm secondRealm;
|
Realm secondRealm;
|
||||||
AuditTrail auditTrail;
|
AuditTrail auditTrail;
|
||||||
AuthenticationToken token;
|
AuthenticationToken token;
|
||||||
SignatureService signatureService;
|
CryptoService cryptoService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
|
@ -67,10 +67,10 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
realms.start();
|
realms.start();
|
||||||
signatureService = mock(SignatureService.class);
|
cryptoService = mock(CryptoService.class);
|
||||||
|
|
||||||
auditTrail = mock(AuditTrail.class);
|
auditTrail = mock(AuditTrail.class);
|
||||||
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail, signatureService);
|
service = new InternalAuthenticationService(ImmutableSettings.EMPTY, realms, auditTrail, cryptoService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
@Test @SuppressWarnings("unchecked")
|
||||||
|
@ -116,7 +116,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
service = spy(service);
|
service = spy(service);
|
||||||
doReturn(token).when(service).token("_action", message);
|
doReturn(token).when(service).token("_action", message);
|
||||||
|
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
|
||||||
|
|
||||||
User result = service.authenticate("_action", message, null);
|
User result = service.authenticate("_action", message, null);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
|
@ -137,7 +137,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
service = spy(service);
|
service = spy(service);
|
||||||
doReturn(token).when(service).token("_action", message);
|
doReturn(token).when(service).token("_action", message);
|
||||||
|
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_encoded_user");
|
||||||
|
|
||||||
User result = service.authenticate("_action", message, null);
|
User result = service.authenticate("_action", message, null);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
|
@ -159,7 +159,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
verifyZeroInteractions(auditTrail);
|
verifyZeroInteractions(auditTrail);
|
||||||
verifyZeroInteractions(firstRealm);
|
verifyZeroInteractions(firstRealm);
|
||||||
verifyZeroInteractions(secondRealm);
|
verifyZeroInteractions(secondRealm);
|
||||||
verifyZeroInteractions(signatureService);
|
verifyZeroInteractions(cryptoService);
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), notNullValue());
|
||||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), is((Object) user));
|
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), is((Object) user));
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(firstRealm.token(message)).thenReturn(token);
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
when(firstRealm.supports(token)).thenReturn(true);
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||||
service = spy(service);
|
service = spy(service);
|
||||||
doReturn(token).when(service).token("_action", message);
|
doReturn(token).when(service).token("_action", message);
|
||||||
User result = service.authenticate("_action", message, null);
|
User result = service.authenticate("_action", message, null);
|
||||||
|
@ -253,7 +253,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(firstRealm.token(message)).thenReturn(null);
|
when(firstRealm.token(message)).thenReturn(null);
|
||||||
when(secondRealm.token(message)).thenReturn(null);
|
when(secondRealm.token(message)).thenReturn(null);
|
||||||
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
User user2 = service.authenticate("_action", message, user1);
|
User user2 = service.authenticate("_action", message, user1);
|
||||||
assertThat(user1, sameInstance(user2));
|
assertThat(user1, sameInstance(user2));
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
@ -266,7 +266,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(firstRealm.token(message)).thenReturn(token);
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
when(firstRealm.supports(token)).thenReturn(true);
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
User user2 = service.authenticate("_action", message, null);
|
User user2 = service.authenticate("_action", message, null);
|
||||||
assertThat(user1, sameInstance(user2));
|
assertThat(user1, sameInstance(user2));
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
@ -279,7 +279,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(firstRealm.token(message)).thenReturn(token);
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
when(firstRealm.supports(token)).thenReturn(true);
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
||||||
assertThat(user1, sameInstance(user2));
|
assertThat(user1, sameInstance(user2));
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
@ -303,7 +303,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
when(firstRealm.token(message)).thenReturn(token);
|
when(firstRealm.token(message)).thenReturn(token);
|
||||||
when(firstRealm.supports(token)).thenReturn(true);
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||||
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
User user2 = service.authenticate("_action", message, User.SYSTEM);
|
||||||
assertThat(user1, sameInstance(user2));
|
assertThat(user1, sameInstance(user2));
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user2));
|
||||||
|
@ -321,7 +321,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
// checking authentication from the user header
|
// checking authentication from the user header
|
||||||
message1.putHeader(InternalAuthenticationService.USER_KEY, message.getHeader(InternalAuthenticationService.USER_KEY));
|
message1.putHeader(InternalAuthenticationService.USER_KEY, message.getHeader(InternalAuthenticationService.USER_KEY));
|
||||||
when(signatureService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
|
when(cryptoService.unsignAndVerify("_signed_user")).thenReturn(InternalAuthenticationService.encodeUser(user1, null));
|
||||||
BytesStreamOutput output = new BytesStreamOutput();
|
BytesStreamOutput output = new BytesStreamOutput();
|
||||||
message1.writeTo(output);
|
message1.writeTo(output);
|
||||||
BytesStreamInput input = new BytesStreamInput(output.bytes());
|
BytesStreamInput input = new BytesStreamInput(output.bytes());
|
||||||
|
@ -335,7 +335,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testAutheticate_Transport_ContextAndHeader_NoSigning() throws Exception {
|
public void testAutheticate_Transport_ContextAndHeader_NoSigning() throws Exception {
|
||||||
Settings settings = ImmutableSettings.builder().put("shield.authc.sign_user_header", false).build();
|
Settings settings = ImmutableSettings.builder().put("shield.authc.sign_user_header", false).build();
|
||||||
service = new InternalAuthenticationService(settings, realms, auditTrail, signatureService);
|
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService);
|
||||||
|
|
||||||
User user1 = new User.Simple("username", "r1", "r2");
|
User user1 = new User.Simple("username", "r1", "r2");
|
||||||
when(firstRealm.supports(token)).thenReturn(true);
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
@ -367,7 +367,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
assertThat(user, equalTo(user1));
|
assertThat(user, equalTo(user1));
|
||||||
verifyZeroInteractions(firstRealm);
|
verifyZeroInteractions(firstRealm);
|
||||||
|
|
||||||
verifyZeroInteractions(signatureService);
|
verifyZeroInteractions(cryptoService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -375,7 +375,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
User user = new User.Simple("username", "r1", "r2");
|
User user = new User.Simple("username", "r1", "r2");
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||||
service.attachUserHeaderIfMissing(message, user);
|
service.attachUserHeaderIfMissing(message, user);
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
@ -384,7 +384,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
message = new InternalMessage();
|
message = new InternalMessage();
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
||||||
when(signatureService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||||
service.attachUserHeaderIfMissing(message, user);
|
service.attachUserHeaderIfMissing(message, user);
|
||||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||||
|
@ -437,7 +437,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
if (username != InternalAuthenticationService.ANONYMOUS_USERNAME) {
|
if (username != InternalAuthenticationService.ANONYMOUS_USERNAME) {
|
||||||
builder.put("shield.authc.anonymous.username", username);
|
builder.put("shield.authc.anonymous.username", username);
|
||||||
}
|
}
|
||||||
service = new InternalAuthenticationService(builder.build(), realms, auditTrail, signatureService);
|
service = new InternalAuthenticationService(builder.build(), realms, auditTrail, cryptoService);
|
||||||
|
|
||||||
RestRequest request = new FakeRestRequest();
|
RestRequest request = new FakeRestRequest();
|
||||||
|
|
||||||
|
@ -454,7 +454,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
Settings settings = ImmutableSettings.builder()
|
Settings settings = ImmutableSettings.builder()
|
||||||
.putArray("shield.authc.anonymous.roles", "r1", "r2", "r3")
|
.putArray("shield.authc.anonymous.roles", "r1", "r2", "r3")
|
||||||
.build();
|
.build();
|
||||||
service = new InternalAuthenticationService(settings, realms, auditTrail, signatureService);
|
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService);
|
||||||
|
|
||||||
InternalMessage message = new InternalMessage();
|
InternalMessage message = new InternalMessage();
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
|
||||||
Settings settings = ImmutableSettings.builder()
|
Settings settings = ImmutableSettings.builder()
|
||||||
.putArray("shield.authc.anonymous.roles", "r1", "r2", "r3")
|
.putArray("shield.authc.anonymous.roles", "r1", "r2", "r3")
|
||||||
.build();
|
.build();
|
||||||
service = new InternalAuthenticationService(settings, realms, auditTrail, signatureService);
|
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService);
|
||||||
|
|
||||||
InternalMessage message = new InternalMessage();
|
InternalMessage message = new InternalMessage();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.signature;
|
package org.elasticsearch.shield.crypto;
|
||||||
|
|
||||||
import org.elasticsearch.common.io.Streams;
|
import org.elasticsearch.common.io.Streams;
|
||||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
|
@ -17,17 +17,17 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
public class InternalCryptoServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
private ResourceWatcherService watcherService;
|
private ResourceWatcherService watcherService;
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
|
@ -38,7 +38,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
keyFile = new File(newTempDir(), "system_key");
|
keyFile = new File(newTempDir(), "system_key");
|
||||||
Streams.copy(InternalSignatureService.generateKey(), keyFile);
|
Streams.copy(InternalCryptoService.generateKey(), keyFile);
|
||||||
settings = ImmutableSettings.builder()
|
settings = ImmutableSettings.builder()
|
||||||
.put("shield.system_key.file", keyFile.getAbsolutePath())
|
.put("shield.system_key.file", keyFile.getAbsolutePath())
|
||||||
.put("watcher.interval.high", "2s")
|
.put("watcher.interval.high", "2s")
|
||||||
|
@ -51,12 +51,13 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void shutdown() throws InterruptedException {
|
public void shutdown() throws InterruptedException {
|
||||||
|
watcherService.stop();
|
||||||
terminate(threadPool);
|
terminate(threadPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSigned() throws Exception {
|
public void testSigned() throws Exception {
|
||||||
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
assertThat(service.signed(signed), is(true));
|
assertThat(service.signed(signed), is(true));
|
||||||
|
@ -64,7 +65,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSignAndUnsign() throws Exception {
|
public void testSignAndUnsign() throws Exception {
|
||||||
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
assertThat(text.equals(signed), is(false));
|
assertThat(text.equals(signed), is(false));
|
||||||
|
@ -74,7 +75,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSignAndUnsign_NoKeyFile() throws Exception {
|
public void testSignAndUnsign_NoKeyFile() throws Exception {
|
||||||
InternalSignatureService service = new InternalSignatureService(ImmutableSettings.EMPTY, env, watcherService).start();
|
InternalCryptoService service = new InternalCryptoService(ImmutableSettings.EMPTY, env, watcherService).start();
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
assertThat(text, equalTo(signed));
|
assertThat(text, equalTo(signed));
|
||||||
|
@ -84,7 +85,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTamperedSignature() throws Exception {
|
public void testTamperedSignature() throws Exception {
|
||||||
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
int i = signed.indexOf("$$", 2);
|
int i = signed.indexOf("$$", 2);
|
||||||
|
@ -102,7 +103,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTamperedSignatureOneChar() throws Exception {
|
public void testTamperedSignatureOneChar() throws Exception {
|
||||||
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
int i = signed.indexOf("$$", 2);
|
int i = signed.indexOf("$$", 2);
|
||||||
|
@ -122,7 +123,7 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTamperedSignatureLength() throws Exception {
|
public void testTamperedSignatureLength() throws Exception {
|
||||||
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService).start();
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
int i = signed.indexOf("$$", 2);
|
int i = signed.indexOf("$$", 2);
|
||||||
|
@ -148,10 +149,88 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionAndDecryptionChars() {
|
||||||
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
|
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
||||||
|
final char[] encrypted = service.encrypt(chars);
|
||||||
|
assertThat(encrypted, notNullValue());
|
||||||
|
assertThat(Arrays.equals(encrypted, chars), is(false));
|
||||||
|
|
||||||
|
final char[] decrypted = service.decrypt(encrypted);
|
||||||
|
assertThat(Arrays.equals(chars, decrypted), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionAndDecryptionBytes() {
|
||||||
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
|
final byte[] bytes = randomByteArray();
|
||||||
|
final byte[] encrypted = service.encrypt(bytes);
|
||||||
|
assertThat(encrypted, notNullValue());
|
||||||
|
assertThat(Arrays.equals(encrypted, bytes), is(false));
|
||||||
|
|
||||||
|
final byte[] decrypted = service.decrypt(encrypted);
|
||||||
|
assertThat(Arrays.equals(bytes, decrypted), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionAndDecryptionCharsWithoutKey() {
|
||||||
|
InternalCryptoService service = new InternalCryptoService(ImmutableSettings.EMPTY, env, watcherService).start();
|
||||||
|
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
|
||||||
|
try {
|
||||||
|
service.encrypt(chars);
|
||||||
|
fail("exception should have been thrown");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e, instanceOf(UnsupportedOperationException.class));
|
||||||
|
assertThat(e.getMessage(), containsString("system_key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
service.decrypt(chars);
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e, instanceOf(UnsupportedOperationException.class));
|
||||||
|
assertThat(e.getMessage(), containsString("system_key"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionAndDecryptionBytesWithoutKey() {
|
||||||
|
InternalCryptoService service = new InternalCryptoService(ImmutableSettings.EMPTY, env, watcherService).start();
|
||||||
|
final byte[] bytes = randomByteArray();
|
||||||
|
try {
|
||||||
|
service.encrypt(bytes);
|
||||||
|
fail("exception should have been thrown");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e, instanceOf(UnsupportedOperationException.class));
|
||||||
|
assertThat(e.getMessage(), containsString("system_key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
service.decrypt(bytes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e, instanceOf(UnsupportedOperationException.class));
|
||||||
|
assertThat(e.getMessage(), containsString("system_key"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChangingAByte() {
|
||||||
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService).start();
|
||||||
|
final byte[] bytes = randomByteArray();
|
||||||
|
final byte[] encrypted = service.encrypt(bytes);
|
||||||
|
assertThat(encrypted, notNullValue());
|
||||||
|
assertThat(Arrays.equals(encrypted, bytes), is(false));
|
||||||
|
|
||||||
|
int tamperedIndex = randomIntBetween(0, encrypted.length - 1);
|
||||||
|
encrypted[tamperedIndex] = randomByte();
|
||||||
|
final byte[] decrypted = service.decrypt(encrypted);
|
||||||
|
assertThat(Arrays.equals(bytes, decrypted), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReloadKey() throws Exception {
|
public void testReloadKey() throws Exception {
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
InternalSignatureService service = new InternalSignatureService(settings, env, watcherService, new InternalSignatureService.Listener() {
|
InternalCryptoService service = new InternalCryptoService(settings, env, watcherService, new InternalCryptoService.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onKeyRefresh() {
|
public void onKeyRefresh() {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
|
@ -160,16 +239,34 @@ public class InternalSignatureServiceTests extends ElasticsearchTestCase {
|
||||||
|
|
||||||
String text = randomAsciiOfLength(10);
|
String text = randomAsciiOfLength(10);
|
||||||
String signed = service.sign(text);
|
String signed = service.sign(text);
|
||||||
|
char[] textChars = text.toCharArray();
|
||||||
|
char[] encrypted = service.encrypt(textChars);
|
||||||
|
|
||||||
// we need to sleep so to ensure the timestamp of the file will definitely change
|
// we need to sleep so to ensure the timestamp of the file will definitely change
|
||||||
// and so the resource watcher will pick up the change.
|
// and so the resource watcher will pick up the change.
|
||||||
sleep(1000);
|
sleep(1000);
|
||||||
|
|
||||||
Streams.copy(InternalSignatureService.generateKey(), keyFile);
|
Streams.copy(InternalCryptoService.generateKey(), keyFile);
|
||||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||||
fail("waiting too long for test to complete. Expected callback is not called");
|
fail("waiting too long for test to complete. Expected callback is not called");
|
||||||
}
|
}
|
||||||
String signed2 = service.sign(text);
|
String signed2 = service.sign(text);
|
||||||
assertThat(signed.equals(signed2), is(false));
|
assertThat(signed.equals(signed2), is(false));
|
||||||
|
|
||||||
|
char[] encrypted2 = service.encrypt(textChars);
|
||||||
|
|
||||||
|
char[] decrypted = service.decrypt(encrypted);
|
||||||
|
char[] decrypted2 = service.decrypt(encrypted2);
|
||||||
|
assertThat(Arrays.equals(textChars, decrypted), is(false));
|
||||||
|
assertThat(Arrays.equals(textChars, decrypted2), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] randomByteArray() {
|
||||||
|
int count = randomIntBetween(0, 1000);
|
||||||
|
byte[] bytes = new byte[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
bytes[i] = randomByte();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.signature.tool;
|
package org.elasticsearch.shield.crypto.tool;
|
||||||
|
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
import org.elasticsearch.common.cli.CliTool;
|
||||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
import org.elasticsearch.common.cli.CliToolTestCase;
|
||||||
|
@ -13,7 +13,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.shield.ShieldPlugin;
|
import org.elasticsearch.shield.ShieldPlugin;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import java.nio.file.attribute.PosixFileAttributeView;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.shield.signature.tool.SystemKeyTool.Generate;
|
import static org.elasticsearch.shield.crypto.tool.SystemKeyTool.Generate;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
@ -67,7 +67,7 @@ public class SystemKeyToolTests extends CliToolTestCase {
|
||||||
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
|
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
|
||||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||||
byte[] bytes = Streams.copyToByteArray(path.toFile());
|
byte[] bytes = Streams.copyToByteArray(path.toFile());
|
||||||
assertThat(bytes.length, is(InternalSignatureService.KEY_SIZE / 8));
|
assertThat(bytes.length, is(InternalCryptoService.KEY_SIZE / 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -80,7 +80,7 @@ public class SystemKeyToolTests extends CliToolTestCase {
|
||||||
CliTool.ExitStatus status = generate.execute(settings, env);
|
CliTool.ExitStatus status = generate.execute(settings, env);
|
||||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||||
byte[] bytes = Streams.copyToByteArray(path.toFile());
|
byte[] bytes = Streams.copyToByteArray(path.toFile());
|
||||||
assertThat(bytes.length, is(InternalSignatureService.KEY_SIZE / 8));
|
assertThat(bytes.length, is(InternalCryptoService.KEY_SIZE / 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,7 +94,7 @@ public class SystemKeyToolTests extends CliToolTestCase {
|
||||||
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
|
CliTool.ExitStatus status = generate.execute(ImmutableSettings.EMPTY, env);
|
||||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||||
byte[] bytes = Streams.copyToByteArray(path.toFile());
|
byte[] bytes = Streams.copyToByteArray(path.toFile());
|
||||||
assertThat(bytes.length, is(InternalSignatureService.KEY_SIZE / 8));
|
assertThat(bytes.length, is(InternalCryptoService.KEY_SIZE / 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
|
@ -15,7 +15,7 @@ import org.elasticsearch.discovery.MasterNotDiscoveredException;
|
||||||
import org.elasticsearch.node.Node;
|
import org.elasticsearch.node.Node;
|
||||||
import org.elasticsearch.node.internal.InternalNode;
|
import org.elasticsearch.node.internal.InternalNode;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
import org.elasticsearch.test.ShieldIntegrationTest;
|
import org.elasticsearch.test.ShieldIntegrationTest;
|
||||||
import org.elasticsearch.test.ShieldSettingsSource;
|
import org.elasticsearch.test.ShieldSettingsSource;
|
||||||
import org.elasticsearch.transport.Transport;
|
import org.elasticsearch.transport.Transport;
|
||||||
|
@ -78,7 +78,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
|
||||||
@Test
|
@Test
|
||||||
public void testThatConnectionToServerTypeConnectionWorks() {
|
public void testThatConnectionToServerTypeConnectionWorks() {
|
||||||
Settings dataNodeSettings = internalCluster().getDataNodeInstance(Settings.class);
|
Settings dataNodeSettings = internalCluster().getDataNodeInstance(Settings.class);
|
||||||
String systemKeyFile = dataNodeSettings.get(InternalSignatureService.FILE_SETTING);
|
String systemKeyFile = dataNodeSettings.get(InternalCryptoService.FILE_SETTING);
|
||||||
|
|
||||||
Transport transport = internalCluster().getDataNodeInstance(Transport.class);
|
Transport transport = internalCluster().getDataNodeInstance(Transport.class);
|
||||||
TransportAddress transportAddress = transport.boundAddress().publishAddress();
|
TransportAddress transportAddress = transport.boundAddress().publishAddress();
|
||||||
|
@ -98,7 +98,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
|
||||||
.put("shield.transport.ssl", sslTransportEnabled())
|
.put("shield.transport.ssl", sslTransportEnabled())
|
||||||
.put("shield.audit.enabled", false)
|
.put("shield.audit.enabled", false)
|
||||||
.put(InternalNode.HTTP_ENABLED, false)
|
.put(InternalNode.HTTP_ENABLED, false)
|
||||||
.put(InternalSignatureService.FILE_SETTING, systemKeyFile)
|
.put(InternalCryptoService.FILE_SETTING, systemKeyFile)
|
||||||
.build();
|
.build();
|
||||||
try (Node node = nodeBuilder().client(true).settings(nodeSettings).node()) {
|
try (Node node = nodeBuilder().client(true).settings(nodeSettings).node()) {
|
||||||
assertGreenClusterState(node.client());
|
assertGreenClusterState(node.client());
|
||||||
|
@ -108,7 +108,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
|
||||||
@Test
|
@Test
|
||||||
public void testThatConnectionToClientTypeConnectionIsRejected() {
|
public void testThatConnectionToClientTypeConnectionIsRejected() {
|
||||||
Settings dataNodeSettings = internalCluster().getDataNodeInstance(Settings.class);
|
Settings dataNodeSettings = internalCluster().getDataNodeInstance(Settings.class);
|
||||||
String systemKeyFile = dataNodeSettings.get(InternalSignatureService.FILE_SETTING);
|
String systemKeyFile = dataNodeSettings.get(InternalCryptoService.FILE_SETTING);
|
||||||
|
|
||||||
File folder = createFolder(globalTempDir(), getClass().getSimpleName() + "-" + randomAsciiOfLength(10));
|
File folder = createFolder(globalTempDir(), getClass().getSimpleName() + "-" + randomAsciiOfLength(10));
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ public class ServerTransportFilterIntegrationTests extends ShieldIntegrationTest
|
||||||
.put("shield.transport.ssl", sslTransportEnabled())
|
.put("shield.transport.ssl", sslTransportEnabled())
|
||||||
.put("shield.audit.enabled", false)
|
.put("shield.audit.enabled", false)
|
||||||
.put(InternalNode.HTTP_ENABLED, false)
|
.put(InternalNode.HTTP_ENABLED, false)
|
||||||
.put(InternalSignatureService.FILE_SETTING, systemKeyFile)
|
.put(InternalCryptoService.FILE_SETTING, systemKeyFile)
|
||||||
.put("discovery.initial_state_timeout", "2s")
|
.put("discovery.initial_state_timeout", "2s")
|
||||||
.build();
|
.build();
|
||||||
try (Node node = nodeBuilder().client(true).settings(nodeSettings).build()) {
|
try (Node node = nodeBuilder().client(true).settings(nodeSettings).build()) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
||||||
import org.elasticsearch.common.transport.TransportAddress;
|
import org.elasticsearch.common.transport.TransportAddress;
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport;
|
import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport;
|
||||||
import org.elasticsearch.test.InternalTestCluster;
|
import org.elasticsearch.test.InternalTestCluster;
|
||||||
import org.elasticsearch.test.ShieldIntegrationTest;
|
import org.elasticsearch.test.ShieldIntegrationTest;
|
||||||
|
@ -57,7 +57,7 @@ public class TribeTests extends ShieldIntegrationTest {
|
||||||
final boolean sslTransportEnabled = globalClusterSettings.getAsBoolean("shield.transport.ssl", null);
|
final boolean sslTransportEnabled = globalClusterSettings.getAsBoolean("shield.transport.ssl", null);
|
||||||
|
|
||||||
//we need to make sure that all clusters and the tribe node use the same system key, we just point to the same file on all clusters
|
//we need to make sure that all clusters and the tribe node use the same system key, we just point to the same file on all clusters
|
||||||
byte[] systemKey = Files.readAllBytes(Paths.get(globalClusterSettings.get(InternalSignatureService.FILE_SETTING)));
|
byte[] systemKey = Files.readAllBytes(Paths.get(globalClusterSettings.get(InternalCryptoService.FILE_SETTING)));
|
||||||
|
|
||||||
//we run this part in @Before instead of beforeClass because we need to have the current cluster already assigned to global
|
//we run this part in @Before instead of beforeClass because we need to have the current cluster already assigned to global
|
||||||
//so that we can retrieve its settings and apply some of them the the second cluster (and tribe node too)
|
//so that we can retrieve its settings and apply some of them the the second cluster (and tribe node too)
|
||||||
|
@ -124,7 +124,7 @@ public class TribeTests extends ShieldIntegrationTest {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//forward the system key to the tribe clients, same file will be used
|
//forward the system key to the tribe clients, same file will be used
|
||||||
if (settingKey.equals(InternalSignatureService.FILE_SETTING)) {
|
if (settingKey.equals(InternalCryptoService.FILE_SETTING)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//forward ssl settings to the tribe clients, same certificates will be used
|
//forward ssl settings to the tribe clients, same certificates will be used
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.elasticsearch.shield.ShieldPlugin;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.shield.signature.InternalSignatureService;
|
import org.elasticsearch.shield.crypto.InternalCryptoService;
|
||||||
import org.elasticsearch.shield.test.ShieldTestUtils;
|
import org.elasticsearch.shield.test.ShieldTestUtils;
|
||||||
import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport;
|
import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport;
|
||||||
import org.elasticsearch.shield.transport.netty.ShieldNettyTransport;
|
import org.elasticsearch.shield.transport.netty.ShieldNettyTransport;
|
||||||
|
@ -111,7 +111,7 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
|
||||||
ImmutableSettings.Builder builder = ImmutableSettings.builder().put(super.node(nodeOrdinal))
|
ImmutableSettings.Builder builder = ImmutableSettings.builder().put(super.node(nodeOrdinal))
|
||||||
.put("plugin.types", ShieldPlugin.class.getName() + "," + licensePluginClass().getName())
|
.put("plugin.types", ShieldPlugin.class.getName() + "," + licensePluginClass().getName())
|
||||||
.put("shield.audit.enabled", randomBoolean())
|
.put("shield.audit.enabled", randomBoolean())
|
||||||
.put(InternalSignatureService.FILE_SETTING, writeFile(folder, "system_key", systemKey))
|
.put(InternalCryptoService.FILE_SETTING, writeFile(folder, "system_key", systemKey))
|
||||||
.put("shield.authc.realms.esusers.type", ESUsersRealm.TYPE)
|
.put("shield.authc.realms.esusers.type", ESUsersRealm.TYPE)
|
||||||
.put("shield.authc.realms.esusers.order", 0)
|
.put("shield.authc.realms.esusers.order", 0)
|
||||||
.put("shield.authc.realms.esusers.files.users", writeFile(folder, "users", configUsers()))
|
.put("shield.authc.realms.esusers.files.users", writeFile(folder, "users", configUsers()))
|
||||||
|
@ -180,7 +180,7 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
|
||||||
|
|
||||||
private static byte[] generateKey() {
|
private static byte[] generateKey() {
|
||||||
try {
|
try {
|
||||||
return InternalSignatureService.generateKey();
|
return InternalCryptoService.generateKey();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ElasticsearchException("exception while generating the system key", e);
|
throw new ElasticsearchException("exception while generating the system key", e);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue