Skip to content

Commit

Permalink
Add a tool for creating enrollment tokens (elastic#74890)
Browse files Browse the repository at this point in the history
This change introduces a CLI tool that can be used to create
enrollment tokens. It doesn't require credentials, but simply
write access to the local filesystem of a node. It uses an
auto-generated user in the file-realm with superuser role.

For this purpose, this change also introduces a base class for a
CLI tool that can be used by any CLI tool needs to perform actions
against an ES node as a superuser without requiring credentials
from the user. It is worth noting that this doesn't change our
existing thread model, because already an actor with write access
to the fs of an ES node, can become superuser (again, by
adding a superuser to the file realm, albeit manually).

Co-authored-by: Adam Locke <[email protected]>
  • Loading branch information
2 people authored and ywangd committed Jul 30, 2021
1 parent e8007e5 commit 07676e0
Show file tree
Hide file tree
Showing 10 changed files with 993 additions and 8 deletions.
59 changes: 59 additions & 0 deletions docs/reference/commands/create-enrollment-token.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[roles="xpack"]
[[create-enrollment-token]]

== elasticsearch-create-enrollment-token

The `elasticsearch-create-enrollment-token` command creates enrollment tokens for
{es} nodes and {kib} instances.

[discrete]
=== Synopsis

[source,shell]
----
bin/elasticsearch-create-enrollment-token
[-f, --force] [-h, --help] [-E <KeyValuePair>] [-s, --scope]
----

[discrete]
=== Description

Use this command to create enrollment tokens, which you can use to enroll new
{es} nodes to an existing cluster or configure {kib} instances to communicate
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
`elasticsearch.yml` file.

This command uses an HTTP connection to connect to the cluster and run the user
management requests. The command automatically attempts to establish the connection
over HTTPS by using the `xpack.security.http.ssl` settings in
the `elasticsearch.yml` file. If you do not use the default configuration directory,
ensure that the `ES_PATH_CONF` environment variable returns the
correct path before you run the `elasticsearch-create-enrollment-token` command. You can
override settings in your `elasticsearch.yml` file by using the `-E` command
option. For more information about debugging connection failures, see
<<trb-security-setup>>.

[discrete]
[[create-enrollment-token-parameters]]
=== Parameters

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

`-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`.

[discrete]
=== Examples

The following command creates an enrollment token for enrolling an {es} node into a cluster:

[source,shell]
----
bin/elasticsearch-create-enrollment-token -s node
----
2 changes: 2 additions & 0 deletions docs/reference/commands/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ tasks from the command line:

* <<certgen>>
* <<certutil>>
* <<create-enrollment-token>>
* <<elasticsearch-croneval>>
* <<elasticsearch-keystore>>
* <<node-tool>>
Expand All @@ -22,6 +23,7 @@ tasks from the command line:

include::certgen.asciidoc[]
include::certutil.asciidoc[]
include::create-enrollment-token.asciidoc[]
include::croneval.asciidoc[]
include::keystore.asciidoc[]
include::node-tool.asciidoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License
# 2.0; you may not use this file except in compliance with the Elastic License
# 2.0.

ES_MAIN_CLASS=org.elasticsearch.xpack.security.enrollment.tool.CreateEnrollmentTokenTool \
ES_ADDITIONAL_SOURCES="x-pack-env;x-pack-security-env" \
"`dirname "$0"`"/elasticsearch-cli \
"$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@echo off

rem Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
rem or more contributor license agreements. Licensed under the Elastic License
rem 2.0; you may not use this file except in compliance with the Elastic License
rem 2.0.

setlocal enabledelayedexpansion
setlocal enableextensions

set ES_MAIN_CLASS=org.elasticsearch.xpack.security.enrollment.tool.CreateEnrollmentTokenTool
set ES_ADDITIONAL_SOURCES=x-pack-env;x-pack-security-env
set ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/security-cli
call "%~dp0elasticsearch-cli.bat" ^
%%* ^
|| goto exit

endlocal
endlocal
:exit
exit /b %ERRORLEVEL%
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.security.enrollment.tool;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;

import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.security.enrollment.CreateEnrollmentToken;
import org.elasticsearch.xpack.security.tool.BaseRunAsSuperuserCommand;
import org.elasticsearch.xpack.security.tool.CommandLineHttpClient;

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

public class CreateEnrollmentTokenTool extends BaseRunAsSuperuserCommand {

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

CreateEnrollmentTokenTool() {
this(
environment -> new CommandLineHttpClient(environment),
environment -> KeyStoreWrapper.load(environment.configFile()),
environment -> new CreateEnrollmentToken(environment)
);
}

CreateEnrollmentTokenTool(
Function<Environment, CommandLineHttpClient> clientFunction,
CheckedFunction<Environment, KeyStoreWrapper, Exception> keyStoreFunction,
CheckedFunction<Environment, CreateEnrollmentToken, Exception> createEnrollmentTokenFunction
) {
super(clientFunction, keyStoreFunction, "Creates enrollment tokens for elasticsearch nodes and kibana instances");
this.createEnrollmentTokenFunction = createEnrollmentTokenFunction;
scope = parser.acceptsAll(List.of("scope", "s"), "The scope of this enrollment token, can be either \"node\" or \"kibana\"")
.withRequiredArg()
.required();
}

public static void main(String[] args) throws Exception {
exit(new CreateEnrollmentTokenTool().main(args, Terminal.DEFAULT));
}

@Override
protected void validate(Terminal terminal, OptionSet options, Environment env) throws Exception {
if (XPackSettings.ENROLLMENT_ENABLED.get(env.settings()) == false) {
throw new UserException(
ExitCodes.CONFIG,
"[xpack.security.enrollment.enabled] must be set to `true` to create an enrollment token"
);
}
final String tokenScope = scope.value(options);
if (ALLOWED_SCOPES.contains(tokenScope) == false) {
terminal.errorPrintln("The scope of this enrollment token, can only be one of " + ALLOWED_SCOPES);
throw new UserException(ExitCodes.USAGE, "Invalid scope");
}
}

@Override
protected void executeCommand(Terminal terminal, OptionSet options, Environment env, String username, SecureString password)
throws Exception {
final String tokenScope = scope.value(options);
try {
CreateEnrollmentToken createEnrollmentTokenService = createEnrollmentTokenFunction.apply(env);
if (tokenScope.equals("node")) {
terminal.println(createEnrollmentTokenService.createNodeEnrollmentToken(username, password));
} else {
terminal.println(createEnrollmentTokenService.createKibanaEnrollmentToken(username, password));
}
} catch (Exception e) {
terminal.errorPrintln("Unable to create enrollment token for scope [" + tokenScope + "]");
throw new UserException(ExitCodes.CANT_CREATE, e.getMessage(), e.getCause());
}
}
}
Loading

0 comments on commit 07676e0

Please sign in to comment.