Remove signing code completely from master (elastic/x-pack-elasticsearch#1719)

After improving the authorization of scroll requests and backporting to 5.x, we no longer need to
have any signing code in master. This commit removes it.

Original commit: elastic/x-pack-elasticsearch@8b65fd9338
This commit is contained in:
Jay Modi 2017-06-15 07:51:39 -06:00 committed by GitHub
parent 07fcf75dd9
commit d920cc7348
4 changed files with 12 additions and 496 deletions

View File

@ -13,15 +13,10 @@ import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.indices.close.CloseIndexAction; import org.elasticsearch.action.admin.indices.close.CloseIndexAction;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction;
import org.elasticsearch.action.admin.indices.open.OpenIndexAction; import org.elasticsearch.action.admin.indices.open.OpenIndexAction;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.ActionFilter; import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.action.support.DestructiveOperations;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -34,27 +29,18 @@ import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.SecurityContext;
import org.elasticsearch.xpack.security.action.SecurityActionMapper; import org.elasticsearch.xpack.security.action.SecurityActionMapper;
import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor;
import org.elasticsearch.xpack.security.audit.AuditTrail;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.AuthorizationUtils; import org.elasticsearch.xpack.security.authz.AuthorizationUtils;
import org.elasticsearch.xpack.security.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.xpack.security.authz.privilege.HealthAndStatsPrivilege;
import org.elasticsearch.xpack.security.crypto.CryptoService;
import org.elasticsearch.xpack.security.support.Automatons; import org.elasticsearch.xpack.security.support.Automatons;
import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.SystemUser;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier;
import static org.elasticsearch.xpack.security.support.Exceptions.authorizationError;
public class SecurityActionFilter extends AbstractComponent implements ActionFilter { public class SecurityActionFilter extends AbstractComponent implements ActionFilter {
@ -63,33 +49,25 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
private final AuthenticationService authcService; private final AuthenticationService authcService;
private final AuthorizationService authzService; private final AuthorizationService authzService;
private final CryptoService cryptoService;
private final AuditTrail auditTrail;
private final SecurityActionMapper actionMapper = new SecurityActionMapper(); private final SecurityActionMapper actionMapper = new SecurityActionMapper();
private final Set<RequestInterceptor> requestInterceptors; private final Set<RequestInterceptor> requestInterceptors;
private final XPackLicenseState licenseState; private final XPackLicenseState licenseState;
private final ThreadContext threadContext; private final ThreadContext threadContext;
private final SecurityContext securityContext; private final SecurityContext securityContext;
private final DestructiveOperations destructiveOperations; private final DestructiveOperations destructiveOperations;
private final ClusterService clusterService;
@Inject @Inject
public SecurityActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, public SecurityActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService,
CryptoService cryptoService, AuditTrailService auditTrail, XPackLicenseState licenseState, XPackLicenseState licenseState, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool,
Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool, SecurityContext securityContext, DestructiveOperations destructiveOperations) {
SecurityContext securityContext, DestructiveOperations destructiveOperations,
ClusterService clusterService) {
super(settings); super(settings);
this.authcService = authcService; this.authcService = authcService;
this.authzService = authzService; this.authzService = authzService;
this.cryptoService = cryptoService;
this.auditTrail = auditTrail;
this.licenseState = licenseState; this.licenseState = licenseState;
this.requestInterceptors = requestInterceptors; this.requestInterceptors = requestInterceptors;
this.threadContext = threadPool.getThreadContext(); this.threadContext = threadPool.getThreadContext();
this.securityContext = securityContext; this.securityContext = securityContext;
this.destructiveOperations = destructiveOperations; this.destructiveOperations = destructiveOperations;
this.clusterService = clusterService;
} }
@Override @Override
@ -108,17 +86,10 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
if (licenseState.isAuthAllowed()) { if (licenseState.isAuthAllowed()) {
final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action); final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action);
final Supplier<ThreadContext.StoredContext> toRestore = threadContext.newRestorableContext(true); final ActionListener<ActionResponse> contextPreservingListener =
final ActionListener<ActionResponse> signingListener = new ContextPreservingActionListener<>(toRestore, ContextPreservingActionListener.wrapPreservingContext(listener, threadContext);
ActionListener.wrap(r -> { ActionListener<Void> authenticatedListener = ActionListener.wrap(
try { (aVoid) -> chain.proceed(task, action, request, contextPreservingListener), contextPreservingListener::onFailure);
listener.onResponse(sign(r));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}, listener::onFailure));
ActionListener<Void> authenticatedListener =
ActionListener.wrap((aVoid) -> chain.proceed(task, action, request, signingListener), signingListener::onFailure);
try { try {
if (useSystemUser) { if (useSystemUser) {
securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> { securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> {
@ -182,15 +153,14 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
(userRoles, runAsRoles) -> { (userRoles, runAsRoles) -> {
authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles); authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles);
final User user = authentication.getUser(); final User user = authentication.getUser();
ActionRequest unsignedRequest = unsign(user, securityAction, request);
/* /*
* We use a separate concept for code that needs to be run after authentication and authorization that could * We use a separate concept for code that needs to be run after authentication and authorization that could
* affect the running of the action. This is done to make it more clear of the state of the request. * affect the running of the action. This is done to make it more clear of the state of the request.
*/ */
for (RequestInterceptor interceptor : requestInterceptors) { for (RequestInterceptor interceptor : requestInterceptors) {
if (interceptor.supports(unsignedRequest)) { if (interceptor.supports(request)) {
interceptor.intercept(unsignedRequest, user); interceptor.intercept(request, user);
} }
} }
listener.onResponse(null); listener.onResponse(null);
@ -198,91 +168,4 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
asyncAuthorizer.authorize(authzService); asyncAuthorizer.authorize(authzService);
} }
} }
ActionRequest unsign(User user, String action, final ActionRequest request) {
try {
// In order to provide backwards compatibility with previous versions that always signed scroll ids
// we sign the scroll requests and do not allow unsigned requests until all of the nodes in the cluster
// have been upgraded to a version that does not sign scroll ids and instead relies improved scroll
// authorization. It is important to note that older versions do not actually sign if the system key
// does not exist so we need to take that into account as well.
// TODO Remove any signing from master!
final ClusterState state = clusterService.state();
final boolean signingRequired = state.nodes().getMinNodeVersion().before(Version.V_5_5_0) &&
cryptoService.isSystemKeyPresent();
if (request instanceof SearchScrollRequest) {
SearchScrollRequest scrollRequest = (SearchScrollRequest) request;
String scrollId = scrollRequest.scrollId();
if (signingRequired) {
if (cryptoService.isSigned(scrollId)) {
scrollRequest.scrollId(cryptoService.unsignAndVerify(scrollId));
} else {
logger.error("scroll id [{}] is not signed but is expected to be. nodes [{}], minimum node version [{}]",
scrollId, state.nodes(), state.nodes().getMinNodeVersion());
// if we get a unsigned scroll request and not all nodes are up to date, then we cannot trust
// this scroll id and reject it
auditTrail.tamperedRequest(user, action, request);
throw authorizationError("invalid request");
}
} else if (cryptoService.isSigned(scrollId)) {
// if signing isn't required we could still get a signed ID from an already running scroll or
// a node that hasn't received the current cluster state that shows signing isn't required
scrollRequest.scrollId(cryptoService.unsignAndVerify(scrollId));
}
// else the scroll id is fine on the request so don't do anything
} else if (request instanceof ClearScrollRequest) {
ClearScrollRequest clearScrollRequest = (ClearScrollRequest) request;
final boolean isClearAllScrollRequest = clearScrollRequest.scrollIds().contains("_all");
if (isClearAllScrollRequest == false) {
List<String> signedIds = clearScrollRequest.scrollIds();
List<String> unsignedIds = new ArrayList<>(signedIds.size());
for (String signedId : signedIds) {
if (signingRequired) {
if (cryptoService.isSigned(signedId)) {
unsignedIds.add(cryptoService.unsignAndVerify(signedId));
} else {
logger.error("scroll id [{}] is not signed but is expected to be. nodes [{}], minimum node version [{}]",
signedId, state.nodes(), state.nodes().getMinNodeVersion());
// if we get a unsigned scroll request and not all nodes are up to date, then we cannot trust
// this scroll id and reject it
auditTrail.tamperedRequest(user, action, request);
throw authorizationError("invalid request");
}
} else if (cryptoService.isSigned(signedId)) {
// if signing isn't required we could still get a signed ID from an already running scroll or
// a node that hasn't received the current cluster state that shows signing isn't required
unsignedIds.add(cryptoService.unsignAndVerify(signedId));
} else {
// the id is not signed and we allow unsigned requests so just add it
unsignedIds.add(signedId);
}
}
clearScrollRequest.scrollIds(unsignedIds);
}
}
} catch (IllegalArgumentException | IllegalStateException e) {
// this can happen when we decode invalid base64 or get a invalid scroll id
auditTrail.tamperedRequest(user, action, request);
throw authorizationError("invalid request. {}", e.getMessage());
}
return request;
}
private <Response extends ActionResponse> Response sign(Response response) throws IOException {
if (response instanceof SearchResponse) {
// In order to provide backwards compatibility with previous versions that always signed scroll ids
// we sign the scroll requests and do not allow unsigned requests until all of the nodes in the cluster
// have been upgraded to a version that supports unsigned scroll ids
final boolean sign = clusterService.state().nodes().getMinNodeVersion().before(Version.V_6_0_0_alpha2);
if (sign) {
SearchResponse searchResponse = (SearchResponse) response;
String scrollId = searchResponse.getScrollId();
if (scrollId != null && !cryptoService.isSigned(scrollId)) {
searchResponse.scrollId(cryptoService.sign(scrollId));
}
}
}
return response;
}
} }

View File

@ -9,13 +9,11 @@ import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -24,7 +22,6 @@ import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
@ -36,7 +33,6 @@ import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.security.authc.support.CharArrays; import org.elasticsearch.xpack.security.authc.support.CharArrays;
import static org.elasticsearch.xpack.security.Security.setting; import static org.elasticsearch.xpack.security.Security.setting;
import static org.elasticsearch.xpack.security.authc.support.CharArrays.constantTimeEquals;
/** /**
* Service that provides cryptographic methods based on a shared system key * Service that provides cryptographic methods based on a shared system key
@ -47,14 +43,11 @@ public class CryptoService extends AbstractComponent {
public static final int KEY_SIZE = 1024; public static final int KEY_SIZE = 1024;
static final String FILE_NAME = "system_key"; 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_ENCRYPTION_ALGORITHM = "AES/CTR/NoPadding";
static final String DEFAULT_KEY_ALGORITH = "AES"; static final String DEFAULT_KEY_ALGORITH = "AES";
static final String ENCRYPTED_TEXT_PREFIX = "::es_encrypted::"; static final String ENCRYPTED_TEXT_PREFIX = "::es_encrypted::";
static final int DEFAULT_KEY_LENGTH = 128; static final int DEFAULT_KEY_LENGTH = 128;
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$[^\\$]*\\$\\$.+");
private static final Setting<Boolean> SYSTEM_KEY_REQUIRED_SETTING = private static final Setting<Boolean> SYSTEM_KEY_REQUIRED_SETTING =
Setting.boolSetting(setting("system_key.required"), false, Property.NodeScope); Setting.boolSetting(setting("system_key.required"), false, Property.NodeScope);
private static final Setting<String> ENCRYPTION_ALGO_SETTING = private static final Setting<String> ENCRYPTION_ALGO_SETTING =
@ -68,11 +61,9 @@ public class CryptoService extends AbstractComponent {
private final String encryptionAlgorithm; private final String encryptionAlgorithm;
private final int ivLength; private final int ivLength;
/* /*
* Scroll ids are signed using the system key to authenticate them as we currently do not have a proper way to authorize these requests * The encryption key is derived from the system key.
* and the key is also used for encrypting sensitive data stored in watches. The encryption key is derived from the system key.
*/ */
private final SecretKey encryptionKey; private final SecretKey encryptionKey;
private final SecretKey systemKey;
public CryptoService(Settings settings, Environment env) throws IOException { public CryptoService(Settings settings, Environment env) throws IOException {
super(settings); super(settings);
@ -86,7 +77,7 @@ public class CryptoService extends AbstractComponent {
} }
Path keyFile = resolveSystemKey(env); Path keyFile = resolveSystemKey(env);
systemKey = readSystemKey(keyFile, SYSTEM_KEY_REQUIRED_SETTING.get(settings)); SecretKey systemKey = readSystemKey(keyFile, SYSTEM_KEY_REQUIRED_SETTING.get(settings));
try { try {
encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm); encryptionKey = encryptionKey(systemKey, keyLength, keyAlgorithm);
@ -129,70 +120,6 @@ public class CryptoService extends AbstractComponent {
return null; return null;
} }
/**
* Signs the given text and returns the signed text (original text + signature)
* @param text the string to sign
*/
public String sign(String text) throws IOException {
if (systemKey != null) {
String sigStr = signInternal(text, systemKey);
return "$$" + sigStr.length() + "$$$$" + sigStr + text;
} else {
return 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 IllegalArgumentException} is thrown.
* @param signedText the string to unsign and verify
*/
public String unsignAndVerify(String signedText) {
if (systemKey == null) {
return signedText;
}
if (!signedText.startsWith("$$") || signedText.length() < 2) {
throw new IllegalArgumentException("tampered signed text");
}
// $$34$$$$sigtext
String[] pieces = signedText.split("\\$\\$");
if (pieces.length != 4 || pieces[0].isEmpty() == false || pieces[2].isEmpty() == false) {
logger.debug("received signed text [{}] with [{}] parts", signedText, pieces.length);
throw new IllegalArgumentException("tampered signed text");
}
String text;
String receivedSignature;
try {
int length = Integer.parseInt(pieces[1]);
receivedSignature = pieces[3].substring(0, length);
text = pieces[3].substring(length);
} catch (Exception e) {
logger.error("error occurred while parsing signed text", e);
throw new IllegalArgumentException("tampered signed text");
}
try {
String sig = signInternal(text, systemKey);
if (constantTimeEquals(sig, receivedSignature)) {
return text;
}
} catch (Exception e) {
logger.error("error occurred while verifying signed text", e);
throw new IllegalStateException("error while verifying the signed text");
}
throw new IllegalArgumentException("tampered signed text");
}
/**
* Checks whether the given text is signed.
*/
public boolean isSigned(String text) {
return SIG_PATTERN.matcher(text).matches();
}
/** /**
* Encrypts the provided char array and returns the encrypted values in a char array * Encrypts the provided char array and returns the encrypted values in a char array
* @param chars the characters to encrypt * @param chars the characters to encrypt
@ -255,14 +182,6 @@ public class CryptoService extends AbstractComponent {
return this.encryptionKey != null; return this.encryptionKey != null;
} }
/**
* Flag for callers to determine if values will actually be signed or returned as is
* @return true if values will be signed
*/
public boolean isSystemKeyPresent() {
return this.systemKey != null;
}
private byte[] encryptInternal(byte[] bytes, SecretKey key) { private byte[] encryptInternal(byte[] bytes, SecretKey key) {
byte[] iv = new byte[ivLength]; byte[] iv = new byte[ivLength];
secureRandom.nextBytes(iv); secureRandom.nextBytes(iv);
@ -297,22 +216,6 @@ public class CryptoService extends AbstractComponent {
} }
} }
static Mac createMac(SecretKey key) {
try {
Mac mac = HmacSHA1Provider.hmacSHA1();
mac.init(key);
return mac;
} catch (Exception e) {
throw new ElasticsearchException("could not initialize mac", e);
}
}
private static String signInternal(String text, SecretKey key) throws IOException {
Mac mac = createMac(key);
byte[] sig = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
return Base64.getUrlEncoder().encodeToString(sig);
}
static Cipher cipher(int mode, String encryptionAlgorithm, SecretKey key, byte[] initializationVector) { static Cipher cipher(int mode, String encryptionAlgorithm, SecretKey key, byte[] initializationVector) {
try { try {
@ -346,28 +249,6 @@ public class CryptoService extends AbstractComponent {
return new SecretKeySpec(truncatedDigest, algorithm); return new SecretKeySpec(truncatedDigest, algorithm);
} }
/**
* Provider class for the HmacSHA1 {@link Mac} that provides an optimization by using a thread local instead of calling
* Mac#getInstance and obtaining a lock (in the internals)
*/
private static class HmacSHA1Provider {
private static final ThreadLocal<Mac> MAC = ThreadLocal.withInitial(() -> {
try {
return Mac.getInstance(HMAC_ALGO);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("could not create Mac instance with algorithm [" + HMAC_ALGO + "]", e);
}
});
private static Mac hmacSHA1() {
Mac instance = MAC.get();
instance.reset();
return instance;
}
}
public static void addSettings(List<Setting<?>> settings) { public static void addSettings(List<Setting<?>> settings) {
settings.add(ENCRYPTION_KEY_LENGTH_SETTING); settings.add(ENCRYPTION_KEY_LENGTH_SETTING);
settings.add(ENCRYPTION_KEY_ALGO_SETTING); settings.add(ENCRYPTION_KEY_ALGO_SETTING);

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.security.action.filter; package org.elasticsearch.xpack.security.action.filter;
import org.apache.lucene.util.SetOnce; import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequest;
@ -14,7 +13,6 @@ import org.elasticsearch.action.MockIndicesRequest;
import org.elasticsearch.action.admin.indices.close.CloseIndexAction; import org.elasticsearch.action.admin.indices.close.CloseIndexAction;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction;
import org.elasticsearch.action.admin.indices.open.OpenIndexAction; import org.elasticsearch.action.admin.indices.open.OpenIndexAction;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.action.support.DestructiveOperations;
@ -22,41 +20,33 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.Task;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.SecurityContext;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; import org.elasticsearch.xpack.security.authc.Authentication.RealmRef;
import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.crypto.CryptoService;
import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.SystemUser;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
import org.junit.Before; import org.junit.Before;
import org.mockito.ArgumentCaptor;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA; import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
@ -65,20 +55,15 @@ import static org.mockito.Mockito.when;
public class SecurityActionFilterTests extends ESTestCase { public class SecurityActionFilterTests extends ESTestCase {
private AuthenticationService authcService; private AuthenticationService authcService;
private AuthorizationService authzService; private AuthorizationService authzService;
private CryptoService cryptoService;
private AuditTrailService auditTrail;
private XPackLicenseState licenseState; private XPackLicenseState licenseState;
private SecurityActionFilter filter; private SecurityActionFilter filter;
private ThreadContext threadContext; private ThreadContext threadContext;
private ClusterService clusterService;
private boolean failDestructiveOperations; private boolean failDestructiveOperations;
@Before @Before
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);
cryptoService = mock(CryptoService.class);
auditTrail = mock(AuditTrailService.class);
licenseState = mock(XPackLicenseState.class); licenseState = mock(XPackLicenseState.class);
when(licenseState.isAuthAllowed()).thenReturn(true); when(licenseState.isAuthAllowed()).thenReturn(true);
when(licenseState.isStatsAndHealthAllowed()).thenReturn(true); when(licenseState.isStatsAndHealthAllowed()).thenReturn(true);
@ -90,9 +75,7 @@ public class SecurityActionFilterTests extends ESTestCase {
.put(DestructiveOperations.REQUIRES_NAME_SETTING.getKey(), failDestructiveOperations).build(); .put(DestructiveOperations.REQUIRES_NAME_SETTING.getKey(), failDestructiveOperations).build();
DestructiveOperations destructiveOperations = new DestructiveOperations(settings, DestructiveOperations destructiveOperations = new DestructiveOperations(settings,
new ClusterSettings(settings, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))); new ClusterSettings(settings, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING)));
clusterService = mock(ClusterService.class);
ClusterState state = mock(ClusterState.class); ClusterState state = mock(ClusterState.class);
when(clusterService.state()).thenReturn(state);
DiscoveryNodes nodes = DiscoveryNodes.builder() DiscoveryNodes nodes = DiscoveryNodes.builder()
.add(new DiscoveryNode("id1", .add(new DiscoveryNode("id1",
new TransportAddress(TransportAddress.META_ADDRESS, randomIntBetween(49000, 65500)), Version.CURRENT)) new TransportAddress(TransportAddress.META_ADDRESS, randomIntBetween(49000, 65500)), Version.CURRENT))
@ -102,8 +85,8 @@ public class SecurityActionFilterTests extends ESTestCase {
when(state.nodes()).thenReturn(nodes); when(state.nodes()).thenReturn(nodes);
SecurityContext securityContext = new SecurityContext(settings, threadContext); SecurityContext securityContext = new SecurityContext(settings, threadContext);
filter = new SecurityActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, filter = new SecurityActionFilter(Settings.EMPTY, authcService, authzService,
licenseState, new HashSet<>(), threadPool, securityContext, destructiveOperations, clusterService); licenseState, new HashSet<>(), threadPool, securityContext, destructiveOperations);
} }
public void testApply() throws Exception { public void testApply() throws Exception {
@ -126,7 +109,6 @@ public class SecurityActionFilterTests extends ESTestCase {
callback.onResponse(empty); callback.onResponse(empty);
return Void.TYPE; return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class)); }).when(authzService).roles(any(User.class), any(ActionListener.class));
doReturn(request).when(spy(filter)).unsign(user, "_action", request);
filter.apply(task, "_action", request, listener, chain); filter.apply(task, "_action", request, listener, chain);
verify(authzService).authorize(authentication, "_action", request, empty, null); verify(authzService).authorize(authentication, "_action", request, empty, null);
verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class)); verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class));
@ -155,7 +137,6 @@ public class SecurityActionFilterTests extends ESTestCase {
callback.onResponse(empty); callback.onResponse(empty);
return Void.TYPE; return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class)); }).when(authzService).roles(any(User.class), any(ActionListener.class));
doReturn(request).when(spy(filter)).unsign(user, "_action", request);
assertNull(threadContext.getTransient(Authentication.AUTHENTICATION_KEY)); assertNull(threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
filter.apply(task, "_action", request, listener, chain); filter.apply(task, "_action", request, listener, chain);
@ -189,11 +170,6 @@ public class SecurityActionFilterTests extends ESTestCase {
callback.onResponse(threadContext.getTransient(Authentication.AUTHENTICATION_KEY)); callback.onResponse(threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
return Void.TYPE; return Void.TYPE;
}).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); }).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
doReturn(request).when(spy(filter)).unsign(user, action, request);
doAnswer((i) -> {
String text = (String) i.getArguments()[0];
return text;
}).when(cryptoService).sign(any(String.class));
filter.apply(task, action, request, listener, chain); filter.apply(task, action, request, listener, chain);
@ -230,7 +206,6 @@ public class SecurityActionFilterTests extends ESTestCase {
callback.onResponse(empty); callback.onResponse(empty);
return Void.TYPE; return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class)); }).when(authzService).roles(any(User.class), any(ActionListener.class));
doReturn(request).when(spy(filter)).unsign(user, action, request);
filter.apply(task, action, request, listener, chain); filter.apply(task, action, request, listener, chain);
if (failDestructiveOperations) { if (failDestructiveOperations) {
verify(listener).onFailure(isA(IllegalArgumentException.class)); verify(listener).onFailure(isA(IllegalArgumentException.class));
@ -268,139 +243,6 @@ public class SecurityActionFilterTests extends ESTestCase {
verifyNoMoreInteractions(chain); verifyNoMoreInteractions(chain);
} }
public void testActionSignature() throws Exception {
SearchScrollRequest request = new SearchScrollRequest("signed_scroll_id");
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
User user = mock(User.class);
Task task = mock(Task.class);
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[3];
callback.onResponse(authentication);
return Void.TYPE;
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
when(cryptoService.isSystemKeyPresent()).thenReturn(true);
when(cryptoService.isSigned("signed_scroll_id")).thenReturn(true);
when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id");
final Role empty = Role.EMPTY;
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[1];
callback.onResponse(empty);
return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class));
filter.apply(task, "_action", request, listener, chain);
assertThat(request.scrollId(), equalTo("scroll_id"));
verify(authzService).authorize(authentication, "_action", request, empty, null);
verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class));
}
public void testUnsignedWithOldVersionNode() throws Exception {
DiscoveryNodes nodes = DiscoveryNodes.builder(clusterService.state().nodes())
.add(new DiscoveryNode("id3",
new TransportAddress(TransportAddress.META_ADDRESS, randomIntBetween(49000, 65500)), Version.V_5_4_0))
.build();
when(clusterService.state().nodes()).thenReturn(nodes);
SearchScrollRequest request = new SearchScrollRequest("unsigned");
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
User user = mock(User.class);
Task task = mock(Task.class);
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[3];
callback.onResponse(authentication);
return Void.TYPE;
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
when(cryptoService.isSigned("unsigned")).thenReturn(false);
when(cryptoService.isSystemKeyPresent()).thenReturn(true);
final Role empty = Role.EMPTY;
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[1];
callback.onResponse(empty);
return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class));
filter.apply(task, "_action", request, listener, chain);
ArgumentCaptor<ElasticsearchSecurityException> captor = ArgumentCaptor.forClass(ElasticsearchSecurityException.class);
verify(listener).onFailure(captor.capture());
ElasticsearchSecurityException e = captor.getValue();
assertEquals("invalid request", e.getMessage());
assertEquals(RestStatus.FORBIDDEN, e.status());
verify(authzService).authorize(authentication, "_action", request, empty, null);
verifyZeroInteractions(chain);
}
public void testUnsigned() throws Exception {
DiscoveryNodes nodes = DiscoveryNodes.builder()
.add(new DiscoveryNode("id1", new TransportAddress(TransportAddress.META_ADDRESS, randomIntBetween(49000, 65500)),
Version.V_6_0_0_alpha2))
.add(new DiscoveryNode("id2", new TransportAddress(TransportAddress.META_ADDRESS, randomIntBetween(49000, 65500)),
Version.V_5_5_0))
.build();
when(clusterService.state().nodes()).thenReturn(nodes);
SearchScrollRequest request = new SearchScrollRequest("unsigned");
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
User user = mock(User.class);
Task task = mock(Task.class);
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[3];
callback.onResponse(authentication);
return Void.TYPE;
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
when(cryptoService.isSigned("unsigned")).thenReturn(false);
when(cryptoService.isSystemKeyPresent()).thenReturn(randomBoolean());
final Role empty = Role.EMPTY;
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[1];
callback.onResponse(empty);
return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class));
filter.apply(task, "_action", request, listener, chain);
assertThat(request.scrollId(), equalTo("unsigned"));
verify(authzService).authorize(authentication, "_action", request, empty, null);
verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class));
}
public void testActionSignatureError() throws Exception {
SearchScrollRequest request = new SearchScrollRequest("scroll_id");
ActionListener listener = mock(ActionListener.class);
ActionFilterChain chain = mock(ActionFilterChain.class);
IllegalArgumentException sigException = new IllegalArgumentException("bad bad boy");
User user = mock(User.class);
Task task = mock(Task.class);
Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null);
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[3];
callback.onResponse(authentication);
return Void.TYPE;
}).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class));
when(cryptoService.isSystemKeyPresent()).thenReturn(true);
when(cryptoService.isSigned("scroll_id")).thenReturn(true);
doThrow(sigException).when(cryptoService).unsignAndVerify("scroll_id");
doAnswer((i) -> {
ActionListener callback =
(ActionListener) i.getArguments()[1];
callback.onResponse(Role.EMPTY);
return Void.TYPE;
}).when(authzService).roles(any(User.class), any(ActionListener.class));
filter.apply(task, "_action", request, listener, chain);
verify(listener).onFailure(isA(ElasticsearchSecurityException.class));
verify(auditTrail).tamperedRequest(user, "_action", request);
verifyNoMoreInteractions(chain);
}
public void testApplyUnlicensed() throws Exception { public void testApplyUnlicensed() throws Exception {
ActionRequest request = mock(ActionRequest.class); ActionRequest request = mock(ActionRequest.class);
ActionListener listener = mock(ActionListener.class); ActionListener listener = mock(ActionListener.class);

View File

@ -20,7 +20,6 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class CryptoServiceTests extends ESTestCase { public class CryptoServiceTests extends ESTestCase {
private Settings settings; private Settings settings;
@ -42,95 +41,6 @@ public class CryptoServiceTests extends ESTestCase {
env = new Environment(settings); env = new Environment(settings);
} }
public void testSigned() throws Exception {
CryptoService service = new CryptoService(settings, env);
String text = randomAlphaOfLength(10);
String signed = service.sign(text);
assertThat(service.isSigned(signed), is(true));
}
public void testSignAndUnsign() throws Exception {
CryptoService service = new CryptoService(settings, env);
String text = randomAlphaOfLength(10);
String signed = service.sign(text);
assertThat(text.equals(signed), is(false));
String text2 = service.unsignAndVerify(signed);
assertThat(text, equalTo(text2));
}
public void testSignAndUnsignNoKeyFile() throws Exception {
Files.delete(keyFile);
CryptoService service = new CryptoService(Settings.EMPTY, env);
final String text = randomAlphaOfLength(10);
String signed = service.sign(text);
assertThat(text, equalTo(signed));
String unsigned = service.unsignAndVerify(signed);
assertThat(unsigned, equalTo(text));
}
public void testTamperedSignature() throws Exception {
CryptoService service = new CryptoService(settings, env);
String text = randomAlphaOfLength(10);
String signed = service.sign(text);
int i = signed.indexOf("$$", 2);
int length = Integer.parseInt(signed.substring(2, i));
String fakeSignature = randomAlphaOfLength(length);
String fakeSignedText = "$$" + length + "$$" + fakeSignature + signed.substring(i + 2 + length);
try {
service.unsignAndVerify(fakeSignedText);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), is(equalTo("tampered signed text")));
assertThat(e.getCause(), is(nullValue()));
}
}
public void testTamperedSignatureOneChar() throws Exception {
CryptoService service = new CryptoService(settings, env);
String text = randomAlphaOfLength(10);
String signed = service.sign(text);
int i = signed.indexOf("$$", 2);
int length = Integer.parseInt(signed.substring(2, i));
StringBuilder fakeSignature = new StringBuilder(signed.substring(i + 2, i + 2 + length));
fakeSignature.setCharAt(randomIntBetween(0, fakeSignature.length() - 1), randomAlphaOfLength(1).charAt(0));
String fakeSignedText = "$$" + length + "$$" + fakeSignature.toString() + signed.substring(i + 2 + length);
try {
service.unsignAndVerify(fakeSignedText);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), is(equalTo("tampered signed text")));
assertThat(e.getCause(), is(nullValue()));
}
}
public void testTamperedSignatureLength() throws Exception {
CryptoService service = new CryptoService(settings, env);
String text = randomAlphaOfLength(10);
String signed = service.sign(text);
int i = signed.indexOf("$$", 2);
int length = Integer.parseInt(signed.substring(2, i));
String fakeSignature = randomAlphaOfLength(length);
// Smaller sig length
String fakeSignedText = "$$" + randomIntBetween(0, length - 1) + "$$" + fakeSignature + signed.substring(i + 2 + length);
try {
service.unsignAndVerify(fakeSignedText);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), is(equalTo("tampered signed text")));
}
// Larger sig length
fakeSignedText = "$$" + randomIntBetween(length + 1, Integer.MAX_VALUE) + "$$" + fakeSignature + signed.substring(i + 2 + length);
try {
service.unsignAndVerify(fakeSignedText);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), is(equalTo("tampered signed text")));
assertThat(e.getCause(), is(nullValue()));
}
}
public void testEncryptionAndDecryptionChars() throws Exception { public void testEncryptionAndDecryptionChars() throws Exception {
CryptoService service = new CryptoService(settings, env); CryptoService service = new CryptoService(settings, env);
assertThat(service.isEncryptionEnabled(), is(true)); assertThat(service.isEncryptionEnabled(), is(true));