Skip to content

Commit 1c234ff

Browse files
authored
Backport: Add exclusive file entitlement for settings (#125272) (#126006) (#126059)
1 parent 6e5a9b5 commit 1c234ff

File tree

6 files changed

+145
-12
lines changed

6 files changed

+145
-12
lines changed

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import java.util.stream.StreamSupport;
6969

7070
import static org.elasticsearch.entitlement.runtime.policy.Platform.LINUX;
71+
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.CONFIG;
7172
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.DATA;
7273
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.SHARED_REPO;
7374
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
@@ -182,6 +183,8 @@ private static PolicyManager createPolicyManager() {
182183
FileData.ofPath(bootstrapArgs.libDir(), READ),
183184
FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE),
184185
FileData.ofRelativePath(Path.of(""), SHARED_REPO, READ_WRITE),
186+
// exclusive settings file
187+
FileData.ofRelativePath(Path.of("operator/settings.json"), CONFIG, READ_WRITE).withExclusive(true),
185188

186189
// OS release on Linux
187190
FileData.ofPath(Path.of("/etc/os-release"), READ).withPlatform(LINUX),

server/src/main/java/org/elasticsearch/common/file/AbstractFileWatchingService.java

+24-7
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@
1616
import org.elasticsearch.reservedstate.service.FileChangedListener;
1717

1818
import java.io.IOException;
19+
import java.io.InputStream;
1920
import java.nio.file.ClosedWatchServiceException;
20-
import java.nio.file.Files;
2121
import java.nio.file.Path;
2222
import java.nio.file.StandardWatchEventKinds;
2323
import java.nio.file.WatchKey;
2424
import java.nio.file.WatchService;
2525
import java.nio.file.attribute.BasicFileAttributes;
26+
import java.nio.file.attribute.FileTime;
2627
import java.util.List;
2728
import java.util.concurrent.CopyOnWriteArrayList;
2829
import java.util.concurrent.ExecutionException;
30+
import java.util.stream.Stream;
2931

3032
/**
3133
* A skeleton service for watching and reacting to a single file changing on disk
@@ -119,20 +121,20 @@ public final boolean watching() {
119121
// platform independent way to tell if a file changed
120122
// we compare the file modified timestamp, the absolute path (symlinks), and file id on the system
121123
final boolean watchedFileChanged(Path path) throws IOException {
122-
if (Files.exists(path) == false) {
124+
if (filesExists(path) == false) {
123125
return false;
124126
}
125127

126128
FileUpdateState previousUpdateState = fileUpdateState;
127129

128-
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
130+
BasicFileAttributes attr = filesReadAttributes(path, BasicFileAttributes.class);
129131
fileUpdateState = new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey());
130132

131133
return (previousUpdateState == null || previousUpdateState.equals(fileUpdateState) == false);
132134
}
133135

134136
protected final synchronized void startWatcher() {
135-
if (Files.exists(watchedFileDir.getParent()) == false) {
137+
if (filesExists(watchedFileDir.getParent()) == false) {
136138
logger.warn("File watcher for [{}] cannot start because grandparent directory does not exist", watchedFile);
137139
return;
138140
}
@@ -147,7 +149,7 @@ protected final synchronized void startWatcher() {
147149
try {
148150
Path settingsDirPath = watchedFileDir();
149151
this.watchService = settingsDirPath.getParent().getFileSystem().newWatchService();
150-
if (Files.exists(settingsDirPath)) {
152+
if (filesExists(settingsDirPath)) {
151153
settingsDirWatchKey = enableDirectoryWatcher(settingsDirWatchKey, settingsDirPath);
152154
} else {
153155
logger.debug("watched directory [{}] not found, will watch for its creation...", settingsDirPath);
@@ -181,7 +183,7 @@ protected final void watcherThread() {
181183

182184
Path path = watchedFile();
183185

184-
if (Files.exists(path)) {
186+
if (filesExists(path)) {
185187
logger.debug("found initial operator settings file [{}], applying...", path);
186188
processSettingsOnServiceStartAndNotifyListeners();
187189
} else {
@@ -209,7 +211,7 @@ protected final void watcherThread() {
209211
* real path of our desired file. We don't actually care what changed, we just re-check ourselves.
210212
*/
211213
Path settingsPath = watchedFileDir();
212-
if (Files.exists(settingsPath)) {
214+
if (filesExists(settingsPath)) {
213215
try {
214216
if (logger.isDebugEnabled()) {
215217
key.pollEvents().forEach(e -> logger.debug("{}:{}", e.kind().toString(), e.context().toString()));
@@ -332,4 +334,19 @@ long retryDelayMillis(int failedCount) {
332334
* class to determine if a file has been changed.
333335
*/
334336
private record FileUpdateState(long timestamp, String path, Object fileKey) {}
337+
338+
// the following methods are a workaround to ensure exclusive access for files
339+
// required by child watchers; this is required because we only check the caller's module
340+
// not the entire stack
341+
protected abstract boolean filesExists(Path path);
342+
343+
protected abstract boolean filesIsDirectory(Path path);
344+
345+
protected abstract <A extends BasicFileAttributes> A filesReadAttributes(Path path, Class<A> clazz) throws IOException;
346+
347+
protected abstract Stream<Path> filesList(Path dir) throws IOException;
348+
349+
protected abstract Path filesSetLastModifiedTime(Path path, FileTime time) throws IOException;
350+
351+
protected abstract InputStream filesNewInputStream(Path path) throws IOException;
335352
}

server/src/main/java/org/elasticsearch/common/file/MasterNodeFileWatchingService.java

+39-3
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
import org.elasticsearch.gateway.GatewayService;
2020

2121
import java.io.IOException;
22+
import java.io.InputStream;
2223
import java.nio.file.Files;
2324
import java.nio.file.Path;
25+
import java.nio.file.attribute.BasicFileAttributes;
2426
import java.nio.file.attribute.FileTime;
2527
import java.time.Instant;
28+
import java.util.stream.Stream;
2629

2730
public abstract class MasterNodeFileWatchingService extends AbstractFileWatchingService implements ClusterStateListener {
2831

@@ -41,7 +44,7 @@ protected void doStart() {
4144
// We start the file watcher when we know we are master from a cluster state change notification.
4245
// We need the additional active flag, since cluster state can change after we've shutdown the service
4346
// causing the watcher to start again.
44-
this.active = Files.exists(watchedFileDir().getParent());
47+
this.active = filesExists(watchedFileDir().getParent());
4548
if (active == false) {
4649
// we don't have a config directory, we can't possibly launch the file settings service
4750
return;
@@ -86,9 +89,9 @@ public final void clusterChanged(ClusterChangedEvent event) {
8689
*/
8790
private void refreshExistingFileStateIfNeeded(ClusterState clusterState) {
8891
if (watching()) {
89-
if (shouldRefreshFileState(clusterState) && Files.exists(watchedFile())) {
92+
if (shouldRefreshFileState(clusterState) && filesExists(watchedFile())) {
9093
try {
91-
Files.setLastModifiedTime(watchedFile(), FileTime.from(Instant.now()));
94+
filesSetLastModifiedTime(watchedFile(), FileTime.from(Instant.now()));
9295
} catch (IOException e) {
9396
logger.warn("encountered I/O error trying to update file settings timestamp", e);
9497
}
@@ -107,4 +110,37 @@ private void refreshExistingFileStateIfNeeded(ClusterState clusterState) {
107110
protected boolean shouldRefreshFileState(ClusterState clusterState) {
108111
return false;
109112
}
113+
114+
// the following methods are a workaround to ensure exclusive access for files
115+
// required by child watchers; this is required because we only check the caller's module
116+
// not the entire stack
117+
@Override
118+
protected boolean filesExists(Path path) {
119+
return Files.exists(path);
120+
}
121+
122+
@Override
123+
protected boolean filesIsDirectory(Path path) {
124+
return Files.isDirectory(path);
125+
}
126+
127+
@Override
128+
protected <A extends BasicFileAttributes> A filesReadAttributes(Path path, Class<A> clazz) throws IOException {
129+
return Files.readAttributes(path, clazz);
130+
}
131+
132+
@Override
133+
protected Stream<Path> filesList(Path dir) throws IOException {
134+
return Files.list(dir);
135+
}
136+
137+
@Override
138+
protected Path filesSetLastModifiedTime(Path path, FileTime time) throws IOException {
139+
return Files.setLastModifiedTime(path, time);
140+
}
141+
142+
@Override
143+
protected InputStream filesNewInputStream(Path path) throws IOException {
144+
return Files.newInputStream(path);
145+
}
110146
}

server/src/main/java/org/elasticsearch/reservedstate/service/FileSettingsService.java

+40-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@
2424

2525
import java.io.BufferedInputStream;
2626
import java.io.IOException;
27+
import java.io.InputStream;
2728
import java.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.nio.file.attribute.BasicFileAttributes;
31+
import java.nio.file.attribute.FileTime;
2832
import java.util.concurrent.ExecutionException;
33+
import java.util.stream.Stream;
2934

3035
import static org.elasticsearch.reservedstate.service.ReservedStateVersionCheck.HIGHER_OR_SAME_VERSION;
3136
import static org.elasticsearch.reservedstate.service.ReservedStateVersionCheck.HIGHER_VERSION_ONLY;
@@ -84,7 +89,7 @@ public void handleSnapshotRestore(ClusterState clusterState, Metadata.Builder md
8489
// since we don't know the current operator configuration, e.g. file settings could be disabled
8590
// on the target cluster. If file settings exist and the cluster state has lost it's reserved
8691
// state for the "file_settings" namespace, we touch our file settings file to cause it to re-process the file.
87-
if (watching() && Files.exists(watchedFile())) {
92+
if (watching() && filesExists(watchedFile())) {
8893
if (fileSettingsMetadata != null) {
8994
ReservedStateMetadata withResetVersion = new ReservedStateMetadata.Builder(fileSettingsMetadata).version(0L).build();
9095
mdBuilder.put(withResetVersion);
@@ -134,7 +139,7 @@ protected void processFileOnServiceStart() throws IOException, ExecutionExceptio
134139
private void processFileChanges(ReservedStateVersionCheck versionCheck) throws IOException, InterruptedException, ExecutionException {
135140
PlainActionFuture<Void> completion = new PlainActionFuture<>();
136141
try (
137-
var fis = Files.newInputStream(watchedFile());
142+
var fis = filesNewInputStream(watchedFile());
138143
var bis = new BufferedInputStream(fis);
139144
var parser = JSON.xContent().createParser(XContentParserConfiguration.EMPTY, bis)
140145
) {
@@ -158,4 +163,37 @@ private static void completeProcessing(Exception e, PlainActionFuture<Void> comp
158163
completion.onResponse(null);
159164
}
160165
}
166+
167+
// the following methods are a workaround to ensure exclusive access for files
168+
// required by child watchers; this is required because we only check the caller's module
169+
// not the entire stack
170+
@Override
171+
protected boolean filesExists(Path path) {
172+
return Files.exists(path);
173+
}
174+
175+
@Override
176+
protected boolean filesIsDirectory(Path path) {
177+
return Files.isDirectory(path);
178+
}
179+
180+
@Override
181+
protected <A extends BasicFileAttributes> A filesReadAttributes(Path path, Class<A> clazz) throws IOException {
182+
return Files.readAttributes(path, clazz);
183+
}
184+
185+
@Override
186+
protected Stream<Path> filesList(Path dir) throws IOException {
187+
return Files.list(dir);
188+
}
189+
190+
@Override
191+
protected Path filesSetLastModifiedTime(Path path, FileTime time) throws IOException {
192+
return Files.setLastModifiedTime(path, time);
193+
}
194+
195+
@Override
196+
protected InputStream filesNewInputStream(Path path) throws IOException {
197+
return Files.newInputStream(path);
198+
}
161199
}

server/src/test/java/org/elasticsearch/common/file/AbstractFileWatchingServiceTests.java

+36
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import org.junit.Before;
2525

2626
import java.io.IOException;
27+
import java.io.InputStream;
2728
import java.nio.charset.StandardCharsets;
2829
import java.nio.file.Files;
2930
import java.nio.file.Path;
3031
import java.nio.file.StandardCopyOption;
3132
import java.nio.file.StandardWatchEventKinds;
3233
import java.nio.file.WatchKey;
34+
import java.nio.file.attribute.BasicFileAttributes;
3335
import java.nio.file.attribute.FileTime;
3436
import java.time.Instant;
3537
import java.time.LocalDateTime;
@@ -38,6 +40,7 @@
3840
import java.util.concurrent.CountDownLatch;
3941
import java.util.concurrent.ExecutionException;
4042
import java.util.concurrent.TimeUnit;
43+
import java.util.stream.Stream;
4144

4245
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
4346
import static org.hamcrest.Matchers.sameInstance;
@@ -81,6 +84,39 @@ protected void processInitialFileMissing() {
8184
countDownLatch.countDown();
8285
}
8386
}
87+
88+
// the following methods are a workaround to ensure exclusive access for files
89+
// required by child watchers; this is required because we only check the caller's module
90+
// not the entire stack
91+
@Override
92+
protected boolean filesExists(Path path) {
93+
return Files.exists(path);
94+
}
95+
96+
@Override
97+
protected boolean filesIsDirectory(Path path) {
98+
return Files.isDirectory(path);
99+
}
100+
101+
@Override
102+
protected <A extends BasicFileAttributes> A filesReadAttributes(Path path, Class<A> clazz) throws IOException {
103+
return Files.readAttributes(path, clazz);
104+
}
105+
106+
@Override
107+
protected Stream<Path> filesList(Path dir) throws IOException {
108+
return Files.list(dir);
109+
}
110+
111+
@Override
112+
protected Path filesSetLastModifiedTime(Path path, FileTime time) throws IOException {
113+
return Files.setLastModifiedTime(path, time);
114+
}
115+
116+
@Override
117+
protected InputStream filesNewInputStream(Path path) throws IOException {
118+
return Files.newInputStream(path);
119+
}
84120
}
85121

86122
private AbstractFileWatchingService fileWatchingService;

x-pack/plugin/core/src/main/config/log4j2.properties

+3
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,6 @@ logger.samlxml_decrypt.name = org.opensaml.xmlsec.encryption.support.Decrypter
115115
logger.samlxml_decrypt.level = fatal
116116
logger.saml2_decrypt.name = org.opensaml.saml.saml2.encryption.Decrypter
117117
logger.saml2_decrypt.level = fatal
118+
119+
logger.entitlements_xpack_security.name = org.elasticsearch.entitlement.runtime.policy.PolicyManager.x-pack-security.org.elasticsearch.security
120+
logger.entitlements_xpack_security.level = error

0 commit comments

Comments
 (0)