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 OWNER_ONLY = + EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); private final Path configFile; private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); @@ -150,16 +157,35 @@ private synchronized void loadFromFile() throws IOException { } 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.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 void testLoggingConfigStore() { 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 perms = Files.getPosixFilePermissions(file); + assertEquals(Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE), perms); + try (Stream siblings = Files.list(file.getParent())) { + assertEquals(List.of(file), siblings.collect(Collectors.toList())); + } + assertEquals("s3-fetcher", new FileBasedConfigStore(file).get("s3").name()); + } }