Loosen setup mode restrictions for upgrade tests (elastic/x-pack-elasticsearch#1927)

This commit is related to elastic/x-pack-elasticsearch#1896. Currently setup mode means that the
password must be set post 6.0 for using x-pack. This interferes with
upgrade tests as setting the password fails without a properly
upgraded security index.

This commit loosens two aspects of the security.

1. The old default password will be accept in setup mode (requests
from localhost).
2. All request types can be submitted in setup mode.

Original commit: elastic/x-pack-elasticsearch@8a2a577038
This commit is contained in:
Tim Brooks 2017-07-06 10:37:48 -05:00 committed by GitHub
parent 4e03c657a3
commit d95c365e64
8 changed files with 37 additions and 196 deletions

View File

@ -50,6 +50,7 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
public static final SecureString EMPTY_PASSWORD_TEXT = new SecureString("".toCharArray()); public static final SecureString EMPTY_PASSWORD_TEXT = new SecureString("".toCharArray());
static final char[] EMPTY_PASSWORD_HASH = Hasher.BCRYPT.hash(EMPTY_PASSWORD_TEXT); static final char[] EMPTY_PASSWORD_HASH = Hasher.BCRYPT.hash(EMPTY_PASSWORD_TEXT);
static final char[] OLD_DEFAULT_PASSWORD_HASH = Hasher.BCRYPT.hash(new SecureString("changeme".toCharArray()));
private static final ReservedUserInfo DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, true, true); private static final ReservedUserInfo DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, true, true);
private static final ReservedUserInfo DISABLED_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, false, true); private static final ReservedUserInfo DISABLED_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, false, true);
@ -102,9 +103,20 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
Runnable action; Runnable action;
if (userInfo != null) { if (userInfo != null) {
try { try {
if (userInfo.hasEmptyPassword && isSetupMode(token.principal(), acceptEmptyPassword) == false) { if (userInfo.hasEmptyPassword) {
// norelease
// Accepting the OLD_DEFAULT_PASSWORD_HASH is a transition step. We do not want to support
// this in a release.
if (isSetupMode(token.principal(), acceptEmptyPassword) == false) {
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]", action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
token.principal())); token.principal()));
} else if (verifyPassword(userInfo, token)
|| Hasher.BCRYPT.verify(token.credentials(), OLD_DEFAULT_PASSWORD_HASH)) {
action = () -> listener.onResponse(getUser(token.principal(), userInfo));
} else {
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
token.principal()));
}
} else if (verifyPassword(userInfo, token)) { } else if (verifyPassword(userInfo, token)) {
final User user = getUser(token.principal(), userInfo); final User user = getUser(token.principal(), userInfo);
action = () -> listener.onResponse(user); action = () -> listener.onResponse(user);
@ -113,7 +125,7 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
token.principal())); token.principal()));
} }
} finally { } finally {
if (userInfo.passwordHash != EMPTY_PASSWORD_HASH) { if (userInfo.passwordHash != EMPTY_PASSWORD_HASH && userInfo.passwordHash != OLD_DEFAULT_PASSWORD_HASH) {
Arrays.fill(userInfo.passwordHash, (char) 0); Arrays.fill(userInfo.passwordHash, (char) 0);
} }
} }

View File

@ -149,11 +149,14 @@ public class AuthorizationService extends AbstractComponent {
throw denial(authentication, action, request); throw denial(authentication, action, request);
} }
// norelease
// TODO: This functionality is disabled as it is not yet compatible with the upgrade process
// If the user is the elastic user in setup mode, then only change password requests can be authorized // If the user is the elastic user in setup mode, then only change password requests can be authorized
if (ElasticUser.isElasticUserInSetupMode(authentication.getUser()) // if (ElasticUser.isElasticUserInSetupMode(authentication.getUser())
&& ChangePasswordAction.NAME.equals(action) == false) { // && ChangePasswordAction.NAME.equals(action) == false
throw denial(authentication, action, request); // && ClusterHealthAction.NAME.equals(action) == false) {
} // throw denial(authentication, action, request);
// }
// get the roles of the authenticated user, which may be different than the effective // get the roles of the authenticated user, which may be different than the effective
Role permission = userRole; Role permission = userRole;

View File

@ -349,6 +349,7 @@ public class AuthorizationServiceTests extends ESTestCase {
verifyNoMoreInteractions(auditTrail); verifyNoMoreInteractions(auditTrail);
} }
@AwaitsFix(bugUrl = "https://github.com/elastic/x-pack-elasticsearch/issues/1217")
public void testElasticUserOnlyAuthorizedForChangePasswordRequestsInSetupMode() { public void testElasticUserOnlyAuthorizedForChangePasswordRequestsInSetupMode() {
final User user = new ElasticUser(true, true); final User user = new ElasticUser(true, true);
final ChangePasswordRequest changePasswordrequest = new ChangePasswordRequestBuilder(mock(Client.class)) final ChangePasswordRequest changePasswordrequest = new ChangePasswordRequestBuilder(mock(Client.class))

View File

@ -14,99 +14,13 @@ dependencies {
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'testArtifacts') testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'testArtifacts')
} }
Closure changePasswordAndWaitWithAuth = { NodeInfo node, AntBuilder ant ->
File tmpFile = new File(node.cwd, 'wait.success')
String password
if (Version.fromString(node.nodeVersion).onOrAfter('6.0.0')) {
password = ""
} else {
password = "changeme"
}
for (int i = 0; i < 10; i++) {
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_xpack/security/user/elastic/_password")
.openConnection();
httpURLConnection.setRequestProperty("Authorization", "Basic " +
Base64.getEncoder().encodeToString("elastic:${password}".getBytes(StandardCharsets.UTF_8)));
httpURLConnection.setRequestMethod("PUT");
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
httpURLConnection.connect();
OutputStream out = httpURLConnection.getOutputStream();
out.write("{\"password\": \"x-pack-test-password\"}".getBytes(StandardCharsets.UTF_8));
out.close()
if (httpURLConnection.getResponseCode() == 200) {
break
}
} catch (Exception e) {
httpURLConnection.disconnect()
if (i == 9) {
logger.error("final attempt to set password", e)
} else {
logger.debug("failed to set elastic password", e)
}
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
// did not start, so wait a bit before trying again
Thread.sleep(500L);
}
// wait up to twenty seconds
final long stopTime = System.currentTimeMillis() + 20000L;
Exception lastException = null;
while (System.currentTimeMillis() < stopTime) {
lastException = null;
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=${node.config.numNodes}&wait_for_status=yellow").openConnection();
httpURLConnection.setRequestProperty("Authorization", "Basic " +
Base64.getEncoder().encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(1000);
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
httpURLConnection.connect();
if (httpURLConnection.getResponseCode() == 200) {
tmpFile.withWriter StandardCharsets.UTF_8.name(), {
it.write(httpURLConnection.getInputStream().getText(StandardCharsets.UTF_8.name()))
}
break;
}
} catch (Exception e) {
logger.debug("failed to call cluster health", e)
lastException = e
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
// did not start, so wait a bit before trying again
Thread.sleep(500L);
}
if (tmpFile.exists() == false && lastException != null) {
logger.error("final attempt of calling cluster health failed", lastException)
}
return tmpFile.exists()
}
Closure waitWithAuth = { NodeInfo node, AntBuilder ant -> Closure waitWithAuth = { NodeInfo node, AntBuilder ant ->
File tmpFile = new File(node.cwd, 'wait.success') File tmpFile = new File(node.cwd, 'wait.success')
// wait up to twenty seconds // wait up to twenty seconds
final long stopTime = System.currentTimeMillis() + 20000L; final long stopTime = System.currentTimeMillis() + 20000L;
Exception lastException = null; Exception lastException = null;
while (System.currentTimeMillis() < stopTime) { while (System.currentTimeMillis() < stopTime) {
lastException = null; lastException = null;
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned // we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
@ -114,7 +28,7 @@ Closure waitWithAuth = { NodeInfo node, AntBuilder ant ->
try { try {
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=${node.config.numNodes}&wait_for_status=yellow").openConnection(); httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=${node.config.numNodes}&wait_for_status=yellow").openConnection();
httpURLConnection.setRequestProperty("Authorization", "Basic " + httpURLConnection.setRequestProperty("Authorization", "Basic " +
Base64.getEncoder().encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8))); Base64.getEncoder().encodeToString("elastic:changeme".getBytes(StandardCharsets.UTF_8)));
httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(1000); httpURLConnection.setConnectTimeout(1000);
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes! httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
@ -202,7 +116,7 @@ subprojects {
numBwcNodes = 2 numBwcNodes = 2
numNodes = 2 numNodes = 2
clusterName = 'full-cluster-restart' clusterName = 'full-cluster-restart'
waitCondition = changePasswordAndWaitWithAuth waitCondition = waitWithAuth
setting 'xpack.security.transport.ssl.enabled', 'true' setting 'xpack.security.transport.ssl.enabled', 'true'
setting 'xpack.ssl.keystore.path', 'testnode.jks' setting 'xpack.ssl.keystore.path', 'testnode.jks'
setting 'xpack.ssl.keystore.password', 'testnode' setting 'xpack.ssl.keystore.password', 'testnode'
@ -277,8 +191,7 @@ subprojects {
} }
} }
// NORELEASE : this test must be unmuted once https://github.com/elastic/dev/issues/741 is completed check.dependsOn(integTest)
// check.dependsOn(integTest)
dependencies { dependencies {
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime') testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime')

View File

@ -58,7 +58,7 @@ public class FullClusterRestartIT extends ESRestTestCase {
@Override @Override
protected Settings restClientSettings() { protected Settings restClientSettings() {
String token = "Basic " + Base64.getEncoder().encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8)); String token = "Basic " + Base64.getEncoder().encodeToString("elastic:changeme".getBytes(StandardCharsets.UTF_8));
return Settings.builder() return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token) .put(ThreadContext.PREFIX + ".Authorization", token)
// we increase the timeout here to 90 seconds to handle long waits for a green // we increase the timeout here to 90 seconds to handle long waits for a green

View File

@ -23,6 +23,7 @@ Closure waitWithAuth = { NodeInfo node, AntBuilder ant ->
Exception lastException = null; Exception lastException = null;
while (System.currentTimeMillis() < stopTime) { while (System.currentTimeMillis() < stopTime) {
lastException = null; lastException = null;
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned // we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
HttpURLConnection httpURLConnection = null; HttpURLConnection httpURLConnection = null;
@ -30,96 +31,7 @@ Closure waitWithAuth = { NodeInfo node, AntBuilder ant ->
// TODO this sucks having to hardcode number of nodes, but node.config.numNodes isn't necessarily accurate for rolling // TODO this sucks having to hardcode number of nodes, but node.config.numNodes isn't necessarily accurate for rolling
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=2&wait_for_status=yellow").openConnection(); httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=2&wait_for_status=yellow").openConnection();
httpURLConnection.setRequestProperty("Authorization", "Basic " + httpURLConnection.setRequestProperty("Authorization", "Basic " +
Base64.getEncoder().encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8))); Base64.getEncoder().encodeToString("elastic:changeme".getBytes(StandardCharsets.UTF_8)));
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(1000);
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
httpURLConnection.connect();
if (httpURLConnection.getResponseCode() == 200) {
tmpFile.withWriter StandardCharsets.UTF_8.name(), {
it.write(httpURLConnection.getInputStream().getText(StandardCharsets.UTF_8.name()))
}
break;
}
} catch (Exception e) {
logger.debug("failed to call cluster health", e)
lastException = e
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
// did not start, so wait a bit before trying again
Thread.sleep(500L);
}
if (tmpFile.exists() == false && lastException != null) {
logger.error("final attempt of calling cluster health failed", lastException)
}
return tmpFile.exists()
}
Closure changePasswordAndWaitWithAuth = { NodeInfo node, AntBuilder ant ->
File tmpFile = new File(node.cwd, 'wait.success')
String password
if (Version.fromString(node.nodeVersion).onOrAfter('6.0.0')) {
password = ""
} else {
password = "changeme"
}
for (int i = 0; i < 10; i++) {
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_xpack/security/user/elastic/_password")
.openConnection();
httpURLConnection.setRequestProperty("Authorization", "Basic " +
Base64.getEncoder().encodeToString("elastic:${password}".getBytes(StandardCharsets.UTF_8)));
httpURLConnection.setRequestMethod("PUT");
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
httpURLConnection.connect();
OutputStream out = httpURLConnection.getOutputStream();
out.write("{\"password\": \"x-pack-test-password\"}".getBytes(StandardCharsets.UTF_8));
out.close()
if (httpURLConnection.getResponseCode() == 200) {
break
}
} catch (Exception e) {
httpURLConnection.disconnect()
if (i == 9) {
logger.error("final attempt to set password", e)
} else {
logger.debug("failed to set elastic password", e)
}
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
// did not start, so wait a bit before trying again
Thread.sleep(500L);
}
// wait up to twenty seconds
final long stopTime = System.currentTimeMillis() + 20000L;
Exception lastException = null;
while (System.currentTimeMillis() < stopTime) {
lastException = null;
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
HttpURLConnection httpURLConnection = null;
try {
// TODO this sucks having to hardcode number of nodes, but node.config.numNodes isn't necessarily accurate for rolling
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=2&wait_for_status=yellow").openConnection();
httpURLConnection.setRequestProperty("Authorization", "Basic " +
Base64.getEncoder().encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(1000); httpURLConnection.setConnectTimeout(1000);
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes! httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
@ -207,7 +119,7 @@ subprojects {
numBwcNodes = 2 numBwcNodes = 2
numNodes = 2 numNodes = 2
clusterName = 'rolling-upgrade' clusterName = 'rolling-upgrade'
waitCondition = changePasswordAndWaitWithAuth waitCondition = waitWithAuth
setting 'xpack.security.transport.ssl.enabled', 'true' setting 'xpack.security.transport.ssl.enabled', 'true'
setting 'xpack.ssl.keystore.path', 'testnode.jks' setting 'xpack.ssl.keystore.path', 'testnode.jks'
setting 'xpack.ssl.keystore.password', 'testnode' setting 'xpack.ssl.keystore.password', 'testnode'
@ -316,8 +228,8 @@ subprojects {
dependsOn = ["v${wireCompatVersions[-1]}#bwcTest"] dependsOn = ["v${wireCompatVersions[-1]}#bwcTest"]
} }
} }
// NORELEASE : this test must be unmuted once https://github.com/elastic/dev/issues/741 is completed
// check.dependsOn(integTest) check.dependsOn(integTest)
dependencies { dependencies {
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime') testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime')

View File

@ -56,7 +56,7 @@ public class UpgradeClusterClientYamlTestSuiteIT extends SecurityClusterClientYa
@Override @Override
protected Settings restClientSettings() { protected Settings restClientSettings() {
String token = "Basic " + Base64.getEncoder().encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8)); String token = "Basic " + Base64.getEncoder().encodeToString(("elastic:changeme").getBytes(StandardCharsets.UTF_8));
return Settings.builder() return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token) .put(ThreadContext.PREFIX + ".Authorization", token)
// we increase the timeout here to 90 seconds to handle long waits for a green // we increase the timeout here to 90 seconds to handle long waits for a green

View File

@ -112,7 +112,7 @@ public class WatchBackwardsCompatibilityIT extends ESRestTestCase {
@Override @Override
protected Settings restClientSettings() { protected Settings restClientSettings() {
String token = "Basic " + Base64.getEncoder() String token = "Basic " + Base64.getEncoder()
.encodeToString("elastic:x-pack-test-password".getBytes(StandardCharsets.UTF_8)); .encodeToString(("elastic:changeme").getBytes(StandardCharsets.UTF_8));
return Settings.builder() return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token) .put(ThreadContext.PREFIX + ".Authorization", token)
.build(); .build();