Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.0] URL option for BaseRunAsSuperuserCommand (#81025) #81185

Merged
merged 1 commit into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions docs/reference/commands/create-enrollment-token.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The `elasticsearch-create-enrollment-token` command creates enrollment tokens fo
[source,shell]
----
bin/elasticsearch-create-enrollment-token
[-f, --force] [-h, --help] [-E <KeyValuePair>] [-s, --scope]
[-f, --force] [-h, --help] [-E <KeyValuePair>] [-s, --scope] [--url]
----

[discrete]
Expand All @@ -23,7 +23,7 @@ Use this command to create enrollment tokens, which you can use to enroll new
with an existing {es} cluster that has security features enabled.
The command generates (and subsequently removes) a temporary user in the
<<file-realm,file realm>> to run the request that creates enrollment tokens.
IMPORTANT: You cannot use this tool if the file realm is disabled in your
IMPORTANT: You cannot use this tool if the file realm is disabled in your
`elasticsearch.yml` file.

This command uses an HTTP connection to connect to the cluster and run the user
Expand All @@ -42,12 +42,17 @@ option. For more information about debugging connection failures, see

`-E <KeyValuePair>`:: Configures a standard {es} or {xpack} setting.

`-f, --force`:: Forces the command to run against an unhealthy cluster.
`-f, --force`:: Forces the command to run against an unhealthy cluster.

`-h, --help`:: Returns all of the command parameters.

`-s, --scope`:: Specifies the scope of the generated token. Supported values are `node` and `kibana`.

`--url`:: Specifies the base URL (hostname and port of the local node) that the tool uses to submit API
requests to {es}. The default value is determined from the settings in your
`elasticsearch.yml` file. If `xpack.security.http.ssl.enabled` is set to `true`,
you must specify an HTTPS URL.

[discrete]
=== Examples

Expand All @@ -57,3 +62,12 @@ The following command creates an enrollment token for enrolling an {es} node int
----
bin/elasticsearch-create-enrollment-token -s node
----

The following command creates an enrollment token for enrolling a {kib} instance into a cluster.
The specified URL indicates where the elasticsearch-create-enrollment-token tool attempts to reach the
local {es} node:

[source,shell]
----
bin/elasticsearch-create-enrollment-token -s kibana --url "https://172.0.0.3:9200"
----
17 changes: 15 additions & 2 deletions docs/reference/commands/reset-password.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ the native realm and built-in users.
bin/elasticsearch-reset-password
[-a, --auto] [-b, --batch] [-E <KeyValuePair]
[-f, --force] [-h, --help] [-i, --interactive]
[-s, --silent] [-u, --username] [-v, --verbose]
[-s, --silent] [-u, --username] [--url] [-v, --verbose]
----

[discrete]
Expand Down Expand Up @@ -59,12 +59,17 @@ option. For more information about debugging connection failures, see

`-u, --username`:: The username of the native realm user or built-in user.

`--url`:: Specifies the base URL (hostname and port of the local node) that the tool uses to submit API
requests to {es}. The default value is determined from the settings in your
`elasticsearch.yml` file. If `xpack.security.http.ssl.enabled` is set to `true`,
you must specify an HTTPS URL.

`-v --verbose`:: Shows verbose output in the console.
[discrete]
=== Examples

The following example resets the password of the `elastic` user to an auto-generated value and
prints the new password in the console.
prints the new password in the console:

[source,shell]
----
Expand All @@ -78,3 +83,11 @@ in the terminal for the desired password:
----
bin/elasticsearch-reset-password --username user1 -i
----

The following example resets the password of a native user with username `user2` to an auto-generated value
prints the new password in the console. The specified URL indicates where the elasticsearch-reset-password
tool attempts to reach the local {es} node:
[source,shell]
----
bin/elasticsearch-reset-password --url "https://172.0.0.3:9200" --username user2 -i
----
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,8 @@ protected void executeCommand(Terminal terminal, OptionSet options, Environment
}
try {
final CommandLineHttpClient client = clientFunction.apply(env);
final URL changePasswordUrl = createURL(
new URL(client.getDefaultURL()),
"_security/user/" + providedUsername + "/_password",
"?pretty"
);
final URL baseUrl = options.has(urlOption) ? new URL(options.valueOf(urlOption)) : new URL(client.getDefaultURL());
final URL changePasswordUrl = createURL(baseUrl, "_security/user/" + providedUsername + "/_password", "?pretty");
final HttpResponse httpResponse = client.execute(
"POST",
changePasswordUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,35 +45,33 @@ public class ExternalEnrollmentTokenGenerator extends BaseEnrollmentTokenGenerat
private final Environment environment;
private final SSLService sslService;
private final CommandLineHttpClient client;
private final URL defaultUrl;

public ExternalEnrollmentTokenGenerator(Environment environment) throws MalformedURLException {
this(environment, new CommandLineHttpClient(environment));
}

// protected for testing
protected ExternalEnrollmentTokenGenerator(Environment environment, CommandLineHttpClient client) throws MalformedURLException {
protected ExternalEnrollmentTokenGenerator(Environment environment, CommandLineHttpClient client) {
this.environment = environment;
this.sslService = new SSLService(environment);
this.client = client;
this.defaultUrl = new URL(client.getDefaultURL());
}

public EnrollmentToken createNodeEnrollmentToken(String user, SecureString password) throws Exception {
return this.create(user, password, NodeEnrollmentAction.NAME);
public EnrollmentToken createNodeEnrollmentToken(String user, SecureString password, URL baseUrl) throws Exception {
return this.create(user, password, NodeEnrollmentAction.NAME, baseUrl);
}

public EnrollmentToken createKibanaEnrollmentToken(String user, SecureString password) throws Exception {
return this.create(user, password, KibanaEnrollmentAction.NAME);
public EnrollmentToken createKibanaEnrollmentToken(String user, SecureString password, URL baseUrl) throws Exception {
return this.create(user, password, KibanaEnrollmentAction.NAME, baseUrl);
}

protected EnrollmentToken create(String user, SecureString password, String action) throws Exception {
protected EnrollmentToken create(String user, SecureString password, String action, URL baseUrl) throws Exception {
if (XPackSettings.ENROLLMENT_ENABLED.get(environment.settings()) != true) {
throw new IllegalStateException("[xpack.security.enrollment.enabled] must be set to `true` to create an enrollment token");
}
final String fingerprint = getHttpsCaFingerprint(sslService);
final String apiKey = getApiKeyCredentials(user, password, action);
final Tuple<List<String>, String> httpInfo = getNodeInfo(user, password);
final String apiKey = getApiKeyCredentials(user, password, action, baseUrl);
final Tuple<List<String>, String> httpInfo = getNodeInfo(user, password, baseUrl);
return new EnrollmentToken(apiKey, fingerprint, httpInfo.v2(), httpInfo.v1());
}

Expand All @@ -89,12 +87,12 @@ private HttpResponse.HttpResponseBuilder responseBuilder(InputStream is) throws
return httpResponseBuilder;
}

protected URL createAPIKeyUrl() throws MalformedURLException, URISyntaxException {
return new URL(defaultUrl, (defaultUrl.toURI().getPath() + "/_security/api_key").replaceAll("/+", "/"));
protected URL createAPIKeyUrl(URL baseUrl) throws MalformedURLException, URISyntaxException {
return new URL(baseUrl, (baseUrl.toURI().getPath() + "/_security/api_key").replaceAll("/+", "/"));
}

protected URL getHttpInfoUrl() throws MalformedURLException, URISyntaxException {
return new URL(defaultUrl, (defaultUrl.toURI().getPath() + "/_nodes/_local/http").replaceAll("/+", "/"));
protected URL getHttpInfoUrl(URL baseUrl) throws MalformedURLException, URISyntaxException {
return new URL(baseUrl, (baseUrl.toURI().getPath() + "/_nodes/_local/http").replaceAll("/+", "/"));
}

@SuppressWarnings("unchecked")
Expand All @@ -114,7 +112,7 @@ static String getVersion(Map<?, ?> nodesInfo) {
return nodeInfo.get("version").toString();
}

protected String getApiKeyCredentials(String user, SecureString password, String action) throws Exception {
protected String getApiKeyCredentials(String user, SecureString password, String action, URL baseUrl) throws Exception {
final CheckedSupplier<String, Exception> createApiKeyRequestBodySupplier = () -> {
XContentBuilder xContentBuilder = JsonXContent.contentBuilder();
xContentBuilder.startObject()
Expand All @@ -129,7 +127,7 @@ protected String getApiKeyCredentials(String user, SecureString password, String
return Strings.toString(xContentBuilder);
};

final URL createApiKeyUrl = createAPIKeyUrl();
final URL createApiKeyUrl = createAPIKeyUrl(baseUrl);
final HttpResponse httpResponseApiKey = client.execute(
"POST",
createApiKeyUrl,
Expand All @@ -155,8 +153,8 @@ protected String getApiKeyCredentials(String user, SecureString password, String
return apiId + ":" + apiKey;
}

protected Tuple<List<String>, String> getNodeInfo(String user, SecureString password) throws Exception {
final URL httpInfoUrl = getHttpInfoUrl();
protected Tuple<List<String>, String> getNodeInfo(String user, SecureString password, URL baseUrl) throws Exception {
final URL httpInfoUrl = getHttpInfoUrl(baseUrl);
final HttpResponse httpResponseHttp = client.execute("GET", httpInfoUrl, user, password, () -> null, is -> responseBuilder(is));
final int httpCode = httpResponseHttp.getHttpStatus();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@
import org.elasticsearch.xpack.security.enrollment.ExternalEnrollmentTokenGenerator;
import org.elasticsearch.xpack.security.tool.BaseRunAsSuperuserCommand;

import java.net.URL;
import java.util.List;
import java.util.function.Function;

public class CreateEnrollmentTokenTool extends BaseRunAsSuperuserCommand {

private final OptionSpec<String> scope;
private final Function<Environment, CommandLineHttpClient> clientFunction;
private final CheckedFunction<Environment, ExternalEnrollmentTokenGenerator, Exception> createEnrollmentTokenFunction;
static final List<String> ALLOWED_SCOPES = List.of("node", "kibana");

CreateEnrollmentTokenTool() {

this(
environment -> new CommandLineHttpClient(environment),
environment -> KeyStoreWrapper.load(environment.configFile()),
Expand All @@ -46,6 +49,7 @@ public class CreateEnrollmentTokenTool extends BaseRunAsSuperuserCommand {
) {
super(clientFunction, keyStoreFunction, "Creates enrollment tokens for elasticsearch nodes and kibana instances");
this.createEnrollmentTokenFunction = createEnrollmentTokenFunction;
this.clientFunction = clientFunction;
scope = parser.acceptsAll(List.of("scope", "s"), "The scope of this enrollment token, can be either \"node\" or \"kibana\"")
.withRequiredArg()
.required();
Expand Down Expand Up @@ -74,12 +78,15 @@ protected void validate(Terminal terminal, OptionSet options, Environment env) t
protected void executeCommand(Terminal terminal, OptionSet options, Environment env, String username, SecureString password)
throws Exception {
final String tokenScope = scope.value(options);
final URL baseUrl = options.has(urlOption)
? new URL(options.valueOf(urlOption))
: new URL(clientFunction.apply(env).getDefaultURL());
try {
ExternalEnrollmentTokenGenerator externalEnrollmentTokenGenerator = createEnrollmentTokenFunction.apply(env);
if (tokenScope.equals("node")) {
terminal.println(externalEnrollmentTokenGenerator.createNodeEnrollmentToken(username, password).getEncoded());
terminal.println(externalEnrollmentTokenGenerator.createNodeEnrollmentToken(username, password, baseUrl).getEncoded());
} else {
terminal.println(externalEnrollmentTokenGenerator.createKibanaEnrollmentToken(username, password).getEncoded());
terminal.println(externalEnrollmentTokenGenerator.createKibanaEnrollmentToken(username, password, baseUrl).getEncoded());
}
} catch (Exception e) {
terminal.errorPrintln("Unable to create enrollment token for scope [" + tokenScope + "]");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package org.elasticsearch.xpack.security.tool;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import joptsimple.OptionSpecBuilder;

import org.elasticsearch.Version;
Expand Down Expand Up @@ -57,6 +58,7 @@ public abstract class BaseRunAsSuperuserCommand extends KeyStoreAwareCommand {
private static final int PASSWORD_LENGTH = 14;

private final OptionSpecBuilder force;
protected final OptionSpec<String> urlOption;
private final Function<Environment, CommandLineHttpClient> clientFunction;
private final CheckedFunction<Environment, KeyStoreWrapper, Exception> keyStoreFunction;

Expand All @@ -72,6 +74,7 @@ public BaseRunAsSuperuserCommand(
List.of("f", "force"),
"Use this option to force execution of the command against a cluster that is currently unhealthy."
);
urlOption = parser.accepts("url", "the URL where the elasticsearch node listens for connections.").withRequiredArg();
}

@Override
Expand Down Expand Up @@ -120,7 +123,7 @@ protected final void execute(Terminal terminal, OptionSet options, Environment e

attributesChecker.check(terminal);
final boolean forceExecution = options.has(force);
checkClusterHealthWithRetries(newEnv, terminal, username, password, 5, forceExecution);
checkClusterHealthWithRetries(newEnv, options, terminal, username, password, 5, forceExecution);
executeCommand(terminal, options, newEnv, username, password);
} catch (Exception e) {
int exitCode;
Expand Down Expand Up @@ -195,14 +198,16 @@ private void ensureFileRealmEnabled(Settings settings) throws Exception {
*/
private void checkClusterHealthWithRetries(
Environment env,
OptionSet options,
Terminal terminal,
String username,
SecureString password,
int retries,
boolean force
) throws Exception {
CommandLineHttpClient client = clientFunction.apply(env);
final URL clusterHealthUrl = CommandLineHttpClient.createURL(new URL(client.getDefaultURL()), "_cluster/health", "?pretty");
final URL baseUrl = options.has(urlOption) ? new URL(options.valueOf(urlOption)) : new URL(client.getDefaultURL());
final URL clusterHealthUrl = CommandLineHttpClient.createURL(baseUrl, "_cluster/health", "?pretty");
final HttpResponse response;
try {
response = client.execute("GET", clusterHealthUrl, username, password, () -> null, CommandLineHttpClient::responseBuilder);
Expand All @@ -225,7 +230,7 @@ private void checkClusterHealthWithRetries(
);
Thread.sleep(1000);
retries -= 1;
checkClusterHealthWithRetries(env, terminal, username, password, retries, force);
checkClusterHealthWithRetries(env, options, terminal, username, password, retries, force);
} else {
throw new UserException(
ExitCodes.DATA_ERROR,
Expand Down
Loading