Add exclusive access files for security module (#123676)

This commit fills out missing entitlements for the security module.
Specifically they are config files which require exclusive access.
This commit is contained in:
Ryan Ernst 2025-03-08 07:02:36 -08:00 committed by GitHub
parent f15cc9667b
commit 7e1195dc9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 115 additions and 90 deletions

View file

@ -23,8 +23,12 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static java.util.Comparator.comparing; import static java.util.Comparator.comparing;
@ -42,27 +46,47 @@ public final class FileAccessTree {
/** /**
* An intermediary structure to help globally validate exclusive paths, and then build exclusive paths for individual modules. * An intermediary structure to help globally validate exclusive paths, and then build exclusive paths for individual modules.
*/ */
record ExclusivePath(String componentName, String moduleName, String path) { record ExclusivePath(String componentName, Set<String> moduleNames, String path) {
@Override @Override
public String toString() { public String toString() {
return "[[" + componentName + "] [" + moduleName + "] [" + path + "]]"; return "[[" + componentName + "] " + moduleNames + " [" + path + "]]";
} }
} }
static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement> exclusiveFileEntitlements, PathLookup pathLookup) { static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement> exclusiveFileEntitlements, PathLookup pathLookup) {
List<ExclusivePath> exclusivePaths = new ArrayList<>(); Map<String, ExclusivePath> exclusivePaths = new LinkedHashMap<>();
for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements) { for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements) {
for (FilesEntitlement.FileData fd : efe.filesEntitlement().filesData()) { for (FilesEntitlement.FileData fd : efe.filesEntitlement().filesData()) {
if (fd.exclusive()) { if (fd.exclusive()) {
List<Path> paths = fd.resolvePaths(pathLookup).toList(); List<Path> paths = fd.resolvePaths(pathLookup).toList();
for (Path path : paths) { for (Path path : paths) {
exclusivePaths.add(new ExclusivePath(efe.componentName(), efe.moduleName(), normalizePath(path))); String normalizedPath = normalizePath(path);
var exclusivePath = exclusivePaths.computeIfAbsent(
normalizedPath,
k -> new ExclusivePath(efe.componentName(), new HashSet<>(), normalizedPath)
);
if (exclusivePath.componentName().equals(efe.componentName()) == false) {
throw new IllegalArgumentException(
"Path ["
+ normalizedPath
+ "] is already exclusive to ["
+ exclusivePath.componentName()
+ "]"
+ exclusivePath.moduleNames
+ ", cannot add exclusive access for ["
+ efe.componentName()
+ "]["
+ efe.moduleName
+ "]"
);
}
exclusivePath.moduleNames.add(efe.moduleName());
} }
} }
} }
} }
return exclusivePaths.stream().sorted(comparing(ExclusivePath::path, PATH_ORDER)).distinct().toList(); return exclusivePaths.values().stream().sorted(comparing(ExclusivePath::path, PATH_ORDER)).distinct().toList();
} }
static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) { static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
@ -97,7 +121,7 @@ public final class FileAccessTree {
) { ) {
List<String> updatedExclusivePaths = new ArrayList<>(); List<String> updatedExclusivePaths = new ArrayList<>();
for (ExclusivePath exclusivePath : exclusivePaths) { for (ExclusivePath exclusivePath : exclusivePaths) {
if (exclusivePath.componentName().equals(componentName) == false || exclusivePath.moduleName().equals(moduleName) == false) { if (exclusivePath.componentName().equals(componentName) == false || exclusivePath.moduleNames().contains(moduleName) == false) {
updatedExclusivePaths.add(exclusivePath.path()); updatedExclusivePaths.add(exclusivePath.path());
} }
} }

View file

@ -164,7 +164,8 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
public Stream<Path> resolveRelativePaths(PathLookup pathLookup) { public Stream<Path> resolveRelativePaths(PathLookup pathLookup) {
Stream<String> result = pathLookup.settingResolver() Stream<String> result = pathLookup.settingResolver()
.apply(setting) .apply(setting)
.filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false); .filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false)
.distinct();
return result.map(Path::of); return result.map(Path::of);
} }

View file

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem; import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList; import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList;
@ -386,7 +387,7 @@ public class FileAccessTreeTests extends ESTestCase {
original.moduleName(), original.moduleName(),
new FilesEntitlement(List.of(originalFileData.withPlatform(WINDOWS))) new FilesEntitlement(List.of(originalFileData.withPlatform(WINDOWS)))
); );
var originalExclusivePath = new ExclusivePath("component1", "module1", normalizePath(path("/a/b"))); var originalExclusivePath = new ExclusivePath("component1", Set.of("module1"), normalizePath(path("/a/b")));
// Some basic tests // Some basic tests
@ -406,27 +407,14 @@ public class FileAccessTreeTests extends ESTestCase {
var distinctEntitlements = List.of(original, differentComponent, differentModule, differentPath); var distinctEntitlements = List.of(original, differentComponent, differentModule, differentPath);
var distinctPaths = List.of( var distinctPaths = List.of(
originalExclusivePath, originalExclusivePath,
new ExclusivePath("component2", original.moduleName(), originalExclusivePath.path()), new ExclusivePath("component2", Set.of(original.moduleName()), originalExclusivePath.path()),
new ExclusivePath(original.componentName(), "module2", originalExclusivePath.path()), new ExclusivePath(original.componentName(), Set.of("module2"), originalExclusivePath.path()),
new ExclusivePath(original.componentName(), original.moduleName(), normalizePath(path("/c/d"))) new ExclusivePath(original.componentName(), Set.of(original.moduleName()), normalizePath(path("/c/d")))
); );
assertEquals( var iae = expectThrows(IllegalArgumentException.class, () -> buildExclusivePathList(distinctEntitlements, TEST_PATH_LOOKUP));
"Distinct elements should not be combined", assertThat(
distinctPaths, iae.getMessage(),
buildExclusivePathList(distinctEntitlements, TEST_PATH_LOOKUP) equalTo("Path [/a/b] is already exclusive to [component1][module1], cannot add exclusive access for [component2][module1]")
);
// Do merge things we should
List<ExclusiveFileEntitlement> interleavedEntitlements = new ArrayList<>();
distinctEntitlements.forEach(e -> {
interleavedEntitlements.add(e);
interleavedEntitlements.add(original);
});
assertEquals(
"Identical elements should be combined wherever they are in the list",
distinctPaths,
buildExclusivePathList(interleavedEntitlements, TEST_PATH_LOOKUP)
); );
var equivalentEntitlements = List.of(original, differentMode, differentPlatform); var equivalentEntitlements = List.of(original, differentMode, differentPlatform);
@ -486,7 +474,7 @@ public class FileAccessTreeTests extends ESTestCase {
static List<ExclusivePath> exclusivePaths(String componentName, String moduleName, String... paths) { static List<ExclusivePath> exclusivePaths(String componentName, String moduleName, String... paths) {
List<ExclusivePath> exclusivePaths = new ArrayList<>(); List<ExclusivePath> exclusivePaths = new ArrayList<>();
for (String path : paths) { for (String path : paths) {
exclusivePaths.add(new ExclusivePath(componentName, moduleName, normalizePath(path(path)))); exclusivePaths.add(new ExclusivePath(componentName, Set.of(moduleName), normalizePath(path(path))));
} }
return exclusivePaths; return exclusivePaths;
} }

View file

@ -38,6 +38,7 @@ import static java.util.Map.entry;
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.SERVER_COMPONENT_NAME; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.SERVER_COMPONENT_NAME;
import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.Matchers.sameInstance;
@ -444,9 +445,9 @@ public class PolicyManagerTests extends ESTestCase {
} }
public void testFilesEntitlementsWithExclusive() { public void testFilesEntitlementsWithExclusive() {
var baseTestPath = Path.of("/tmp").toAbsolutePath(); var baseTestPath = Path.of("/base").toAbsolutePath();
var testPath1 = Path.of("/tmp/test").toAbsolutePath(); var testPath1 = Path.of("/base/test").toAbsolutePath();
var testPath2 = Path.of("/tmp/test/foo").toAbsolutePath(); var testPath2 = Path.of("/base/test/foo").toAbsolutePath();
var iae = expectThrows( var iae = expectThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> new PolicyManager( () -> new PolicyManager(
@ -458,7 +459,7 @@ public class PolicyManagerTests extends ESTestCase {
"test", "test",
List.of( List.of(
new Scope( new Scope(
"test", "test.module1",
List.of( List.of(
new FilesEntitlement( new FilesEntitlement(
List.of(FilesEntitlement.FileData.ofPath(testPath1, FilesEntitlement.Mode.READ).withExclusive(true)) List.of(FilesEntitlement.FileData.ofPath(testPath1, FilesEntitlement.Mode.READ).withExclusive(true))
@ -472,7 +473,7 @@ public class PolicyManagerTests extends ESTestCase {
"test", "test",
List.of( List.of(
new Scope( new Scope(
"test", "test.module2",
List.of( List.of(
new FilesEntitlement( new FilesEntitlement(
List.of(FilesEntitlement.FileData.ofPath(testPath1, FilesEntitlement.Mode.READ).withExclusive(true)) List.of(FilesEntitlement.FileData.ofPath(testPath1, FilesEntitlement.Mode.READ).withExclusive(true))
@ -490,8 +491,13 @@ public class PolicyManagerTests extends ESTestCase {
Set.of() Set.of()
) )
); );
assertTrue(iae.getMessage().contains("duplicate/overlapping exclusive paths found in files entitlements:")); assertThat(
assertTrue(iae.getMessage().contains(Strings.format("[test] [%s]]", testPath1.toString()))); iae.getMessage(),
equalTo(
"Path [/base/test] is already exclusive to [plugin1][test.module1],"
+ " cannot add exclusive access for [plugin2][test.module2]"
)
);
iae = expectThrows( iae = expectThrows(
IllegalArgumentException.class, IllegalArgumentException.class,

View file

@ -10,11 +10,9 @@
package org.elasticsearch.common.ssl; package org.elasticsearch.common.ssl;
import org.elasticsearch.core.Tuple; import org.elasticsearch.core.Tuple;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -126,10 +124,8 @@ public final class PemKeyConfig implements SslKeyConfig {
throw new SslConfigException("could not load ssl private key file [" + path + "]"); throw new SslConfigException("could not load ssl private key file [" + path + "]");
} }
return privateKey; return privateKey;
} catch (AccessControlException e) { } catch (SecurityException e) {
throw SslFileUtil.accessControlFailure(KEY_FILE_TYPE, List.of(path), e, configBasePath); throw SslFileUtil.accessControlFailure(KEY_FILE_TYPE, List.of(path), e, configBasePath);
} catch (NotEntitledException e) {
throw SslFileUtil.notEntitledFailure(KEY_FILE_TYPE, List.of(path), e, configBasePath);
} catch (IOException e) { } catch (IOException e) {
throw SslFileUtil.ioException(KEY_FILE_TYPE, List.of(path), e); throw SslFileUtil.ioException(KEY_FILE_TYPE, List.of(path), e);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
@ -140,7 +136,7 @@ public final class PemKeyConfig implements SslKeyConfig {
private List<Certificate> getCertificates(Path path) { private List<Certificate> getCertificates(Path path) {
try { try {
return PemUtils.readCertificates(Collections.singleton(path)); return PemUtils.readCertificates(Collections.singleton(path));
} catch (AccessControlException e) { } catch (SecurityException e) {
throw SslFileUtil.accessControlFailure(CERT_FILE_TYPE, List.of(path), e, configBasePath); throw SslFileUtil.accessControlFailure(CERT_FILE_TYPE, List.of(path), e, configBasePath);
} catch (IOException e) { } catch (IOException e) {
throw SslFileUtil.ioException(CERT_FILE_TYPE, List.of(path), e); throw SslFileUtil.ioException(CERT_FILE_TYPE, List.of(path), e);

View file

@ -9,12 +9,9 @@
package org.elasticsearch.common.ssl; package org.elasticsearch.common.ssl;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.cert.Certificate; import java.security.cert.Certificate;
@ -99,10 +96,8 @@ public final class PemTrustConfig implements SslTrustConfig {
private List<Certificate> readCertificates(List<Path> paths) { private List<Certificate> readCertificates(List<Path> paths) {
try { try {
return PemUtils.readCertificates(paths); return PemUtils.readCertificates(paths);
} catch (AccessControlException e) { } catch (SecurityException e) {
throw SslFileUtil.accessControlFailure(CA_FILE_TYPE, paths, e, basePath); throw SslFileUtil.accessControlFailure(CA_FILE_TYPE, paths, e, basePath);
} catch (NotEntitledException e) {
throw SslFileUtil.notEntitledFailure(CA_FILE_TYPE, paths, e, basePath);
} catch (IOException e) { } catch (IOException e) {
throw SslFileUtil.ioException(CA_FILE_TYPE, paths, e); throw SslFileUtil.ioException(CA_FILE_TYPE, paths, e);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {

View file

@ -10,7 +10,6 @@
package org.elasticsearch.common.ssl; package org.elasticsearch.common.ssl;
import org.elasticsearch.core.CharArrays; import org.elasticsearch.core.CharArrays;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -19,7 +18,6 @@ import java.math.BigInteger;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyFactory; import java.security.KeyFactory;
@ -111,10 +109,8 @@ public final class PemUtils {
throw new SslConfigException("could not load ssl private key file [" + path + "]"); throw new SslConfigException("could not load ssl private key file [" + path + "]");
} }
return privateKey; return privateKey;
} catch (AccessControlException e) { } catch (SecurityException e) {
throw SslFileUtil.accessControlFailure("PEM private key", List.of(path), e, null); throw SslFileUtil.accessControlFailure("PEM private key", List.of(path), e, null);
} catch (NotEntitledException e) {
throw SslFileUtil.notEntitledFailure("PEM private key", List.of(path), e, null);
} catch (IOException e) { } catch (IOException e) {
throw SslFileUtil.ioException("PEM private key", List.of(path), e); throw SslFileUtil.ioException("PEM private key", List.of(path), e);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {

View file

@ -16,7 +16,6 @@ import java.io.IOException;
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.util.List; import java.util.List;
@ -84,7 +83,7 @@ final class SslFileUtil {
return innerAccessControlFailure(fileType, paths, cause, basePath); return innerAccessControlFailure(fileType, paths, cause, basePath);
} }
static SslConfigException accessControlFailure(String fileType, List<Path> paths, AccessControlException cause, Path basePath) { static SslConfigException accessControlFailure(String fileType, List<Path> paths, SecurityException cause, Path basePath) {
return innerAccessControlFailure(fileType, paths, cause, basePath); return innerAccessControlFailure(fileType, paths, cause, basePath);
} }

View file

@ -11,11 +11,9 @@ package org.elasticsearch.common.ssl;
import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.Tuple; import org.elasticsearch.core.Tuple;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
@ -167,10 +165,8 @@ public class StoreKeyConfig implements SslKeyConfig {
private KeyStore readKeyStore(Path path) { private KeyStore readKeyStore(Path path) {
try { try {
return KeyStoreUtil.readKeyStore(path, type, storePassword); return KeyStoreUtil.readKeyStore(path, type, storePassword);
} catch (AccessControlException e) { } catch (SecurityException e) {
throw SslFileUtil.accessControlFailure("[" + type + "] keystore", List.of(path), e, configBasePath); throw SslFileUtil.accessControlFailure("[" + type + "] keystore", List.of(path), e, configBasePath);
} catch (NotEntitledException e) {
throw SslFileUtil.notEntitledFailure("[" + type + "] keystore", List.of(path), e, configBasePath);
} catch (IOException e) { } catch (IOException e) {
throw SslFileUtil.ioException("[" + type + "] keystore", List.of(path), e); throw SslFileUtil.ioException("[" + type + "] keystore", List.of(path), e);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {

View file

@ -9,11 +9,8 @@
package org.elasticsearch.common.ssl; package org.elasticsearch.common.ssl;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -95,10 +92,8 @@ public final class StoreTrustConfig implements SslTrustConfig {
private KeyStore readKeyStore(Path path) { private KeyStore readKeyStore(Path path) {
try { try {
return KeyStoreUtil.readKeyStore(path, type, password); return KeyStoreUtil.readKeyStore(path, type, password);
} catch (AccessControlException e) { } catch (SecurityException e) {
throw SslFileUtil.accessControlFailure(fileTypeForException(), List.of(path), e, configBasePath); throw SslFileUtil.accessControlFailure(fileTypeForException(), List.of(path), e, configBasePath);
} catch (NotEntitledException e) {
throw SslFileUtil.notEntitledFailure(fileTypeForException(), List.of(path), e, configBasePath);
} catch (IOException e) { } catch (IOException e) {
throw SslFileUtil.ioException(fileTypeForException(), List.of(path), e, getAdditionalErrorDetails()); throw SslFileUtil.ioException(fileTypeForException(), List.of(path), e, getAdditionalErrorDetails());
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {

View file

@ -64,7 +64,6 @@ import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessControlException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -247,7 +246,7 @@ public class Analysis {
} catch (IOException ioe) { } catch (IOException ioe) {
String message = Strings.format("IOException while reading %s: %s", settingPath, path); String message = Strings.format("IOException while reading %s: %s", settingPath, path);
throw new IllegalArgumentException(message, ioe); throw new IllegalArgumentException(message, ioe);
} catch (AccessControlException ace) { } catch (SecurityException ace) {
throw new IllegalArgumentException(Strings.format("Access denied trying to read file %s: %s", settingPath, path), ace); throw new IllegalArgumentException(Strings.format("Access denied trying to read file %s: %s", settingPath, path), ace);
} }
} }

View file

@ -20,7 +20,6 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.security.AccessControlException;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -256,7 +255,7 @@ public class FileWatcher extends AbstractResourceWatcher<FileChangesListener> {
FileObserver child = new FileObserver(file); FileObserver child = new FileObserver(file);
child.init(initial); child.init(initial);
return child; return child;
} catch (AccessControlException e) { } catch (SecurityException e) {
// don't have permissions, use a placeholder // don't have permissions, use a placeholder
logger.debug(() -> Strings.format("Don't have permissions to watch path [%s]", file), e); logger.debug(() -> Strings.format("Don't have permissions to watch path [%s]", file), e);
return new DeniedObserver(file); return new DeniedObserver(file);

View file

@ -45,7 +45,6 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.security.AccessControlException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -259,7 +258,7 @@ public class StoreRecoveryTests extends ESTestCase {
BasicFileAttributes sourceAttr = Files.readAttributes(path.resolve("foo.bar"), BasicFileAttributes.class); BasicFileAttributes sourceAttr = Files.readAttributes(path.resolve("foo.bar"), BasicFileAttributes.class);
// we won't get here - no permission ;) // we won't get here - no permission ;)
return destAttr.fileKey() != null && destAttr.fileKey().equals(sourceAttr.fileKey()); return destAttr.fileKey() != null && destAttr.fileKey().equals(sourceAttr.fileKey());
} catch (AccessControlException ex) { } catch (SecurityException ex) {
return true; // if we run into that situation we know it's supported. return true; // if we run into that situation we know it's supported.
} catch (UnsupportedOperationException ex) { } catch (UnsupportedOperationException ex) {
return false; return false;

View file

@ -10,6 +10,7 @@ package org.elasticsearch.xpack.security;
import org.elasticsearch.watcher.FileWatcher; import org.elasticsearch.watcher.FileWatcher;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -34,6 +35,15 @@ public class PrivilegedFileWatcher extends FileWatcher {
super(path); super(path);
} }
public PrivilegedFileWatcher(Path path, boolean checkFileContents) {
super(path, checkFileContents);
}
@Override
protected InputStream newInputStream(Path path) throws IOException {
return Files.newInputStream(path);
}
@Override @Override
protected boolean fileExists(Path path) { protected boolean fileExists(Path path) {
return doPrivileged((PrivilegedAction<Boolean>) () -> Files.exists(path)); return doPrivileged((PrivilegedAction<Boolean>) () -> Files.exists(path));

View file

@ -20,6 +20,7 @@ import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.support.NoOpLogger; import org.elasticsearch.xpack.core.security.support.NoOpLogger;
import org.elasticsearch.xpack.core.security.support.Validation; import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.security.PrivilegedFileWatcher;
import org.elasticsearch.xpack.security.support.SecurityFiles; import org.elasticsearch.xpack.security.support.SecurityFiles;
import java.io.IOException; import java.io.IOException;
@ -57,7 +58,7 @@ public class FileUserRolesStore {
file = resolveFile(config.env()); file = resolveFile(config.env());
userRoles = parseFileLenient(file, logger); userRoles = parseFileLenient(file, logger);
listeners = new CopyOnWriteArrayList<>(Collections.singletonList(listener)); listeners = new CopyOnWriteArrayList<>(Collections.singletonList(listener));
FileWatcher watcher = new FileWatcher(file.getParent()); FileWatcher watcher = new PrivilegedFileWatcher(file.getParent());
watcher.addListener(new FileListener()); watcher.addListener(new FileListener());
try { try {
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);

View file

@ -24,6 +24,7 @@ import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource; import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.support.NoOpLogger; import org.elasticsearch.xpack.core.security.support.NoOpLogger;
import org.elasticsearch.xpack.security.PrivilegedFileWatcher;
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId; import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry; import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
import org.elasticsearch.xpack.security.support.FileLineParser; import org.elasticsearch.xpack.security.support.FileLineParser;
@ -59,7 +60,7 @@ public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStor
super(env.settings(), threadPool); super(env.settings(), threadPool);
this.clusterService = clusterService; this.clusterService = clusterService;
file = resolveFile(env); file = resolveFile(env);
FileWatcher watcher = new FileWatcher(file.getParent()); FileWatcher watcher = new PrivilegedFileWatcher(file.getParent());
watcher.addListener(new FileReloadListener(file, this::tryReload)); watcher.addListener(new FileReloadListener(file, this::tryReload));
try { try {
resourceWatcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); resourceWatcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);

View file

@ -37,6 +37,7 @@ import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
import org.elasticsearch.xpack.core.security.authz.support.DLSRoleQueryValidator; import org.elasticsearch.xpack.core.security.authz.support.DLSRoleQueryValidator;
import org.elasticsearch.xpack.core.security.support.NoOpLogger; import org.elasticsearch.xpack.core.security.support.NoOpLogger;
import org.elasticsearch.xpack.core.security.support.Validation; import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.security.PrivilegedFileWatcher;
import org.elasticsearch.xpack.security.authz.FileRoleValidator; import org.elasticsearch.xpack.security.authz.FileRoleValidator;
import java.io.IOException; import java.io.IOException;
@ -110,7 +111,7 @@ public class FileRolesStore implements BiConsumer<Set<String>, ActionListener<Ro
} }
this.licenseState = licenseState; this.licenseState = licenseState;
this.xContentRegistry = xContentRegistry; this.xContentRegistry = xContentRegistry;
FileWatcher watcher = new FileWatcher(file.getParent()); FileWatcher watcher = new PrivilegedFileWatcher(file.getParent());
watcher.addListener(new FileListener()); watcher.addListener(new FileListener());
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
permissions = parseFile(file, logger, settings, licenseState, xContentRegistry, roleValidator); permissions = parseFile(file, logger, settings, licenseState, xContentRegistry, roleValidator);

View file

@ -31,6 +31,7 @@ import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings; import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
import org.elasticsearch.xpack.core.security.authc.jwt.JwtRealmSettings; import org.elasticsearch.xpack.core.security.authc.jwt.JwtRealmSettings;
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
import org.elasticsearch.xpack.security.PrivilegedFileWatcher;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import java.io.IOException; import java.io.IOException;
@ -59,7 +60,7 @@ public class FileOperatorUsersStore {
public FileOperatorUsersStore(Environment env, ResourceWatcherService watcherService) { public FileOperatorUsersStore(Environment env, ResourceWatcherService watcherService) {
this.file = XPackPlugin.resolveConfigFile(env, "operator_users.yml"); this.file = XPackPlugin.resolveConfigFile(env, "operator_users.yml");
this.operatorUsersDescriptor = parseFile(this.file, logger); this.operatorUsersDescriptor = parseFile(this.file, logger);
FileWatcher watcher = new FileWatcher(file.getParent(), true); FileWatcher watcher = new PrivilegedFileWatcher(file.getParent(), true);
watcher.addListener(new FileOperatorUsersStore.FileListener()); watcher.addListener(new FileOperatorUsersStore.FileListener());
try { try {
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);

View file

@ -4,6 +4,35 @@ org.elasticsearch.security:
- relative_path: "" - relative_path: ""
relative_to: config relative_to: config
mode: read mode: read
- relative_path: users
relative_to: config
mode: read
exclusive: true
- relative_path: x-pack/users
relative_to: config
mode: read
exclusive: true
- path_setting: xpack.security.authc.realms.ldap.*.files.role_mapping
basedir_if_relative: config
mode: read
exclusive: true
- path_setting: xpack.security.authc.realms.pki.*.files.role_mapping
basedir_if_relative: config
mode: read
exclusive: true
- path_setting: xpack.security.authc.realms.kerberos.*.keytab.path
basedir_if_relative: config
mode: read
exclusive: true
- path_setting: xpack.security.authc.realms.jwt.*.pkc_jwkset_path
basedir_if_relative: config
mode: read
exclusive: true
- path_setting: xpack.security.authc.realms.saml.*.idp.metadata.path
basedir_if_relative: config
mode: read
exclusive: true
io.netty.transport: io.netty.transport:
- manage_threads - manage_threads
- inbound_network - inbound_network
@ -25,18 +54,7 @@ org.opensaml.xmlsec.impl:
- org.apache.xml.security.ignoreLineBreaks - org.apache.xml.security.ignoreLineBreaks
org.opensaml.saml.impl: org.opensaml.saml.impl:
- files: - files:
- relative_path: idp-docs-metadata.xml - path_setting: xpack.security.authc.realms.saml.*.idp.metadata.path
relative_to: config basedir_if_relative: config
mode: read
- relative_path: idp-metadata.xml
relative_to: config
mode: read
- relative_path: saml-metadata.xml
relative_to: config
mode: read
- relative_path: metadata.xml
relative_to: config
mode: read
- relative_path: "saml/"
relative_to: config
mode: read mode: read
exclusive: true