Skip to content

Commit 7e3d85a

Browse files
authored
Handle NotEntitledException in SSL file utils (#123491) (#123603)
SSL file utils currently only handle security manager access control exceptions around file read checks. This PR extends these to support entitlement checks as well. There is no easy way to unit test this since we can't run unit tests with entitlements enabled (for now). The PR includes a REST test instead. Relates: #121960
1 parent af3c93a commit 7e3d85a

File tree

11 files changed

+109
-1
lines changed

11 files changed

+109
-1
lines changed

libs/ssl-config/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ apply plugin: "elasticsearch.publish"
1010

1111
dependencies {
1212
api project(':libs:core')
13+
api project(':libs:entitlement')
1314

1415
testImplementation(project(":test:framework")) {
1516
exclude group: 'org.elasticsearch', module: 'ssl-config'

libs/ssl-config/src/main/java/module-info.java

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
module org.elasticsearch.sslconfig {
1111
requires org.elasticsearch.base;
12+
requires org.elasticsearch.entitlement;
1213

1314
exports org.elasticsearch.common.ssl;
1415
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.elasticsearch.common.ssl;
1111

1212
import org.elasticsearch.core.Tuple;
13+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1314

1415
import java.io.IOException;
1516
import java.nio.file.Path;
@@ -127,6 +128,8 @@ private PrivateKey getPrivateKey(Path path) {
127128
return privateKey;
128129
} catch (AccessControlException e) {
129130
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);
130133
} catch (IOException e) {
131134
throw SslFileUtil.ioException(KEY_FILE_TYPE, List.of(path), e);
132135
} catch (GeneralSecurityException e) {

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

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
package org.elasticsearch.common.ssl;
1111

12+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
13+
1214
import java.io.IOException;
1315
import java.io.InputStream;
1416
import java.nio.file.Path;
@@ -99,6 +101,8 @@ private List<Certificate> readCertificates(List<Path> paths) {
99101
return PemUtils.readCertificates(paths);
100102
} catch (AccessControlException e) {
101103
throw SslFileUtil.accessControlFailure(CA_FILE_TYPE, paths, e, basePath);
104+
} catch (NotEntitledException e) {
105+
throw SslFileUtil.notEntitledFailure(CA_FILE_TYPE, paths, e, basePath);
102106
} catch (IOException e) {
103107
throw SslFileUtil.ioException(CA_FILE_TYPE, paths, e);
104108
} catch (GeneralSecurityException e) {

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

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.elasticsearch.common.ssl;
1111

1212
import org.elasticsearch.core.CharArrays;
13+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1314

1415
import java.io.BufferedReader;
1516
import java.io.IOException;
@@ -112,6 +113,8 @@ public static PrivateKey readPrivateKey(Path path, Supplier<char[]> passwordSupp
112113
return privateKey;
113114
} catch (AccessControlException e) {
114115
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);
115118
} catch (IOException e) {
116119
throw SslFileUtil.ioException("PEM private key", List.of(path), e);
117120
} catch (GeneralSecurityException e) {

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

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
package org.elasticsearch.common.ssl;
1111

12+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
13+
1214
import java.io.FileNotFoundException;
1315
import java.io.IOException;
1416
import java.nio.file.AccessDeniedException;
@@ -78,7 +80,15 @@ static SslConfigException accessDenied(String fileType, List<Path> paths, Access
7880
return new SslConfigException(message, cause);
7981
}
8082

83+
static SslConfigException notEntitledFailure(String fileType, List<Path> paths, NotEntitledException cause, Path basePath) {
84+
return innerAccessControlFailure(fileType, paths, cause, basePath);
85+
}
86+
8187
static SslConfigException accessControlFailure(String fileType, List<Path> paths, AccessControlException cause, Path basePath) {
88+
return innerAccessControlFailure(fileType, paths, cause, basePath);
89+
}
90+
91+
private static SslConfigException innerAccessControlFailure(String fileType, List<Path> paths, Exception cause, Path basePath) {
8292
String message = "cannot read configured " + fileType + " [" + pathsToString(paths) + "] because ";
8393
if (paths.size() == 1) {
8494
message += "access to read the file is blocked";

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

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.elasticsearch.core.Nullable;
1313
import org.elasticsearch.core.Tuple;
14+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1415

1516
import java.io.IOException;
1617
import java.nio.file.Path;
@@ -168,6 +169,8 @@ private KeyStore readKeyStore(Path path) {
168169
return KeyStoreUtil.readKeyStore(path, type, storePassword);
169170
} catch (AccessControlException e) {
170171
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);
171174
} catch (IOException e) {
172175
throw SslFileUtil.ioException("[" + type + "] keystore", List.of(path), e);
173176
} catch (GeneralSecurityException e) {

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

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
package org.elasticsearch.common.ssl;
1111

12+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
13+
1214
import java.io.IOException;
1315
import java.nio.file.Path;
1416
import java.security.AccessControlException;
@@ -95,6 +97,8 @@ private KeyStore readKeyStore(Path path) {
9597
return KeyStoreUtil.readKeyStore(path, type, password);
9698
} catch (AccessControlException e) {
9799
throw SslFileUtil.accessControlFailure(fileTypeForException(), List.of(path), e, configBasePath);
100+
} catch (NotEntitledException e) {
101+
throw SslFileUtil.notEntitledFailure(fileTypeForException(), List.of(path), e, configBasePath);
98102
} catch (IOException e) {
99103
throw SslFileUtil.ioException(fileTypeForException(), List.of(path), e, getAdditionalErrorDetails());
100104
} catch (GeneralSecurityException e) {

x-pack/plugin/core/src/main/java/module-info.java

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
module org.elasticsearch.xcore {
99
requires org.elasticsearch.cli;
10+
requires org.elasticsearch.entitlement;
1011
requires org.elasticsearch.base;
1112
requires org.elasticsearch.grok;
1213
requires org.elasticsearch.server;

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloader.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.action.support.PlainActionFuture;
1313
import org.elasticsearch.common.ssl.SslConfiguration;
1414
import org.elasticsearch.core.TimeValue;
15+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
1516
import org.elasticsearch.watcher.FileChangesListener;
1617
import org.elasticsearch.watcher.FileWatcher;
1718
import org.elasticsearch.watcher.ResourceWatcherService;
@@ -109,7 +110,7 @@ private static void startWatching(
109110
fileWatcher.addListener(changeListener);
110111
try {
111112
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
112-
} catch (IOException | AccessControlException e) {
113+
} catch (IOException | AccessControlException | NotEntitledException e) {
113114
logger.error("failed to start watching directory [{}] for ssl configurations [{}] - {}", path, configurations, e);
114115
}
115116
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.security.ssl;
9+
10+
import org.elasticsearch.common.io.Streams;
11+
import org.elasticsearch.common.settings.SecureString;
12+
import org.elasticsearch.common.settings.Settings;
13+
import org.elasticsearch.common.util.concurrent.ThreadContext;
14+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
15+
import org.elasticsearch.test.cluster.LogType;
16+
import org.elasticsearch.test.cluster.MutableSettingsProvider;
17+
import org.elasticsearch.test.rest.ESRestTestCase;
18+
import org.elasticsearch.xpack.security.SecurityOnTrialLicenseRestTestCase;
19+
import org.junit.ClassRule;
20+
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.util.concurrent.atomic.AtomicBoolean;
24+
25+
import static org.hamcrest.Matchers.is;
26+
27+
public class SslEntitlementRestIT extends ESRestTestCase {
28+
29+
private static final MutableSettingsProvider settingsProvider = new MutableSettingsProvider();
30+
31+
@ClassRule
32+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
33+
.apply(SecurityOnTrialLicenseRestTestCase.commonTrialSecurityClusterConfig)
34+
.settings(settingsProvider)
35+
.systemProperty("es.entitlements.enabled", "true")
36+
.build();
37+
38+
@Override
39+
protected String getTestRestCluster() {
40+
return cluster.getHttpAddresses();
41+
}
42+
43+
public void testSslEntitlementInaccessiblePath() throws IOException {
44+
settingsProvider.put("xpack.security.transport.ssl.key", "/bad/path/transport.key");
45+
settingsProvider.put("xpack.security.transport.ssl.certificate", "/bad/path/transport.crt");
46+
expectThrows(Exception.class, () -> cluster.restart(false));
47+
AtomicBoolean found = new AtomicBoolean(false);
48+
for (int i = 0; i < cluster.getNumNodes(); i++) {
49+
try (InputStream log = cluster.getNodeLog(i, LogType.SERVER)) {
50+
Streams.readAllLines(log, line -> {
51+
if (line.contains("failed to load SSL configuration") && line.contains("because access to read the file is blocked")) {
52+
found.set(true);
53+
}
54+
});
55+
}
56+
}
57+
assertThat("cluster logs did not include events of blocked file access", found.get(), is(true));
58+
}
59+
60+
@Override
61+
protected Settings restAdminSettings() {
62+
String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray()));
63+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
64+
}
65+
66+
@Override
67+
protected Settings restClientSettings() {
68+
String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray()));
69+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
70+
}
71+
72+
@Override
73+
protected boolean preserveClusterUponCompletion() {
74+
// as the cluster is dead its state can not be wiped successfully so we have to bypass wiping the cluster
75+
return true;
76+
}
77+
}

0 commit comments

Comments
 (0)