Skip to content

Commit 0194727

Browse files
rjernstgeorgewallace
authored andcommitted
Add exclusive access files for security module (elastic#123676)
This commit fills out missing entitlements for the security module. Specifically they are config files which require exclusive access.
1 parent 9fead41 commit 0194727

File tree

19 files changed

+115
-90
lines changed

19 files changed

+115
-90
lines changed

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@
2323
import java.nio.file.Paths;
2424
import java.util.ArrayList;
2525
import java.util.Arrays;
26+
import java.util.HashSet;
27+
import java.util.LinkedHashMap;
2628
import java.util.List;
29+
import java.util.Map;
2730
import java.util.Objects;
31+
import java.util.Set;
2832
import java.util.function.BiConsumer;
2933

3034
import static java.util.Comparator.comparing;
@@ -42,27 +46,47 @@ record ExclusiveFileEntitlement(String componentName, String moduleName, FilesEn
4246
/**
4347
* An intermediary structure to help globally validate exclusive paths, and then build exclusive paths for individual modules.
4448
*/
45-
record ExclusivePath(String componentName, String moduleName, String path) {
49+
record ExclusivePath(String componentName, Set<String> moduleNames, String path) {
4650

4751
@Override
4852
public String toString() {
49-
return "[[" + componentName + "] [" + moduleName + "] [" + path + "]]";
53+
return "[[" + componentName + "] " + moduleNames + " [" + path + "]]";
5054
}
5155
}
5256

5357
static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement> exclusiveFileEntitlements, PathLookup pathLookup) {
54-
List<ExclusivePath> exclusivePaths = new ArrayList<>();
58+
Map<String, ExclusivePath> exclusivePaths = new LinkedHashMap<>();
5559
for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements) {
5660
for (FilesEntitlement.FileData fd : efe.filesEntitlement().filesData()) {
5761
if (fd.exclusive()) {
5862
List<Path> paths = fd.resolvePaths(pathLookup).toList();
5963
for (Path path : paths) {
60-
exclusivePaths.add(new ExclusivePath(efe.componentName(), efe.moduleName(), normalizePath(path)));
64+
String normalizedPath = normalizePath(path);
65+
var exclusivePath = exclusivePaths.computeIfAbsent(
66+
normalizedPath,
67+
k -> new ExclusivePath(efe.componentName(), new HashSet<>(), normalizedPath)
68+
);
69+
if (exclusivePath.componentName().equals(efe.componentName()) == false) {
70+
throw new IllegalArgumentException(
71+
"Path ["
72+
+ normalizedPath
73+
+ "] is already exclusive to ["
74+
+ exclusivePath.componentName()
75+
+ "]"
76+
+ exclusivePath.moduleNames
77+
+ ", cannot add exclusive access for ["
78+
+ efe.componentName()
79+
+ "]["
80+
+ efe.moduleName
81+
+ "]"
82+
);
83+
}
84+
exclusivePath.moduleNames.add(efe.moduleName());
6185
}
6286
}
6387
}
6488
}
65-
return exclusivePaths.stream().sorted(comparing(ExclusivePath::path, PATH_ORDER)).distinct().toList();
89+
return exclusivePaths.values().stream().sorted(comparing(ExclusivePath::path, PATH_ORDER)).distinct().toList();
6690
}
6791

6892
static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
@@ -97,7 +121,7 @@ private FileAccessTree(
97121
) {
98122
List<String> updatedExclusivePaths = new ArrayList<>();
99123
for (ExclusivePath exclusivePath : exclusivePaths) {
100-
if (exclusivePath.componentName().equals(componentName) == false || exclusivePath.moduleName().equals(moduleName) == false) {
124+
if (exclusivePath.componentName().equals(componentName) == false || exclusivePath.moduleNames().contains(moduleName) == false) {
101125
updatedExclusivePaths.add(exclusivePath.path());
102126
}
103127
}

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ public PathSettingFileData withExclusive(boolean exclusive) {
164164
public Stream<Path> resolveRelativePaths(PathLookup pathLookup) {
165165
Stream<String> result = pathLookup.settingResolver()
166166
.apply(setting)
167-
.filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false);
167+
.filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false)
168+
.distinct();
168169
return result.map(Path::of);
169170
}
170171

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.HashMap;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.Set;
2930

3031
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
3132
import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList;
@@ -386,7 +387,7 @@ public void testDuplicateExclusivePaths() {
386387
original.moduleName(),
387388
new FilesEntitlement(List.of(originalFileData.withPlatform(WINDOWS)))
388389
);
389-
var originalExclusivePath = new ExclusivePath("component1", "module1", normalizePath(path("/a/b")));
390+
var originalExclusivePath = new ExclusivePath("component1", Set.of("module1"), normalizePath(path("/a/b")));
390391

391392
// Some basic tests
392393

@@ -406,27 +407,14 @@ public void testDuplicateExclusivePaths() {
406407
var distinctEntitlements = List.of(original, differentComponent, differentModule, differentPath);
407408
var distinctPaths = List.of(
408409
originalExclusivePath,
409-
new ExclusivePath("component2", original.moduleName(), originalExclusivePath.path()),
410-
new ExclusivePath(original.componentName(), "module2", originalExclusivePath.path()),
411-
new ExclusivePath(original.componentName(), original.moduleName(), normalizePath(path("/c/d")))
410+
new ExclusivePath("component2", Set.of(original.moduleName()), originalExclusivePath.path()),
411+
new ExclusivePath(original.componentName(), Set.of("module2"), originalExclusivePath.path()),
412+
new ExclusivePath(original.componentName(), Set.of(original.moduleName()), normalizePath(path("/c/d")))
412413
);
413-
assertEquals(
414-
"Distinct elements should not be combined",
415-
distinctPaths,
416-
buildExclusivePathList(distinctEntitlements, TEST_PATH_LOOKUP)
417-
);
418-
419-
// Do merge things we should
420-
421-
List<ExclusiveFileEntitlement> interleavedEntitlements = new ArrayList<>();
422-
distinctEntitlements.forEach(e -> {
423-
interleavedEntitlements.add(e);
424-
interleavedEntitlements.add(original);
425-
});
426-
assertEquals(
427-
"Identical elements should be combined wherever they are in the list",
428-
distinctPaths,
429-
buildExclusivePathList(interleavedEntitlements, TEST_PATH_LOOKUP)
414+
var iae = expectThrows(IllegalArgumentException.class, () -> buildExclusivePathList(distinctEntitlements, TEST_PATH_LOOKUP));
415+
assertThat(
416+
iae.getMessage(),
417+
equalTo("Path [/a/b] is already exclusive to [component1][module1], cannot add exclusive access for [component2][module1]")
430418
);
431419

432420
var equivalentEntitlements = List.of(original, differentMode, differentPlatform);
@@ -486,7 +474,7 @@ static FilesEntitlement entitlement(Map<String, String> value) {
486474
static List<ExclusivePath> exclusivePaths(String componentName, String moduleName, String... paths) {
487475
List<ExclusivePath> exclusivePaths = new ArrayList<>();
488476
for (String path : paths) {
489-
exclusivePaths.add(new ExclusivePath(componentName, moduleName, normalizePath(path(path))));
477+
exclusivePaths.add(new ExclusivePath(componentName, Set.of(moduleName), normalizePath(path(path))));
490478
}
491479
return exclusivePaths;
492480
}

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
3939
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.SERVER_COMPONENT_NAME;
4040
import static org.hamcrest.Matchers.aMapWithSize;
41+
import static org.hamcrest.Matchers.equalTo;
4142
import static org.hamcrest.Matchers.is;
4243
import static org.hamcrest.Matchers.sameInstance;
4344

@@ -444,9 +445,9 @@ public void testDuplicateEntitlements() {
444445
}
445446

446447
public void testFilesEntitlementsWithExclusive() {
447-
var baseTestPath = Path.of("/tmp").toAbsolutePath();
448-
var testPath1 = Path.of("/tmp/test").toAbsolutePath();
449-
var testPath2 = Path.of("/tmp/test/foo").toAbsolutePath();
448+
var baseTestPath = Path.of("/base").toAbsolutePath();
449+
var testPath1 = Path.of("/base/test").toAbsolutePath();
450+
var testPath2 = Path.of("/base/test/foo").toAbsolutePath();
450451
var iae = expectThrows(
451452
IllegalArgumentException.class,
452453
() -> new PolicyManager(
@@ -458,7 +459,7 @@ public void testFilesEntitlementsWithExclusive() {
458459
"test",
459460
List.of(
460461
new Scope(
461-
"test",
462+
"test.module1",
462463
List.of(
463464
new FilesEntitlement(
464465
List.of(FilesEntitlement.FileData.ofPath(testPath1, FilesEntitlement.Mode.READ).withExclusive(true))
@@ -472,7 +473,7 @@ public void testFilesEntitlementsWithExclusive() {
472473
"test",
473474
List.of(
474475
new Scope(
475-
"test",
476+
"test.module2",
476477
List.of(
477478
new FilesEntitlement(
478479
List.of(FilesEntitlement.FileData.ofPath(testPath1, FilesEntitlement.Mode.READ).withExclusive(true))
@@ -490,8 +491,13 @@ public void testFilesEntitlementsWithExclusive() {
490491
Set.of()
491492
)
492493
);
493-
assertTrue(iae.getMessage().contains("duplicate/overlapping exclusive paths found in files entitlements:"));
494-
assertTrue(iae.getMessage().contains(Strings.format("[test] [%s]]", testPath1.toString())));
494+
assertThat(
495+
iae.getMessage(),
496+
equalTo(
497+
"Path [/base/test] is already exclusive to [plugin1][test.module1],"
498+
+ " cannot add exclusive access for [plugin2][test.module2]"
499+
)
500+
);
495501

496502
iae = expectThrows(
497503
IllegalArgumentException.class,

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@
1010
package org.elasticsearch.common.ssl;
1111

1212
import org.elasticsearch.core.Tuple;
13-
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1413

1514
import java.io.IOException;
1615
import java.nio.file.Path;
17-
import java.security.AccessControlException;
1816
import java.security.GeneralSecurityException;
1917
import java.security.KeyStore;
2018
import java.security.PrivateKey;
@@ -126,10 +124,8 @@ private PrivateKey getPrivateKey(Path path) {
126124
throw new SslConfigException("could not load ssl private key file [" + path + "]");
127125
}
128126
return privateKey;
129-
} catch (AccessControlException e) {
127+
} catch (SecurityException e) {
130128
throw SslFileUtil.accessControlFailure(KEY_FILE_TYPE, List.of(path), e, configBasePath);
131-
} catch (NotEntitledException e) {
132-
throw SslFileUtil.notEntitledFailure(KEY_FILE_TYPE, List.of(path), e, configBasePath);
133129
} catch (IOException e) {
134130
throw SslFileUtil.ioException(KEY_FILE_TYPE, List.of(path), e);
135131
} catch (GeneralSecurityException e) {
@@ -140,7 +136,7 @@ private PrivateKey getPrivateKey(Path path) {
140136
private List<Certificate> getCertificates(Path path) {
141137
try {
142138
return PemUtils.readCertificates(Collections.singleton(path));
143-
} catch (AccessControlException e) {
139+
} catch (SecurityException e) {
144140
throw SslFileUtil.accessControlFailure(CERT_FILE_TYPE, List.of(path), e, configBasePath);
145141
} catch (IOException e) {
146142
throw SslFileUtil.ioException(CERT_FILE_TYPE, List.of(path), e);

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemTrustConfig.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@
99

1010
package org.elasticsearch.common.ssl;
1111

12-
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
13-
1412
import java.io.IOException;
1513
import java.io.InputStream;
1614
import java.nio.file.Path;
17-
import java.security.AccessControlException;
1815
import java.security.GeneralSecurityException;
1916
import java.security.KeyStore;
2017
import java.security.cert.Certificate;
@@ -99,10 +96,8 @@ private Path resolveFile(String other) {
9996
private List<Certificate> readCertificates(List<Path> paths) {
10097
try {
10198
return PemUtils.readCertificates(paths);
102-
} catch (AccessControlException e) {
99+
} catch (SecurityException e) {
103100
throw SslFileUtil.accessControlFailure(CA_FILE_TYPE, paths, e, basePath);
104-
} catch (NotEntitledException e) {
105-
throw SslFileUtil.notEntitledFailure(CA_FILE_TYPE, paths, e, basePath);
106101
} catch (IOException e) {
107102
throw SslFileUtil.ioException(CA_FILE_TYPE, paths, e);
108103
} catch (GeneralSecurityException e) {

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package org.elasticsearch.common.ssl;
1111

1212
import org.elasticsearch.core.CharArrays;
13-
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1413

1514
import java.io.BufferedReader;
1615
import java.io.IOException;
@@ -19,7 +18,6 @@
1918
import java.nio.charset.StandardCharsets;
2019
import java.nio.file.Files;
2120
import java.nio.file.Path;
22-
import java.security.AccessControlException;
2321
import java.security.AlgorithmParameters;
2422
import java.security.GeneralSecurityException;
2523
import java.security.KeyFactory;
@@ -111,10 +109,8 @@ public static PrivateKey readPrivateKey(Path path, Supplier<char[]> passwordSupp
111109
throw new SslConfigException("could not load ssl private key file [" + path + "]");
112110
}
113111
return privateKey;
114-
} catch (AccessControlException e) {
112+
} catch (SecurityException e) {
115113
throw SslFileUtil.accessControlFailure("PEM private key", List.of(path), e, null);
116-
} catch (NotEntitledException e) {
117-
throw SslFileUtil.notEntitledFailure("PEM private key", List.of(path), e, null);
118114
} catch (IOException e) {
119115
throw SslFileUtil.ioException("PEM private key", List.of(path), e);
120116
} catch (GeneralSecurityException e) {

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslFileUtil.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import java.nio.file.AccessDeniedException;
1717
import java.nio.file.NoSuchFileException;
1818
import java.nio.file.Path;
19-
import java.security.AccessControlException;
2019
import java.security.GeneralSecurityException;
2120
import java.security.UnrecoverableKeyException;
2221
import java.util.List;
@@ -84,7 +83,7 @@ static SslConfigException notEntitledFailure(String fileType, List<Path> paths,
8483
return innerAccessControlFailure(fileType, paths, cause, basePath);
8584
}
8685

87-
static SslConfigException accessControlFailure(String fileType, List<Path> paths, AccessControlException cause, Path basePath) {
86+
static SslConfigException accessControlFailure(String fileType, List<Path> paths, SecurityException cause, Path basePath) {
8887
return innerAccessControlFailure(fileType, paths, cause, basePath);
8988
}
9089

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111

1212
import org.elasticsearch.core.Nullable;
1313
import org.elasticsearch.core.Tuple;
14-
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1514

1615
import java.io.IOException;
1716
import java.nio.file.Path;
18-
import java.security.AccessControlException;
1917
import java.security.GeneralSecurityException;
2018
import java.security.KeyStore;
2119
import java.security.KeyStoreException;
@@ -167,10 +165,8 @@ private KeyStore processKeyStore(KeyStore keyStore) {
167165
private KeyStore readKeyStore(Path path) {
168166
try {
169167
return KeyStoreUtil.readKeyStore(path, type, storePassword);
170-
} catch (AccessControlException e) {
168+
} catch (SecurityException e) {
171169
throw SslFileUtil.accessControlFailure("[" + type + "] keystore", List.of(path), e, configBasePath);
172-
} catch (NotEntitledException e) {
173-
throw SslFileUtil.notEntitledFailure("[" + type + "] keystore", List.of(path), e, configBasePath);
174170
} catch (IOException e) {
175171
throw SslFileUtil.ioException("[" + type + "] keystore", List.of(path), e);
176172
} catch (GeneralSecurityException e) {

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreTrustConfig.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@
99

1010
package org.elasticsearch.common.ssl;
1111

12-
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
13-
1412
import java.io.IOException;
1513
import java.nio.file.Path;
16-
import java.security.AccessControlException;
1714
import java.security.GeneralSecurityException;
1815
import java.security.KeyStore;
1916
import java.security.cert.X509Certificate;
@@ -95,10 +92,8 @@ public X509ExtendedTrustManager createTrustManager() {
9592
private KeyStore readKeyStore(Path path) {
9693
try {
9794
return KeyStoreUtil.readKeyStore(path, type, password);
98-
} catch (AccessControlException e) {
95+
} catch (SecurityException e) {
9996
throw SslFileUtil.accessControlFailure(fileTypeForException(), List.of(path), e, configBasePath);
100-
} catch (NotEntitledException e) {
101-
throw SslFileUtil.notEntitledFailure(fileTypeForException(), List.of(path), e, configBasePath);
10297
} catch (IOException e) {
10398
throw SslFileUtil.ioException(fileTypeForException(), List.of(path), e, getAdditionalErrorDetails());
10499
} catch (GeneralSecurityException e) {

0 commit comments

Comments
 (0)