Skip to content

Commit f1d1d20

Browse files
authored
Watch SSL files instead of directories (#129738) (#129987)
With the introduction of entitlements (#120243) and exclusive file access (#123087) it is no longer safe to watch a whole directory. In a lot of deployments, the parent directory for SSL config files will be the main config directory, which also contains exclusive files such as SAML realm metadata or File realm users. Watching that directory will cause entitlement warnings because it is not permissible for core/ssl-config to read files that are exclusively owned by the security module (or other modules)
1 parent e17c9ba commit f1d1d20

File tree

4 files changed

+51
-17
lines changed

4 files changed

+51
-17
lines changed

docs/changelog/129738.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 129738
2+
summary: Watch SSL files instead of directories
3+
area: TLS
4+
type: bug
5+
issues: []

server/src/main/java/org/elasticsearch/watcher/FileWatcher.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public FileWatcher(Path path, boolean checkFileContents) {
5757
rootFileObserver = new FileObserver(path);
5858
}
5959

60+
// For testing
61+
public Path getPath() {
62+
return path;
63+
}
64+
6065
/**
6166
* Clears any state with the FileWatcher, making all files show up as new
6267
*/

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloader.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@
2222
import java.util.ArrayList;
2323
import java.util.Collection;
2424
import java.util.HashMap;
25-
import java.util.HashSet;
2625
import java.util.List;
2726
import java.util.Map;
28-
import java.util.Set;
2927
import java.util.concurrent.ExecutionException;
3028
import java.util.concurrent.Future;
3129
import java.util.function.Consumer;
@@ -80,7 +78,7 @@ private static Consumer<SslConfiguration> reloadConsumer(Future<SSLService> futu
8078
}
8179

8280
/**
83-
* Collects all of the directories that need to be monitored for the provided {@link SslConfiguration} instances and ensures that
81+
* Collects all of the files that need to be monitored for the provided {@link SslConfiguration} instances and ensures that
8482
* they are being watched for changes
8583
*/
8684
private static void startWatching(
@@ -91,8 +89,8 @@ private static void startWatching(
9189
Map<Path, List<SslConfiguration>> pathToConfigurationsMap = new HashMap<>();
9290
for (SslConfiguration sslConfiguration : sslConfigurations) {
9391
final Collection<Path> filesToMonitor = sslConfiguration.getDependentFiles();
94-
for (Path directory : directoriesToMonitor(filesToMonitor)) {
95-
pathToConfigurationsMap.compute(directory, (path, list) -> {
92+
for (Path file : filesToMonitor) {
93+
pathToConfigurationsMap.compute(file, (path, list) -> {
9694
if (list == null) {
9795
list = new ArrayList<>();
9896
}
@@ -109,22 +107,11 @@ private static void startWatching(
109107
try {
110108
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
111109
} catch (IOException | SecurityException e) {
112-
logger.error("failed to start watching directory [{}] for ssl configurations [{}] - {}", path, configurations, e);
110+
logger.error("failed to start watching file [{}] for ssl configurations [{}] - {}", path, configurations, e);
113111
}
114112
});
115113
}
116114

117-
/**
118-
* Returns a unique set of directories that need to be monitored based on the provided file paths
119-
*/
120-
private static Set<Path> directoriesToMonitor(Iterable<Path> filePaths) {
121-
Set<Path> paths = new HashSet<>();
122-
for (Path path : filePaths) {
123-
paths.add(path.getParent());
124-
}
125-
return paths;
126-
}
127-
128115
private static class ChangeListener implements FileChangesListener {
129116

130117
private final List<SslConfiguration> sslConfigurations;

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.elasticsearch.test.http.MockWebServer;
3838
import org.elasticsearch.threadpool.TestThreadPool;
3939
import org.elasticsearch.threadpool.ThreadPool;
40+
import org.elasticsearch.watcher.FileWatcher;
41+
import org.elasticsearch.watcher.ResourceWatcher;
4042
import org.elasticsearch.watcher.ResourceWatcherService;
4143
import org.junit.After;
4244
import org.junit.Before;
@@ -66,7 +68,9 @@
6668
import java.security.cert.CertificateException;
6769
import java.util.Collection;
6870
import java.util.Collections;
71+
import java.util.HashSet;
6972
import java.util.List;
73+
import java.util.Set;
7074
import java.util.concurrent.CountDownLatch;
7175
import java.util.concurrent.CyclicBarrier;
7276
import java.util.concurrent.TimeUnit;
@@ -79,6 +83,7 @@
7983
import javax.net.ssl.SSLSocket;
8084

8185
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
86+
import static org.hamcrest.Matchers.containsInAnyOrder;
8287
import static org.hamcrest.Matchers.containsString;
8388
import static org.hamcrest.Matchers.sameInstance;
8489

@@ -559,6 +564,38 @@ public void testFailureToReadFileDoesntFail() throws Exception {
559564
}
560565
}
561566

567+
/**
568+
* Due to exclusive access entitlements
569+
* (see {@link org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.FileData#exclusive}),
570+
* it is not safe to monitor a directory or any files that are not an explicit part of this SSL configuration.
571+
*/
572+
public void testReloaderOnlyWatchesSpecifiedFiles() throws Exception {
573+
final Set<Path> watchedPaths = new HashSet<>();
574+
final ResourceWatcherService mockResourceWatcher = Mockito.mock(ResourceWatcherService.class);
575+
Mockito.when(mockResourceWatcher.add(Mockito.any(ResourceWatcher.class), Mockito.any(ResourceWatcherService.Frequency.class)))
576+
.then(inv -> {
577+
final FileWatcher fileWatcher = asInstanceOf(FileWatcher.class, inv.getArguments()[0]);
578+
watchedPaths.add(fileWatcher.getPath());
579+
return null;
580+
});
581+
582+
final Path tempDir = createTempDir();
583+
final Path clientCertPath = tempDir.resolve("testclient.crt");
584+
Settings settings = baseKeystoreSettings(tempDir, null).putList(
585+
"xpack.security.transport.ssl.certificate_authorities",
586+
clientCertPath.toString()
587+
).put("path.home", createTempDir()).build();
588+
589+
final Environment env = newEnvironment(settings);
590+
final Collection<SslConfiguration> configurations = SSLService.getSSLConfigurations(env).values();
591+
new SSLConfigurationReloader(ignore -> {}, mockResourceWatcher, configurations);
592+
593+
assertThat(
594+
watchedPaths,
595+
containsInAnyOrder(tempDir.resolve("testclient.pem"), tempDir.resolve("testclient.crt"), tempDir.resolve("testclientcert.crt"))
596+
);
597+
}
598+
562599
private Settings.Builder baseKeystoreSettings(Path tempDir, MockSecureSettings secureSettings) throws IOException {
563600
final Path keyPath = tempDir.resolve("testclient.pem");
564601
final Path certPath = tempDir.resolve("testclientcert.crt"); // testclient.crt filename already used in #testPEMTrustReloadException

0 commit comments

Comments
 (0)