Fix the building of the default URL for the setup password tool (elastic/x-pack-elasticsearch#2176)
This commit fixes the building of the default URL for the setup password tool so that a default elasticsearch.yml file will still result in a succesful run of the tool. relates elastic/x-pack-elasticsearch#2174 Original commit: elastic/x-pack-elasticsearch@2291b14875
This commit is contained in:
parent
2d9d4c41d8
commit
a7d6138f83
|
@ -10,6 +10,7 @@ import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
import org.elasticsearch.common.lease.Releasables;
|
import org.elasticsearch.common.lease.Releasables;
|
||||||
|
import org.elasticsearch.common.network.InetAddresses;
|
||||||
import org.elasticsearch.common.network.NetworkService;
|
import org.elasticsearch.common.network.NetworkService;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -26,14 +27,18 @@ import javax.net.ssl.HttpsURLConnection;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_PORT;
|
||||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_PUBLISH_HOST;
|
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_PUBLISH_HOST;
|
||||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_PUBLISH_PORT;
|
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_PUBLISH_PORT;
|
||||||
import static org.elasticsearch.xpack.security.Security.setting;
|
import static org.elasticsearch.xpack.security.Security.setting;
|
||||||
|
@ -103,12 +108,34 @@ public class CommandLineHttpClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultURL() {
|
String getDefaultURL() {
|
||||||
final String scheme = XPackSettings.HTTP_SSL_ENABLED.get(settings) ? "https" : "http";
|
final String scheme = XPackSettings.HTTP_SSL_ENABLED.get(settings) ? "https" : "http";
|
||||||
List<String> httpPublishHost = SETTING_HTTP_PUBLISH_HOST.get(settings);
|
List<String> httpPublishHost = SETTING_HTTP_PUBLISH_HOST.get(settings);
|
||||||
final String host =
|
if (httpPublishHost.isEmpty()) {
|
||||||
(httpPublishHost.isEmpty() ? NetworkService.GLOBAL_NETWORK_PUBLISHHOST_SETTING.get(settings) : httpPublishHost).get(0);
|
httpPublishHost = NetworkService.GLOBAL_NETWORK_PUBLISHHOST_SETTING.get(settings);
|
||||||
final int port = SETTING_HTTP_PUBLISH_PORT.get(settings);
|
}
|
||||||
return scheme + "://" + host + ":" + port;
|
|
||||||
|
// we cannot do custom name resolution here...
|
||||||
|
NetworkService networkService = new NetworkService(Collections.emptyList());
|
||||||
|
try {
|
||||||
|
InetAddress publishAddress = networkService.resolvePublishHostAddresses(httpPublishHost.toArray(Strings.EMPTY_ARRAY));
|
||||||
|
int port = SETTING_HTTP_PUBLISH_PORT.get(settings);
|
||||||
|
if (port <= 0) {
|
||||||
|
int[] ports = SETTING_HTTP_PORT.get(settings).ports();
|
||||||
|
if (ports.length > 0) {
|
||||||
|
port = ports[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// this sucks but a port can be specified with a value of 0, we'll never be able to connect to it so just default to
|
||||||
|
// what we know
|
||||||
|
if (port <= 0) {
|
||||||
|
throw new IllegalStateException("unable to determine http port from settings, please use the -u option to provide the" +
|
||||||
|
" url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scheme + "://" + InetAddresses.toUriString(publishAddress) + ":" + port;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("failed to resolve default URL", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.authc.esnative.tool;
|
package org.elasticsearch.xpack.security.authc.esnative.tool;
|
||||||
|
|
||||||
|
import joptsimple.OptionParser;
|
||||||
import joptsimple.OptionSet;
|
import joptsimple.OptionSet;
|
||||||
import joptsimple.OptionSpec;
|
import joptsimple.OptionSpec;
|
||||||
import org.elasticsearch.cli.EnvironmentAwareCommand;
|
import org.elasticsearch.cli.EnvironmentAwareCommand;
|
||||||
|
@ -77,6 +78,11 @@ public class SetupPasswordTool extends MultiCommand {
|
||||||
exit(new SetupPasswordTool().main(args, Terminal.DEFAULT));
|
exit(new SetupPasswordTool().main(args, Terminal.DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visible for testing
|
||||||
|
OptionParser getParser() {
|
||||||
|
return this.parser;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class sets the passwords using automatically generated random passwords. The passwords will be
|
* This class sets the passwords using automatically generated random passwords. The passwords will be
|
||||||
* printed to the console.
|
* printed to the console.
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
apply plugin: 'elasticsearch.standalone-rest-test'
|
||||||
|
apply plugin: 'elasticsearch.rest-test'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime')
|
||||||
|
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'testArtifacts')
|
||||||
|
}
|
||||||
|
|
||||||
|
integTestRunner {
|
||||||
|
systemProperty 'tests.security.manager', 'false'
|
||||||
|
}
|
||||||
|
|
||||||
|
integTestCluster {
|
||||||
|
plugin ':x-pack-elasticsearch:plugin'
|
||||||
|
keystoreSetting 'bootstrap.password', 'x-pack-test-password'
|
||||||
|
setupCommand 'setupTestAdmin',
|
||||||
|
'bin/x-pack/users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser"
|
||||||
|
waitCondition = { node, ant ->
|
||||||
|
File tmpFile = new File(node.cwd, 'wait.success')
|
||||||
|
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
|
||||||
|
dest: tmpFile.toString(),
|
||||||
|
username: 'test_admin',
|
||||||
|
password: 'x-pack-test-password',
|
||||||
|
ignoreerrors: true,
|
||||||
|
retries: 10)
|
||||||
|
return tmpFile.exists()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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.xpack.security.authc.esnative.tool;
|
||||||
|
|
||||||
|
import org.elasticsearch.cli.MockTerminal;
|
||||||
|
import org.elasticsearch.client.Response;
|
||||||
|
import org.elasticsearch.client.http.HttpHost;
|
||||||
|
import org.elasticsearch.client.http.message.BasicHeader;
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
|
import org.elasticsearch.common.network.InetAddresses;
|
||||||
|
import org.elasticsearch.common.network.NetworkService;
|
||||||
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||||
|
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
|
import org.elasticsearch.xpack.security.SecurityClusterClientYamlTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||||
|
|
||||||
|
public class SetupPasswordToolIT extends ESRestTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Settings restClientSettings() {
|
||||||
|
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
|
||||||
|
return Settings.builder()
|
||||||
|
.put(ThreadContext.PREFIX + ".Authorization", token)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testSetupPasswordToolAutoSetup() throws Exception {
|
||||||
|
SecurityClusterClientYamlTestCase.waitForSecurity();
|
||||||
|
|
||||||
|
final String testConfigDir = System.getProperty("tests.config.dir");
|
||||||
|
logger.info("--> CONF: {}", testConfigDir);
|
||||||
|
final Path configPath = PathUtils.get(testConfigDir);
|
||||||
|
setSystemPropsForTool(configPath);
|
||||||
|
|
||||||
|
Response nodesResponse = client().performRequest("GET", "/_nodes/http");
|
||||||
|
Map<String, Object> nodesMap = entityAsMap(nodesResponse);
|
||||||
|
|
||||||
|
Map<String,Object> nodes = (Map<String,Object>) nodesMap.get("nodes");
|
||||||
|
Map<String, Object> firstNode = (Map<String,Object>) nodes.entrySet().iterator().next().getValue();
|
||||||
|
Map<String, Object> firstNodeHttp = (Map<String,Object>) firstNode.get("http");
|
||||||
|
String nodePublishAddress = (String) firstNodeHttp.get("publish_address");
|
||||||
|
final int lastColonIndex = nodePublishAddress.lastIndexOf(':');
|
||||||
|
InetAddress actualPublishAddress = InetAddresses.forString(nodePublishAddress.substring(0, lastColonIndex));
|
||||||
|
InetAddress expectedPublishAddress = new NetworkService(Collections.emptyList()).resolvePublishHostAddresses(Strings.EMPTY_ARRAY);
|
||||||
|
final int port = Integer.valueOf(nodePublishAddress.substring(lastColonIndex + 1));
|
||||||
|
|
||||||
|
List<String> lines = Files.readAllLines(configPath.resolve("elasticsearch.yml"));
|
||||||
|
lines = lines.stream().filter(s -> s.startsWith("http.port") == false && s.startsWith("http.publish_port") == false)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
lines.add(randomFrom("http.port", "http.publish_port") + ": " + port);
|
||||||
|
if (expectedPublishAddress.equals(actualPublishAddress) == false) {
|
||||||
|
lines.add("http.publish_address: " + InetAddresses.toAddrString(actualPublishAddress));
|
||||||
|
}
|
||||||
|
Files.write(configPath.resolve("elasticsearch.yml"), lines, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
|
||||||
|
MockTerminal mockTerminal = new MockTerminal();
|
||||||
|
SetupPasswordTool tool = new SetupPasswordTool();
|
||||||
|
final int status;
|
||||||
|
if (randomBoolean()) {
|
||||||
|
mockTerminal.addTextInput("y"); // answer yes to continue prompt
|
||||||
|
status = tool.main(new String[] { "auto" }, mockTerminal);
|
||||||
|
} else {
|
||||||
|
status = tool.main(new String[] { "auto", "--batch" }, mockTerminal);
|
||||||
|
}
|
||||||
|
assertEquals(0, status);
|
||||||
|
String output = mockTerminal.getOutput();
|
||||||
|
logger.info("CLI TOOL OUTPUT:\n{}", output);
|
||||||
|
String[] outputLines = output.split("\\n");
|
||||||
|
Map<String, String> userPasswordMap = new HashMap<>();
|
||||||
|
Arrays.asList(outputLines).forEach(line -> {
|
||||||
|
if (line.startsWith("PASSWORD ")) {
|
||||||
|
String[] pieces = line.split(" ");
|
||||||
|
String user = pieces[1];
|
||||||
|
String password = pieces[pieces.length - 1];
|
||||||
|
logger.info("user [{}] password [{}]", user, password);
|
||||||
|
userPasswordMap.put(user, password);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(3, userPasswordMap.size());
|
||||||
|
userPasswordMap.entrySet().forEach(entry -> {
|
||||||
|
final String basicHeader = "Basic " +
|
||||||
|
Base64.getEncoder().encodeToString((entry.getKey() + ":" + entry.getValue()).getBytes(StandardCharsets.UTF_8));
|
||||||
|
try {
|
||||||
|
Response authenticateResponse = client().performRequest("GET", "/_xpack/security/_authenticate",
|
||||||
|
new BasicHeader("Authorization", basicHeader));
|
||||||
|
assertEquals(200, authenticateResponse.getStatusLine().getStatusCode());
|
||||||
|
Map<String, Object> userInfoMap = entityAsMap(authenticateResponse);
|
||||||
|
assertEquals(entry.getKey(), userInfoMap.get("username"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "need to set sys props for CLI tool")
|
||||||
|
private void setSystemPropsForTool(Path configPath) {
|
||||||
|
System.setProperty("es.path.conf", configPath.toString());
|
||||||
|
System.setProperty("es.path.home", configPath.getParent().toString());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue