ソースを参照

Correct file ownership on node reconfiguration (#82789)

When running elasticsearch-reconfigure-node to allow a node that
was installed via a package(RPM/DEB) to enroll to an existing
secured cluster, we should ensure that the file ownership is
proper so that elasticsearch can actually read the files when it
starts after reconfiguration.
This commits sets the group owner of the keystore files to
`elasticsearch` which is the group that we create during
installation.
Ioannis Kakavas 3 年 前
コミット
814f7f9f52

+ 15 - 0
libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemUtilsTests.java

@@ -23,6 +23,7 @@ import java.security.interfaces.ECPrivateKey;
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.ECParameterSpec;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.instanceOf;
@@ -247,6 +248,20 @@ public class PemUtilsTests extends ESTestCase {
         assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString()));
     }
 
+    public void testParsePKCS8PemString() throws Exception {
+        Key key = getKeyFromKeystore("EC");
+        assertThat(key, notNullValue());
+        assertThat(key, instanceOf(PrivateKey.class));
+        final Path path = getDataPath("/certs/pem-utils/ec_key_pkcs8_plain.pem");
+        final String transportKeyPemString = Files.readAllLines(path)
+            .stream()
+            .filter(l -> l.contains("-----") == false)
+            .collect(Collectors.joining());
+        final PrivateKey privateKey = PemUtils.parsePKCS8PemString(transportKeyPemString);
+        assertThat(privateKey, notNullValue());
+        assertThat(privateKey, equalTo(key));
+    }
+
     private Key getKeyFromKeystore(String algo) throws Exception {
         Path keystorePath = getDataPath("/certs/pem-utils/testnode.jks");
         try (InputStream in = Files.newInputStream(keystorePath)) {

+ 85 - 0
qa/os/src/test/java/org/elasticsearch/packaging/test/PackagesSecurityAutoConfigurationTests.java

@@ -8,25 +8,47 @@
 
 package org.elasticsearch.packaging.test;
 
+import org.elasticsearch.Version;
 import org.elasticsearch.cli.ExitCodes;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.ssl.PemKeyConfig;
+import org.elasticsearch.packaging.util.FileMatcher;
 import org.elasticsearch.packaging.util.Installation;
 import org.elasticsearch.packaging.util.Packages;
 import org.elasticsearch.packaging.util.Shell;
+import org.elasticsearch.test.http.MockResponse;
+import org.elasticsearch.test.http.MockWebServer;
+import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.xpack.core.security.EnrollmentToken;
+import org.hamcrest.CoreMatchers;
 import org.junit.BeforeClass;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
+import java.security.SecureRandom;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
 
 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static org.elasticsearch.packaging.util.FileMatcher.Fileness.Directory;
+import static org.elasticsearch.packaging.util.FileMatcher.Fileness.File;
+import static org.elasticsearch.packaging.util.FileMatcher.p660;
+import static org.elasticsearch.packaging.util.FileMatcher.p750;
 import static org.elasticsearch.packaging.util.FileUtils.append;
 import static org.elasticsearch.packaging.util.Packages.assertInstalled;
 import static org.elasticsearch.packaging.util.Packages.assertRemoved;
 import static org.elasticsearch.packaging.util.Packages.installPackage;
 import static org.elasticsearch.packaging.util.Packages.verifyPackageInstallation;
+import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
@@ -235,6 +257,69 @@ public class PackagesSecurityAutoConfigurationTests extends PackagingTestCase {
         assertThat(newConfigurationLines, hasItem("node.name: testnodename"));
     }
 
+    public void test73ReconfigureCreatesFilesWithCorrectPermissions() throws Exception {
+        cleanup();
+        assertRemoved(distribution());
+        installation = installPackage(sh, distribution(), successfulAutoConfiguration());
+        assertInstalled(distribution());
+        verifyPackageInstallation(installation, distribution(), sh);
+        verifySecurityAutoConfigured(installation);
+        assertNotNull(installation.getElasticPassword());
+        final PemKeyConfig keyConfig = new PemKeyConfig(
+            Paths.get(getClass().getResource("http.crt").toURI()).toAbsolutePath().normalize().toString(),
+            Paths.get(getClass().getResource("http.key").toURI()).toAbsolutePath().normalize().toString(),
+            new char[0],
+            Paths.get(getClass().getResource("http.crt").toURI()).getParent().toAbsolutePath().normalize()
+        );
+        final SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(new KeyManager[] { keyConfig.createKeyManager() }, new TrustManager[] {}, new SecureRandom());
+        // We can't run multiple nodes as package installations. We mock an initial node that would respond to the enroll node API
+        try (MockWebServer mockNode = new MockWebServer(sslContext, false)) {
+            mockNode.start();
+            final String httpCaCertPemString = Files.readAllLines(
+                Paths.get(getClass().getResource("http_ca.crt").toURI()).toAbsolutePath().normalize()
+            ).stream().filter(l -> l.contains("-----") == false).collect(Collectors.joining());
+            final String httpCaKeyPemString = Files.readAllLines(
+                Paths.get(getClass().getResource("http_ca.key").toURI()).toAbsolutePath().normalize()
+            ).stream().filter(l -> l.contains("-----") == false).collect(Collectors.joining());
+            final String transportCaCertPemString = Files.readAllLines(
+                Paths.get(getClass().getResource("transport_ca.crt").toURI()).toAbsolutePath().normalize()
+            ).stream().filter(l -> l.contains("-----") == false).collect(Collectors.joining());
+            final String transportKeyPemString = Files.readAllLines(
+                Paths.get(getClass().getResource("transport.key").toURI()).toAbsolutePath().normalize()
+            ).stream().filter(l -> l.contains("-----") == false).collect(Collectors.joining());
+            final String transportCertPemString = Files.readAllLines(
+                Paths.get(getClass().getResource("transport.crt").toURI()).toAbsolutePath().normalize()
+            ).stream().filter(l -> l.contains("-----") == false).collect(Collectors.joining());
+            final XContentBuilder responseBuilder = jsonBuilder().startObject()
+                .field("http_ca_key", httpCaKeyPemString)
+                .field("http_ca_cert", httpCaCertPemString)
+                .field("transport_ca_cert", transportCaCertPemString)
+                .field("transport_key", transportKeyPemString)
+                .field("transport_cert", transportCertPemString)
+                .array("nodes_addresses", "192.168.1.23:9300") // won't be used, can be anything
+                .endObject();
+            mockNode.enqueue(new MockResponse().setResponseCode(200).setBody(Strings.toString(responseBuilder)));
+            final EnrollmentToken enrollmentToken = new EnrollmentToken(
+                "some-api-key",
+                "b0150fd8a29f9012207912de9a01aa1d1f0dd696c847d3a9353881f9045bf442", // fingerprint of http_ca.crt
+                Version.CURRENT.toString(),
+                List.of(mockNode.getHostName() + ":" + mockNode.getPort())
+            );
+            Shell.Result result = installation.executables().nodeReconfigureTool.run(
+                "-v --enrollment-token " + enrollmentToken.getEncoded(),
+                "y",
+                true
+            );
+            assertThat(result.exitCode(), CoreMatchers.equalTo(0));
+            assertThat(installation.config("certs"), FileMatcher.file(Directory, "root", "elasticsearch", p750));
+            Stream.of("http.p12", "http_ca.crt", "transport.p12")
+                .forEach(
+                    file -> assertThat(installation.config("certs").resolve(file), FileMatcher.file(File, "root", "elasticsearch", p660))
+                );
+        }
+    }
+
     private Predicate<String> successfulAutoConfiguration() {
         Predicate<String> p1 = output -> output.contains("Authentication and authorization are enabled.");
         Predicate<String> p2 = output -> output.contains("TLS for the transport and HTTP layers is enabled and configured.");

+ 40 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/http.crt

@@ -0,0 +1,40 @@
+-----BEGIN CERTIFICATE-----
+MIIDPzCCAiegAwIBAgIVAPIVDR5rVSUV+dljxvdiQHFVkwipMA0GCSqGSIb3DQEB
+CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
+ZXJhdGVkIENBMB4XDTIyMDExOTE3NDQyMloXDTI1MDExODE3NDQyMlowEzERMA8G
+A1UEAxMIaW5zdGFuY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCe
+79lY0Q4PQm24IBR6OAtjLSdFLFtwC8/i1Z5ujgPkkNW5QrlveGRU52V5Vp9CC8lc
+ngiKwRcW5vmG9pvlKaWFImxE6Ap/2OH7sVCgHBhysmDI+naAAnFch2qB7dUr6vn7
+hN6KhdhuCBVxDGK9kBk+6Lo4eSk2lIN5tSf92pHZlcR9rkf5giDoQ3qDZHNvPSlX
+kdHdag0VtoxSvHUi1AGcoW4Hq1YqayeO8s+Acm2MnnNgweK4O9YElEVqsqldQlZ/
+jRgygLAHwgmG+kVahwI//ok0c208MBq3ZZBthAuxjT5a9fqW+9OASgexGR+qmp3+
+zT94bgRGg28EEQ7lzLMpAgMBAAGjaTBnMB0GA1UdDgQWBBS4WdAtqOnzKXm60Q3O
+2sEYTRwEbDAfBgNVHSMEGDAWgBSouO0kAGN6VSErE0jElIB7IQyvpDAaBgNVHREE
+EzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOC
+AQEAk3Fad2CkccpvjQBfs6+8rN+sVNUTiFyOJ4EFF+OlsCVSAVYgcX5wi3ddUPHL
+2TKOvKmiAF/aWQ8X4wQWBPq0xBN56qwNbxGv2Fc/9dMQo+YtEt2+3yCi83tpAyjP
+hAId4aHFRCjzcfb0Zwq7qmfrtorxfY59dAXWHCNhTcxETFCKxaBg7ZSWLFXSef/q
+fL64iyxzb2gctCPHgAp/jANpO1vGLPBO0M1mCBp/I95jgyscZdX9TgqwXuZgsI+d
+nGUsd8cUCxnA6RLiek+z7Y8gN/RITmGYuCGMfWqKFrUQ4wQZRkZJEE7nyJis/uCK
+VdJyfewvRvjnLvqfgF16cBJwQw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIVALA5mjBbdcSBX/AX5ugQy+gbiBJwMA0GCSqGSIb3DQEB
+CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
+ZXJhdGVkIENBMB4XDTIyMDExOTA5MDIzM1oXDTI1MDExODA5MDIzM1owNDEyMDAG
+A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfQ7Br31QqYFaXjWYKG8Vh
+FnPMnZGAT3L9xW7TdBQ1vlp3pnv77vMg0NZXLLx7FUp5HzZj/I2mUdADTxL/fWg5
+WCtPH6UzFFimk8H2v30OFGSGkdIB6tAXuesuZBihIhIb14OY4btBWoyUwOdMgRX8
+SAzFq+zpq3P49Aiv9tU7icXJyrD2wZCIS0L/nogjIFXXnmUQLFYfVlm7xFQnFTqw
+sdTpKthkgQyV6hYaCInktP+X+osOrlnOqHWpRpqgqqj1OB/TqocACpgH1Wmgt0F+
+IR0acVWR1jV0EbSL15i0QTRFgw4/7AbXXf8SKtkhw+SP+epyjDsh9mA1gSiT5q1t
+AgMBAAGjUzBRMB0GA1UdDgQWBBSouO0kAGN6VSErE0jElIB7IQyvpDAfBgNVHSME
+GDAWgBSouO0kAGN6VSErE0jElIB7IQyvpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBCwUAA4IBAQB1tuaFxErPbAlVojdRTFbosqoNRS4kcXhKO3Evk4h9yqkH
+kplWPv+5/0PRFycYu9eZau0Gghzsd7ePcra8WLLwFPofuJad6wefWvbb0qGZmsi+
+yQW8/CGWTVVjJZPc1WMElP4eLvMhPrdS2Wioq2s4b9vYHBUHxLrDsx9dr4A4s4Yw
+/dt0b15KrscNRXdM0rnvhAghh6grZ+P9lg4wyDEYr3e3ZUROPBWBT/yjveNOLj7n
+7M28rgVkAvKzqtb3shLQL4UnsQJfB67sKpruIt+VjecUaTjvLyYaH4NvnlvqOIr3
+Eg+gjpSRGnatAzgwBHx5WYU4FTKfGdrmO81kngyA
+-----END CERTIFICATE-----

+ 28 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/http.key

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCe79lY0Q4PQm24
+IBR6OAtjLSdFLFtwC8/i1Z5ujgPkkNW5QrlveGRU52V5Vp9CC8lcngiKwRcW5vmG
+9pvlKaWFImxE6Ap/2OH7sVCgHBhysmDI+naAAnFch2qB7dUr6vn7hN6KhdhuCBVx
+DGK9kBk+6Lo4eSk2lIN5tSf92pHZlcR9rkf5giDoQ3qDZHNvPSlXkdHdag0VtoxS
+vHUi1AGcoW4Hq1YqayeO8s+Acm2MnnNgweK4O9YElEVqsqldQlZ/jRgygLAHwgmG
++kVahwI//ok0c208MBq3ZZBthAuxjT5a9fqW+9OASgexGR+qmp3+zT94bgRGg28E
+EQ7lzLMpAgMBAAECggEAC2x05E/eYVVmenn/zssRcnbv4CZwigynTAgLo6mceQS5
+/99eYbc3Cu0423BQ8RfUyM3pEkQPq0s9uygli2KjbX7MZmWBP7Awif00LNXoIfJV
+R4zNEKVcTYjELIOSM15nyl7B+hXluP7mv6HixPpC/kUPAXkf77bb/lb5gWMA4bEn
+ghRhHcB9wLh9UZT8rP9q8fisygnGoRRxP2vdVYP7aczeW+tdIjyPV86QTkLgQgE/
+KA5BHJihexzqufoud9IOpp7klp4c3VYcnWVYBob71aREQC6CG3iRU1hNMZHOCCeP
+lM8avVkXakGHzjOWceKLUTafmEcnAJ9+RUss+MZX+wKBgQDZs2E3lTzThy7Wa5SK
+YmrZUB+7W24Or1SzJtjqulDDukQewlS8Bb2HuPAkA+Gv3/euP96XlQNwHr/WEBwE
+JPDqyZQobiYRS2TEdaOn8ALrTyfVo+VIqaDArZo/BBFjfASNFhs6vaEccO+TcEJr
+iCbE7iuPYRQmRmuhlwnd0Pwg8wKBgQC65epONDH7VNbmqe8Jd/zzX8Chwm+bcUZb
+uDSxi1vfFpp+zvx+EVjyquQz0yE4czTuogU5UesIBx9qOU5eBpbnjqZ0Q3yyRrR/
+tRZMxcpr9Zao42t7LNBAsLaONKZxpbNQifkuT60eFRcBrYmvu+ak7rj1am8FHXC/
+HfvYlJgCcwKBgAqG2v+WDs/nrIMfxpn+ck45yp8Dp8m7/qt/CGQpSDh+rEaUfQZu
+fKCwd6Q2L9aSTOa3HetUgEsOoZYXR1OH+cJQpwJheSPC8odxbM11FJ8OP83b0/10
+flpyyo+bgb++wnWUCRMJEl5Os7b8aanLdpx1K9QODKxhIVk59XctYNOzAoGBAK7k
+XUiHIUpuTpcU6AF1Tbg0nx7/ws6zxkIn8u89WGA/V51YdgBGnXSfo+I3LLwVWq//
+32GPHZ+qgqQ1MfdctTPEUlEzoel1roxe2ZpH8C7sZuptvfNKsSoH8xzJegOZKl4w
+boP+vSIMXDYOvWw8Kg2m8l99FJy1uv6swTIfgsuvAoGBAJ9Cmsol0u/WqzWBKmHY
+oR7ukYgUG6dRiKkz971UBDp0J7SrVQ437OO4IzsSg4Rt0rM/y+EIx5NM0PLq1KKZ
+fu+TmepPWwPeQKYPp6YwBB5qNvG90AdwyAUabW23khDZM0vBFBv+n1oZRHqJ8oqW
+uze4XCHJfUn5+lmukNTkul78
+-----END PRIVATE KEY-----

+ 20 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/http_ca.crt

@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIVALA5mjBbdcSBX/AX5ugQy+gbiBJwMA0GCSqGSIb3DQEB
+CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
+ZXJhdGVkIENBMB4XDTIyMDExOTA5MDIzM1oXDTI1MDExODA5MDIzM1owNDEyMDAG
+A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfQ7Br31QqYFaXjWYKG8Vh
+FnPMnZGAT3L9xW7TdBQ1vlp3pnv77vMg0NZXLLx7FUp5HzZj/I2mUdADTxL/fWg5
+WCtPH6UzFFimk8H2v30OFGSGkdIB6tAXuesuZBihIhIb14OY4btBWoyUwOdMgRX8
+SAzFq+zpq3P49Aiv9tU7icXJyrD2wZCIS0L/nogjIFXXnmUQLFYfVlm7xFQnFTqw
+sdTpKthkgQyV6hYaCInktP+X+osOrlnOqHWpRpqgqqj1OB/TqocACpgH1Wmgt0F+
+IR0acVWR1jV0EbSL15i0QTRFgw4/7AbXXf8SKtkhw+SP+epyjDsh9mA1gSiT5q1t
+AgMBAAGjUzBRMB0GA1UdDgQWBBSouO0kAGN6VSErE0jElIB7IQyvpDAfBgNVHSME
+GDAWgBSouO0kAGN6VSErE0jElIB7IQyvpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBCwUAA4IBAQB1tuaFxErPbAlVojdRTFbosqoNRS4kcXhKO3Evk4h9yqkH
+kplWPv+5/0PRFycYu9eZau0Gghzsd7ePcra8WLLwFPofuJad6wefWvbb0qGZmsi+
+yQW8/CGWTVVjJZPc1WMElP4eLvMhPrdS2Wioq2s4b9vYHBUHxLrDsx9dr4A4s4Yw
+/dt0b15KrscNRXdM0rnvhAghh6grZ+P9lg4wyDEYr3e3ZUROPBWBT/yjveNOLj7n
+7M28rgVkAvKzqtb3shLQL4UnsQJfB67sKpruIt+VjecUaTjvLyYaH4NvnlvqOIr3
+Eg+gjpSRGnatAzgwBHx5WYU4FTKfGdrmO81kngyA
+-----END CERTIFICATE-----

+ 28 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/http_ca.key

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfQ7Br31QqYFaX
+jWYKG8VhFnPMnZGAT3L9xW7TdBQ1vlp3pnv77vMg0NZXLLx7FUp5HzZj/I2mUdAD
+TxL/fWg5WCtPH6UzFFimk8H2v30OFGSGkdIB6tAXuesuZBihIhIb14OY4btBWoyU
+wOdMgRX8SAzFq+zpq3P49Aiv9tU7icXJyrD2wZCIS0L/nogjIFXXnmUQLFYfVlm7
+xFQnFTqwsdTpKthkgQyV6hYaCInktP+X+osOrlnOqHWpRpqgqqj1OB/TqocACpgH
+1Wmgt0F+IR0acVWR1jV0EbSL15i0QTRFgw4/7AbXXf8SKtkhw+SP+epyjDsh9mA1
+gSiT5q1tAgMBAAECggEAGBtMTb+uQy3K3ZdbAxuvVXdAYY/WHBL/3QFF5FQf0lS0
+59h3etZGecidMPEbZGaqxkj0WCURocOkwI4hr4dhamX57uOOLfxIljTWTqLZwHL+
+Fk+7IcF+QaEew9UF1K9OCLsnIacaoL/zzc5Mabbo8i32X53wNxxVv7CHIhoIA5dP
+9PSRDFXo5ZGDZ8s4ZVWDYyG9nPE42DF/DHk/aS7wGHuao1cQC+dQq73ZaJbT/1eT
+PM6a87TLUpcfTzJsvVK2dgTtYSEPXKRhX140+gO9Nj65RsCvUh3OF6k7IEFrF8fW
+rWb2oB5BhXPfr/pv5sselNvRcEd9kELJsotzcVZzBwKBgQC/OLPg3yxhi/TDEqqm
+YR38U/h3raJmicWGFITkomIOfZSZEkqPgmKXiAUrWSQuhMUCv/Ukm/phq6A+u3vB
+/Zp/hRbcw/lbemexd7eK9sQWHNT5avtH5Jykbt2eKW9oGK7URlZTTNFfMdfwf6Ug
+M7G9otavpo7J8RjvojVqdM6o0wKBgQDVN5KxxYSXohfxymGPfzteWoWGVDl77tB5
+JAeCwlkMPWkIt6UEj3enC4hImyozdt1wZsjXiefYPPt6JuE32Faf4Z5b8SWaMIt2
+mzUQybvtozOF/AOTlLAYrwajbqoDpaAVddTGgBpkqM/8WRB9NhAe73MuKokNh/Z4
+HYSc3btovwKBgF7rsr7piYpSgwlidrtocg4TUL93vanQsfBAt0cXJD21MNJbNg/O
+1UoLByXhdghxd6pJx1KI6t0Y6M67Gk/Np6etVQ/5aAp01IxPtRiSLfwcC8xTwrCJ
+Vwh40CH2x4qQ1hghYjCvbS8n+t0zP7CgeJZ+ArvmnfK4b3xktMdHfF5pAoGAEgRq
+Lv+D3tRhtZrmd5vGxohbtCg121Uo5LO4tsH5iGFAGO570VDWSZVd5NTH0iV/PNpS
+qnQK1WkBzyat7WwolcYY/af5B3iGsHUZHNwPN1uNJQtQug3ce5l+tBzL3RcH2ghk
+/IkisdLaEHbuP8ZrwlF1qDcL8crFdwz3gdHy3j0CgYEAhpZ/FWrf1eaXk91D2Xba
+kXeAFNENAsNgwubHhFlGUm0TxzkZYimrdAzqL9mpPl08r9awR1nfglfeW+n4vLB/
+9zHcegXxdK9hbxR3ES6jAX07tQXtBPNNMsHwqugfYF9acKrPDTXO1OOHOKz4/vy2
+cvt1BUtzvB57eQd2QtXhXCo=
+-----END PRIVATE KEY-----

+ 19 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/transport.crt

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIjCCAgqgAwIBAgIUS83knQ28f817BNKYxsKC9S2achYwDQYJKoZIhvcNAQEL
+BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
+cmF0ZWQgQ0EwHhcNMjIwMTE5MDkwNTUxWhcNMjUwMTE4MDkwNTUxWjATMREwDwYD
+VQQDEwhpbnN0YW5jZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8Z
+dERaKHI8+9BOmWo6cD20bSkNTWwJI9BhqT4Cxj/zBmK06mOwqHqfw5DZMRFUOuCU
+BeDlX35vr+2eEcWnXWA0zkYPFBwN2ZXtae1Cjyo+JN34onsF2BwpBbAz1BwABs+Z
+W9tsrlkRvJ1Msfxr2DJOjJK5vZVF89DCzS7qqALlSlJMfvPGj+wBjf3MoS1AFIIu
+UGBOKtbVEOVVchvZ5VA3wSsgaT94/T5ISFjVnSR572PJJJ7ve2K5Z8crClscDQHE
+mxxHRnuqgVbBOe3K2ltMvOtpCTOSSP2u1H1EAW9IH1KSYB51E6Ob6E/R3qY7ukrr
+ipZ06TVK6wC3rwaVRQMCAwEAAaNNMEswHQYDVR0OBBYEFNBu7h1/U3tr51nO9frB
+NE9fsYyKMB8GA1UdIwQYMBaAFKi47SQAY3pVISsTSMSUgHshDK+kMAkGA1UdEwQC
+MAAwDQYJKoZIhvcNAQELBQADggEBACV6DaVgMpfNRRMY1xM3G1fJeSXt7sZQxTUM
+IwpzKvFpoUo8Qcz5ZVW0ZJ0syoPZcnDjYBCM4HfcI7T5tCNH2TFWbRjacjNfu2gz
+p8NycN8proqKKnNDRr5XqRqJvzaU4OfNXIbkKY1B5MZJsJWB5CNMGBfrLfKu/rhl
+kdxndwa+eTJCHcJBGauZmQ8wgqhn8UIUv9+VLVjyDMA3AgtUvwYgKKYIKF4Ev0XZ
+b7RxIQ1c+h+/hkvzDP5KOTkr1Ri6tCIMaCz3Bosk8CfwNQDFGHa+vabm98wQTBmI
+Ke4hGkuAX/crqzFruWkQ0Lw6r4ZjD2/I6ZKKj+BQLmiQAfQ5l/A=
+-----END CERTIFICATE-----

+ 28 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/transport.key

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCvGXREWihyPPvQ
+TplqOnA9tG0pDU1sCSPQYak+AsY/8wZitOpjsKh6n8OQ2TERVDrglAXg5V9+b6/t
+nhHFp11gNM5GDxQcDdmV7WntQo8qPiTd+KJ7BdgcKQWwM9QcAAbPmVvbbK5ZEbyd
+TLH8a9gyToySub2VRfPQws0u6qgC5UpSTH7zxo/sAY39zKEtQBSCLlBgTirW1RDl
+VXIb2eVQN8ErIGk/eP0+SEhY1Z0kee9jySSe73tiuWfHKwpbHA0BxJscR0Z7qoFW
+wTntytpbTLzraQkzkkj9rtR9RAFvSB9SkmAedROjm+hP0d6mO7pK64qWdOk1SusA
+t68GlUUDAgMBAAECggEAQUuVrtOMF6WkJbbZwOJP7sK0nkVmpK333AI/MW+pbDl9
+HRvn+ArtmOSw8ff8M0Ecv5iTZJ1EcgrGKS7z46gYywKGFVwUHK4RgLZK9P4IEzGf
+X+MS/BtezomBpIZ/R32/DHVc6sNpdK+HjYUHLmEs+v2NsD1xdBPk6ulpyGAbDRDD
+y+xofqz7RlQhAaXtbdgz3oH1Waz5uBIcy7a576e6X3GZSjq8sCgRgIAYnMSXxMJC
+VW5pl4S+sk3XeDK0G10ur7Bb/4BclWQCqbgkdQ9sgGn5/JYFcAOD/g+VriKSrM9Q
+DdUMZgFuJVyCeexMW4W3MIXj7A4DT0TfbhwA9cktaQKBgQDFOTq5MuHzXDRvT0ui
+almzptvhlZFw1esehXHh870xK6K3oHNNisT4v6UCX/aSWXrWapONx7cWUl+DbKZZ
+8Qj4SdA0tIQ1qrZn8aYZAYbt5dVgNhHHntg/0wQf/dR5/szgFp4Q47SDpATaNPOd
+04QaGsEpAQ5lI2+5g7ZAXmJbhwKBgQDjSE9PH1+BaDjk7KyPZJNrOuX0XP8/4oRZ
+vok9hoGkHT/7idEkryINGWaJqdW0COoNXCDDuiClQ9dFN09QxBAQR0uA4JPajCsE
+9f/BY2OSy0XfrsB/tUtNyxC8IGc8G/VTJVEQUNMvgRt4116HIjOHG70EUyAb9IYu
+Tdei0zVBpQKBgQCZ+F/cDdlQgH9/FszZc2WsV2v55SayjI8OOOf7mqntJT/XU7Aw
+rVGxUQylmf2Jq8m2c2XWnkBVcOGYXM5SEVcLX7ToMLW2oBvfckxV4VdRisjWX9/p
+lB0HVto1j5i91SplF8M3NE3NJ9OR9xzp7iOHrbN3K5ftZYjnr+gswILRKQKBgQDf
+yyykmRy+XHRhHZZQX1U1KbqR4hxHuHBfudiC53WxtkdxE+QGvhfGVDN96+gMLRbh
+bsyoTRamBGXstqh3u4ahsMHstbWAZbJaYSujLY03VsaHmRfc6BOtFv10cGeWbWUj
+qMPoT92SkgsN8usWHpwkNjDpGDyuqhiRcX6ZymRPLQKBgC6+P5ijHZ4ewaEGpgzW
+fFh18MYGfFQDiFGijM1f/RmouB1im2pXsOFrzOfKENVfizhYw9yQKf+/Bg0tXY0Q
+NWCq9WVRrx7GZKfUYLfAI9I/vCviRNdHNabnWbYE3FbG04C/lJeHt0VljDa61Ve6
+Oxqu0QUOd+JzRfuHISztZNWy
+-----END PRIVATE KEY-----

+ 20 - 0
qa/os/src/test/resources/org/elasticsearch/packaging/test/transport_ca.crt

@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIVALA5mjBbdcSBX/AX5ugQy+gbiBJwMA0GCSqGSIb3DQEB
+CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
+ZXJhdGVkIENBMB4XDTIyMDExOTA5MDIzM1oXDTI1MDExODA5MDIzM1owNDEyMDAG
+A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfQ7Br31QqYFaXjWYKG8Vh
+FnPMnZGAT3L9xW7TdBQ1vlp3pnv77vMg0NZXLLx7FUp5HzZj/I2mUdADTxL/fWg5
+WCtPH6UzFFimk8H2v30OFGSGkdIB6tAXuesuZBihIhIb14OY4btBWoyUwOdMgRX8
+SAzFq+zpq3P49Aiv9tU7icXJyrD2wZCIS0L/nogjIFXXnmUQLFYfVlm7xFQnFTqw
+sdTpKthkgQyV6hYaCInktP+X+osOrlnOqHWpRpqgqqj1OB/TqocACpgH1Wmgt0F+
+IR0acVWR1jV0EbSL15i0QTRFgw4/7AbXXf8SKtkhw+SP+epyjDsh9mA1gSiT5q1t
+AgMBAAGjUzBRMB0GA1UdDgQWBBSouO0kAGN6VSErE0jElIB7IQyvpDAfBgNVHSME
+GDAWgBSouO0kAGN6VSErE0jElIB7IQyvpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBCwUAA4IBAQB1tuaFxErPbAlVojdRTFbosqoNRS4kcXhKO3Evk4h9yqkH
+kplWPv+5/0PRFycYu9eZau0Gghzsd7ePcra8WLLwFPofuJad6wefWvbb0qGZmsi+
+yQW8/CGWTVVjJZPc1WMElP4eLvMhPrdS2Wioq2s4b9vYHBUHxLrDsx9dr4A4s4Yw
+/dt0b15KrscNRXdM0rnvhAghh6grZ+P9lg4wyDEYr3e3ZUROPBWBT/yjveNOLj7n
+7M28rgVkAvKzqtb3shLQL4UnsQJfB67sKpruIt+VjecUaTjvLyYaH4NvnlvqOIr3
+Eg+gjpSRGnatAzgwBHx5WYU4FTKfGdrmO81kngyA
+-----END CERTIFICATE-----

+ 44 - 12
x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/AutoConfigureNode.java

@@ -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)) {