|
@@ -33,6 +33,8 @@ import org.elasticsearch.common.settings.KeyStoreWrapper;
|
|
|
import org.elasticsearch.common.settings.SecureString;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
import org.elasticsearch.core.CheckedConsumer;
|
|
|
+import org.elasticsearch.core.Nullable;
|
|
|
+import org.elasticsearch.core.PathUtils;
|
|
|
import org.elasticsearch.core.SuppressForbidden;
|
|
|
import org.elasticsearch.discovery.DiscoveryModule;
|
|
|
import org.elasticsearch.discovery.SettingsBasedSeedHostsProvider;
|
|
@@ -59,10 +61,12 @@ import java.nio.file.LinkOption;
|
|
|
import java.nio.file.Path;
|
|
|
import java.nio.file.StandardCopyOption;
|
|
|
import java.nio.file.StandardOpenOption;
|
|
|
+import java.nio.file.attribute.GroupPrincipal;
|
|
|
import java.nio.file.attribute.PosixFileAttributeView;
|
|
|
import java.nio.file.attribute.PosixFilePermission;
|
|
|
import java.nio.file.attribute.PosixFilePermissions;
|
|
|
import java.nio.file.attribute.UserPrincipal;
|
|
|
+import java.nio.file.attribute.UserPrincipalLookupService;
|
|
|
import java.security.KeyPair;
|
|
|
import java.security.KeyStore;
|
|
|
import java.security.PrivateKey;
|
|
@@ -110,6 +114,7 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
private static final String TRANSPORT_AUTOGENERATED_KEYSTORE_NAME = "transport";
|
|
|
private static final String TRANSPORT_KEY_KEYSTORE_ENTRY = "transport";
|
|
|
private static final String TRANSPORT_CA_CERT_KEYSTORE_ENTRY = "transport_ca";
|
|
|
+ private static final String ELASTICSEARCH_GROUP_OWNER = "elasticsearch";
|
|
|
private static final int TRANSPORT_CERTIFICATE_DAYS = 99 * 365;
|
|
|
private static final int TRANSPORT_CA_CERTIFICATE_DAYS = 99 * 365;
|
|
|
private static final int TRANSPORT_KEY_SIZE = 4096;
|
|
@@ -152,8 +157,9 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
@Override
|
|
|
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
|
|
|
final boolean inEnrollmentMode = options.has(enrollmentTokenParam);
|
|
|
+ final boolean inReconfigureMode = options.has(reconfigure);
|
|
|
|
|
|
- // skipping security auto configuration because node considered as restarting.
|
|
|
+ // skipping security auto-configuration because node considered as restarting.
|
|
|
for (Path dataPath : env.dataFiles()) {
|
|
|
if (Files.isDirectory(dataPath) && false == isDirEmpty(dataPath)) {
|
|
|
final String msg = "Skipping security auto configuration because it appears that the node is not starting up for the "
|
|
@@ -197,11 +203,11 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
notifyOfFailure(inEnrollmentMode, terminal, Terminal.Verbosity.NORMAL, ExitCodes.NOOP, msg);
|
|
|
}
|
|
|
|
|
|
- if (options.has(reconfigure)) {
|
|
|
+ if (inReconfigureMode) {
|
|
|
if (false == inEnrollmentMode) {
|
|
|
throw new UserException(ExitCodes.USAGE, "enrollment-token is a mandatory parameter when reconfiguring the node");
|
|
|
}
|
|
|
- env = possibleReconfigureNode(env, terminal);
|
|
|
+ env = possiblyReconfigureNode(env, terminal);
|
|
|
}
|
|
|
|
|
|
// only perform auto-configuration if the existing configuration is not conflicting (eg Security already enabled)
|
|
@@ -458,13 +464,21 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
|
|
|
// the HTTP CA PEM file is provided "just in case". The node doesn't use it, but clients (configured manually, outside of the
|
|
|
// enrollment process) might indeed need it, and it is currently impossible to retrieve it
|
|
|
- fullyWriteFile(tempGeneratedTlsCertsDir, HTTP_AUTOGENERATED_CA_NAME + ".crt", false, stream -> {
|
|
|
- try (
|
|
|
- JcaPEMWriter pemWriter = new JcaPEMWriter(new BufferedWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8)))
|
|
|
- ) {
|
|
|
- pemWriter.writeObject(httpCaCert);
|
|
|
+ fullyWriteFile(
|
|
|
+ tempGeneratedTlsCertsDir,
|
|
|
+ HTTP_AUTOGENERATED_CA_NAME + ".crt",
|
|
|
+ false,
|
|
|
+ inReconfigureMode ? ELASTICSEARCH_GROUP_OWNER : null,
|
|
|
+ stream -> {
|
|
|
+ try (
|
|
|
+ JcaPEMWriter pemWriter = new JcaPEMWriter(
|
|
|
+ new BufferedWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8))
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ pemWriter.writeObject(httpCaCert);
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
+ );
|
|
|
} catch (Throwable t) {
|
|
|
try {
|
|
|
deleteDirectory(tempGeneratedTlsCertsDir);
|
|
@@ -528,6 +542,7 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
tempGeneratedTlsCertsDir,
|
|
|
TRANSPORT_AUTOGENERATED_KEYSTORE_NAME + ".p12",
|
|
|
false,
|
|
|
+ inReconfigureMode ? ELASTICSEARCH_GROUP_OWNER : null,
|
|
|
stream -> transportKeystore.store(stream, transportKeystorePassword.getChars())
|
|
|
);
|
|
|
nodeKeystore.setString("xpack.security.transport.ssl.keystore.secure_password", transportKeystorePassword.getChars());
|
|
@@ -555,6 +570,7 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
tempGeneratedTlsCertsDir,
|
|
|
HTTP_AUTOGENERATED_KEYSTORE_NAME + ".p12",
|
|
|
false,
|
|
|
+ inReconfigureMode ? ELASTICSEARCH_GROUP_OWNER : null,
|
|
|
stream -> httpKeystore.store(stream, httpKeystorePassword.getChars())
|
|
|
);
|
|
|
nodeKeystore.setString("xpack.security.http.ssl.keystore.secure_password", httpKeystorePassword.getChars());
|
|
@@ -838,7 +854,7 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private Environment possibleReconfigureNode(Environment env, Terminal terminal) throws UserException {
|
|
|
+ private Environment possiblyReconfigureNode(Environment env, Terminal terminal) throws UserException {
|
|
|
// We remove the existing auto-configuration stanza from elasticsearch.yml, the elastisearch.keystore and
|
|
|
// the directory with the auto-configured TLS key material, and then proceed as if elasticsearch is started
|
|
|
// with --enrolment-token token, in the first place.
|
|
@@ -1044,8 +1060,13 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
|| ClusterBootstrapService.INITIAL_MASTER_NODES_SETTING.get(settings).equals(List.of(NODE_NAME_SETTING.get(settings)));
|
|
|
}
|
|
|
|
|
|
- private static void fullyWriteFile(Path basePath, String fileName, boolean replace, CheckedConsumer<OutputStream, Exception> writer)
|
|
|
- throws Exception {
|
|
|
+ private static void fullyWriteFile(
|
|
|
+ Path basePath,
|
|
|
+ String fileName,
|
|
|
+ boolean replace,
|
|
|
+ @Nullable String groupOwner,
|
|
|
+ CheckedConsumer<OutputStream, Exception> writer
|
|
|
+ ) throws Exception {
|
|
|
Path filePath = basePath.resolve(fileName);
|
|
|
if (false == replace && Files.exists(filePath)) {
|
|
|
throw new UserException(
|
|
@@ -1071,17 +1092,28 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
|
|
PosixFileAttributeView view = Files.getFileAttributeView(tmpPath, PosixFileAttributeView.class);
|
|
|
if (view != null) {
|
|
|
view.setPermissions(permission);
|
|
|
+ if (null != groupOwner) {
|
|
|
+ UserPrincipalLookupService lookupService = PathUtils.getDefaultFileSystem().getUserPrincipalLookupService();
|
|
|
+ GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName(groupOwner);
|
|
|
+ view.setGroup(groupPrincipal);
|
|
|
+ }
|
|
|
}
|
|
|
if (replace) {
|
|
|
Files.move(tmpPath, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
|
|
} else {
|
|
|
Files.move(tmpPath, filePath, StandardCopyOption.ATOMIC_MOVE);
|
|
|
}
|
|
|
+
|
|
|
} finally {
|
|
|
Files.deleteIfExists(tmpPath);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static void fullyWriteFile(Path basePath, String fileName, boolean replace, CheckedConsumer<OutputStream, Exception> writer)
|
|
|
+ throws Exception {
|
|
|
+ fullyWriteFile(basePath, fileName, replace, null, writer);
|
|
|
+ }
|
|
|
+
|
|
|
private static boolean isDirEmpty(Path path) throws IOException {
|
|
|
// Files.list MUST always be used in a try-with-resource construct in order to release the dir file handler
|
|
|
try (Stream<Path> dirContentsStream = Files.list(path)) {
|