This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 9e58a5c9ee write the file config store with owner-only permissions
(#3411)
9e58a5c9ee is described below
commit 9e58a5c9ee4850d7c1c86918218b78150ad17907
Author: Naveed <[email protected]>
AuthorDate: Mon Oct 26 23:39:03 2026 +0530
write the file config store with owner-only permissions (#3411)
* write the file config store with owner-only permissions
FileBasedConfigStore wrote config-store.json through a sibling .tmp file
which carry resolved credentials, were readable by every local user under
the default 022 umask. The .tmp is now created owner-only on POSIX
filesystems before it is written and atomically renamed into place.
* use a unique owner-only temp file per config store save
---
.../pipes/core/config/FileBasedConfigStore.java | 32 ++++++++++++++++++++--
.../tika/pipes/core/config/ConfigStoreTest.java | 28 +++++++++++++++++++
2 files changed, 57 insertions(+), 3 deletions(-)
diff --git
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
index ee0de07fd5..b43b1bf2cd 100644
---
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
+++
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/config/FileBasedConfigStore.java
@@ -17,10 +17,14 @@
package org.apache.tika.pipes.core.config;
import java.io.IOException;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -41,6 +45,9 @@ public class FileBasedConfigStore implements ConfigStore {
private static final Logger LOG =
LoggerFactory.getLogger(FileBasedConfigStore.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ // stored configs carry fetcher/emitter credentials; keep them from other
local users
+ private static final Set<PosixFilePermission> OWNER_ONLY =
+ EnumSet.of(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE);
private final Path configFile;
private final ConcurrentHashMap<String, ExtensionConfig> cache = new
ConcurrentHashMap<>();
@@ -150,16 +157,35 @@ public class FileBasedConfigStore implements ConfigStore {
}
private synchronized void saveToFile() {
+ Path tempFile = null;
try {
- // Write to temp file first, then atomic rename
- Path tempFile = Paths.get(configFile.toString() + ".tmp");
+ // Write to a unique temp file first, then atomic rename; a unique
name keeps
+ // other instances/processes from deleting or recreating it
mid-write
+ tempFile =
Files.createTempFile(configFile.toAbsolutePath().getParent(),
+ configFile.getFileName() + ".", ".tmp", ownerOnly());
OBJECT_MAPPER.writerWithDefaultPrettyPrinter()
.writeValue(tempFile.toFile(), cache);
Files.move(tempFile, configFile,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+ tempFile = null;
LOG.debug("Saved {} configurations to {}", cache.size(),
configFile);
} catch (IOException e) {
LOG.error("Failed to save configurations to {}", configFile, e);
throw new RuntimeException("Failed to save config store", e);
+ } finally {
+ if (tempFile != null) {
+ try {
+ Files.deleteIfExists(tempFile);
+ } catch (IOException e) {
+ LOG.warn("Failed to delete temp file {}", tempFile, e);
+ }
+ }
}
}
+
+ private static FileAttribute<?>[] ownerOnly() {
+ if
(FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
+ return new
FileAttribute<?>[]{PosixFilePermissions.asFileAttribute(OWNER_ONLY)};
+ }
+ return new FileAttribute<?>[0];
+ }
}
diff --git
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
index dbffab352f..dbaab85dc8 100644
---
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
+++
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/config/ConfigStoreTest.java
@@ -21,8 +21,19 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import org.apache.tika.plugins.ExtensionConfig;
@@ -112,4 +123,21 @@ public class ConfigStoreTest {
assertNull(store.get("nonexistent"));
}
+
+ @Test
+ public void testFileBasedConfigStoreIsOwnerOnly(@TempDir Path tmp) throws
Exception {
+
assumeTrue(FileSystems.getDefault().supportedFileAttributeViews().contains("posix"));
+ Path file = tmp.resolve("store").resolve("config-store.json");
+ FileBasedConfigStore store = new FileBasedConfigStore(file);
+ store.init();
+ store.put("s3", new ExtensionConfig("s3", "s3-fetcher",
"{\"secretKey\":\"hunter2\"}"));
+ store.put("http", new ExtensionConfig("http", "http-fetcher",
"{\"password\":\"x\"}"));
+
+ Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
+ assertEquals(Set.of(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE), perms);
+ try (Stream<Path> siblings = Files.list(file.getParent())) {
+ assertEquals(List.of(file), siblings.collect(Collectors.toList()));
+ }
+ assertEquals("s3-fetcher", new
FileBasedConfigStore(file).get("s3").name());
+ }
}