Skip to content

Commit 4ed5ee9

Browse files
tvernumjakelandis
andauthored
[8.5] Support SAN/dnsName for restricted trust (#92077)
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: `*.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"]` To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects. Co-authored-by: Jake Landis <[email protected]>
1 parent 4a9116a commit 4ed5ee9

File tree

271 files changed

+6181
-1761
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

271 files changed

+6181
-1761
lines changed

docs/changelog/91946.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 91946
2+
summary: Support SAN/dnsName for restricted trust
3+
area: TLS
4+
type: enhancement
5+
issues: []

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,10 @@ protected Path resolvePath(String settingKey, Path basePath) {
346346
return resolveSetting(settingKey, basePath::resolve, null);
347347
}
348348

349+
protected List<String> resolveList(String settingKey, List<String> defaultList) {
350+
return resolveListSetting(settingKey, Function.identity(), defaultList);
351+
}
352+
349353
private String expandSettingKey(String key) {
350354
return settingPrefix + key;
351355
}

x-pack/plugin/core/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ tasks.named("dependencyLicenses").configure {
2626
mapping from: /commons-.*/, to: 'commons' // pulled in by rest client
2727
}
2828

29+
configurations {
30+
signedCerts
31+
rootCert
32+
}
33+
2934
dependencies {
3035
compileOnly project(":server")
3136
api project(':libs:elasticsearch-grok')
@@ -59,6 +64,8 @@ dependencies {
5964

6065
yamlRestTestImplementation project(':x-pack:plugin:core')
6166
javaRestTestImplementation(testArtifact(project(xpackModule('core'))))
67+
signedCerts fileTree("src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/ca-signed")
68+
rootCert files("src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/ca.crt")
6269
}
6370

6471
ext.expansions = [

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Collection;
1818
import java.util.List;
1919
import java.util.Objects;
20+
import java.util.Set;
2021

2122
import javax.net.ssl.X509ExtendedTrustManager;
2223

@@ -28,10 +29,15 @@
2829
public final class RestrictedTrustConfig implements SslTrustConfig {
2930

3031
private static final String RESTRICTIONS_KEY_SUBJECT_NAME = "trust.subject_name";
32+
public static final String SAN_OTHER_COMMON = "subjectAltName.otherName.commonName";
33+
public static final String SAN_DNS = "subjectAltName.dnsName";
34+
static final Set<String> SUPPORTED_X_509_FIELDS = Set.of(SAN_OTHER_COMMON, SAN_DNS);
3135
private final Path groupConfigPath;
3236
private final SslTrustConfig delegate;
37+
private final Set<String> configuredX509Fields;
3338

34-
RestrictedTrustConfig(Path groupConfigPath, SslTrustConfig delegate) {
39+
RestrictedTrustConfig(Path groupConfigPath, Set<String> configuredX509Fields, SslTrustConfig delegate) {
40+
this.configuredX509Fields = configuredX509Fields;
3541
this.groupConfigPath = Objects.requireNonNull(groupConfigPath);
3642
this.delegate = Objects.requireNonNull(delegate);
3743
}
@@ -41,7 +47,7 @@ public RestrictedTrustManager createTrustManager() {
4147
try {
4248
final X509ExtendedTrustManager delegateTrustManager = delegate.createTrustManager();
4349
final CertificateTrustRestrictions trustGroupConfig = readTrustGroup(groupConfigPath);
44-
return new RestrictedTrustManager(delegateTrustManager, trustGroupConfig);
50+
return new RestrictedTrustManager(delegateTrustManager, trustGroupConfig, configuredX509Fields);
4551
} catch (IOException e) {
4652
throw new ElasticsearchException("failed to initialize TrustManager for {}", e, toString());
4753
}

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

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import java.security.cert.X509Certificate;
1818
import java.util.Collection;
1919
import java.util.Collections;
20+
import java.util.HashSet;
2021
import java.util.List;
22+
import java.util.Locale;
2123
import java.util.Objects;
2224
import java.util.Optional;
2325
import java.util.Set;
@@ -28,26 +30,39 @@
2830
import javax.net.ssl.X509ExtendedTrustManager;
2931

3032
import static org.elasticsearch.core.Strings.format;
33+
import static org.elasticsearch.xpack.core.ssl.RestrictedTrustConfig.SAN_DNS;
34+
import static org.elasticsearch.xpack.core.ssl.RestrictedTrustConfig.SAN_OTHER_COMMON;
3135

3236
/**
3337
* An X509 trust manager that only trusts connections from a restricted set of predefined network entities (nodes, clients, etc).
34-
* The trusted entities are defined as a list of predicates on {@link CertificateTrustRestrictions} that are applied to the
35-
* common-names of the certificate.
36-
* The common-names are read as subject-alternative-names with type 'Other' and a 'cn' OID.
37-
* The underlying certificate validation is delegated to another TrustManager.
38+
* The trusted entities are defined as a list of predicates on {@link CertificateTrustRestrictions} that built from the
39+
* configured restricted trust file. The values in the restricted trust file are compared to value(s) read from the X509 certificate.
40+
* If the value(s) read from the X509 certificate match values configured in restricted trust file then restricted trust is established.
41+
* If there is no match, then restricted trust is not established and the connection should be terminated. Restricted trust should be used
42+
* in conjunction with additional trust models and is intended to restrict, not provide trust.
43+
* The values read from the X509 certificate are configurable and the following are supported:
44+
* <ul>
45+
* <li>subjectAltName.otherName.commonName</li>
46+
* <li>subjectAltName.dnsName</li>
47+
* </ul>
48+
* see also: {@link RestrictedTrustConfig}
3849
*/
3950
public final class RestrictedTrustManager extends X509ExtendedTrustManager {
4051
private static final Logger logger = LogManager.getLogger(RestrictedTrustManager.class);
4152
private static final String CN_OID = "2.5.4.3";
4253
private static final int SAN_CODE_OTHERNAME = 0;
54+
private static final int SAN_CODE_DNS = 2;
4355

4456
private final X509ExtendedTrustManager delegate;
4557
private final CertificateTrustRestrictions trustRestrictions;
58+
private final Set<String> x509Fields;
4659

47-
public RestrictedTrustManager(X509ExtendedTrustManager delegate, CertificateTrustRestrictions restrictions) {
60+
public RestrictedTrustManager(X509ExtendedTrustManager delegate, CertificateTrustRestrictions restrictions, Set<String> x509Fields) {
4861
this.delegate = delegate;
4962
this.trustRestrictions = restrictions;
63+
this.x509Fields = x509Fields.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toSet());
5064
logger.debug("Configured with trust restrictions: [{}]", restrictions);
65+
logger.debug("Configured with x509 fields: [{}]", x509Fields);
5166
}
5267

5368
@Override
@@ -96,28 +111,32 @@ private void verifyTrust(X509Certificate[] chain) throws CertificateException {
96111
throw new CertificateException("No certificate presented");
97112
}
98113
final X509Certificate certificate = chain[0];
99-
Set<String> names = readCommonNames(certificate);
100-
if (verifyCertificateNames(names)) {
114+
Set<String> values = readX509Certificate(certificate);
115+
if (verifyCertificateNames(values)) {
101116
logger.debug(
102117
() -> format(
103-
"Trusting certificate [%s] [%s] with common-names [%s]",
118+
"Trusting certificate [%s] [%s] with fields [%s] with values [%s]",
104119
certificate.getSubjectX500Principal(),
105120
certificate.getSerialNumber().toString(16),
106-
names
121+
x509Fields,
122+
values
107123
)
108124
);
109125
} else {
110126
logger.info(
111-
"Rejecting certificate [{}] [{}] with common-names [{}]",
127+
"Rejecting certificate [{}] [{}] for fields [{}] with values [{}]",
112128
certificate.getSubjectX500Principal(),
113129
certificate.getSerialNumber().toString(16),
114-
names
130+
x509Fields,
131+
values
115132
);
116133
throw new CertificateException(
117134
"Certificate for "
118135
+ certificate.getSubjectX500Principal()
119-
+ " with common-names "
120-
+ names
136+
+ " with fields "
137+
+ x509Fields
138+
+ " with values "
139+
+ values
121140
+ " does not match the trusted names "
122141
+ trustRestrictions.getTrustedNames()
123142
);
@@ -135,13 +154,28 @@ private boolean verifyCertificateNames(Set<String> names) {
135154
return false;
136155
}
137156

138-
private static Set<String> readCommonNames(X509Certificate certificate) throws CertificateParsingException {
139-
return getSubjectAlternativeNames(certificate).stream()
140-
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_OTHERNAME)
141-
.map(pair -> pair.get(1))
142-
.map(value -> decodeDerValue((byte[]) value, certificate))
143-
.filter(Objects::nonNull)
144-
.collect(Collectors.toSet());
157+
private Set<String> readX509Certificate(X509Certificate certificate) throws CertificateParsingException {
158+
Collection<List<?>> sans = getSubjectAlternativeNames(certificate);
159+
Set<String> values = new HashSet<>();
160+
if (x509Fields.contains(SAN_DNS.toLowerCase(Locale.ROOT))) {
161+
Set<String> dnsNames = sans.stream()
162+
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_DNS)
163+
.map(pair -> pair.get(1))
164+
.map(Object::toString)
165+
.filter(Objects::nonNull)
166+
.collect(Collectors.toSet());
167+
values.addAll(dnsNames);
168+
}
169+
if (x509Fields.contains(SAN_OTHER_COMMON.toLowerCase(Locale.ROOT))) {
170+
Set<String> otherNames = getSubjectAlternativeNames(certificate).stream()
171+
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_OTHERNAME)
172+
.map(pair -> pair.get(1))
173+
.map(value -> decodeDerValue((byte[]) value, certificate))
174+
.filter(Objects::nonNull)
175+
.collect(Collectors.toSet());
176+
values.addAll(otherNames);
177+
}
178+
return values;
145179
}
146180

147181
/**

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

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.common.settings.Setting.Property;
1313
import org.elasticsearch.common.settings.Settings;
1414
import org.elasticsearch.common.ssl.SslClientAuthenticationMode;
15+
import org.elasticsearch.common.ssl.SslConfigException;
1516
import org.elasticsearch.common.ssl.SslConfigurationKeys;
1617
import org.elasticsearch.common.ssl.SslVerificationMode;
1718
import org.elasticsearch.common.util.CollectionUtils;
@@ -43,6 +44,7 @@ public class SSLConfigurationSettings {
4344
final Setting<String> truststoreAlgorithm;
4445
final Setting<Optional<String>> truststoreType;
4546
final Setting<Optional<String>> trustRestrictionsPath;
47+
final Setting<List<String>> trustRestrictionsX509Fields;
4648
final Setting<List<String>> caPaths;
4749
final Setting<Optional<SslClientAuthenticationMode>> clientAuth;
4850
final Setting<Optional<SslVerificationMode>> verificationMode;
@@ -143,16 +145,43 @@ public class SSLConfigurationSettings {
143145
TRUST_STORE_TYPE_TEMPLATE
144146
);
145147

146-
private static final Function<String, Setting<Optional<String>>> TRUST_RESTRICTIONS_TEMPLATE = key -> new Setting<>(
148+
private static final Function<String, Setting<Optional<String>>> TRUST_RESTRICTIONS_PATH_TEMPLATE = key -> new Setting<>(
147149
key,
148150
s -> null,
149151
Optional::ofNullable,
150152
Property.NodeScope,
151153
Property.Filtered
152154
);
153-
private static final SslSetting<Optional<String>> TRUST_RESTRICTIONS = SslSetting.setting(
155+
private static final SslSetting<Optional<String>> TRUST_RESTRICTIONS_PATH = SslSetting.setting(
154156
"trust_restrictions.path",
155-
TRUST_RESTRICTIONS_TEMPLATE
157+
TRUST_RESTRICTIONS_PATH_TEMPLATE
158+
);
159+
160+
public static final Function<String, Setting<List<String>>> TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE = key -> Setting.listSetting(
161+
key,
162+
List.of("subjectAltName.otherName.commonName"),
163+
s -> {
164+
RestrictedTrustConfig.SUPPORTED_X_509_FIELDS.stream()
165+
.filter(v -> v.equalsIgnoreCase(s))
166+
.findAny()
167+
.ifPresentOrElse(v -> {}, () -> {
168+
throw new SslConfigException(
169+
s
170+
+ " is not a supported x509 field for trust restrictions. "
171+
+ "Recognised values are ["
172+
+ String.join(",", RestrictedTrustConfig.SUPPORTED_X_509_FIELDS)
173+
+ "]"
174+
);
175+
});
176+
return s;
177+
},
178+
Property.NodeScope,
179+
Property.Filtered
180+
);
181+
182+
public static final SslSetting<List<String>> TRUST_RESTRICTIONS_X509_FIELDS = SslSetting.setting(
183+
"trust_restrictions.x509_fields",
184+
TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE
156185
);
157186

158187
private static final SslSetting<SecureString> LEGACY_KEY_PASSWORD = SslSetting.setting(
@@ -228,7 +257,8 @@ private SSLConfigurationSettings(String prefix, boolean acceptNonSecurePasswords
228257
truststorePassword = TRUSTSTORE_PASSWORD.withPrefix(prefix);
229258
truststoreAlgorithm = TRUSTSTORE_ALGORITHM.withPrefix(prefix);
230259
truststoreType = TRUSTSTORE_TYPE.withPrefix(prefix);
231-
trustRestrictionsPath = TRUST_RESTRICTIONS.withPrefix(prefix);
260+
trustRestrictionsPath = TRUST_RESTRICTIONS_PATH.withPrefix(prefix);
261+
trustRestrictionsX509Fields = TRUST_RESTRICTIONS_X509_FIELDS.withPrefix(prefix);
232262
caPaths = CERT_AUTH_PATH.withPrefix(prefix);
233263
clientAuth = CLIENT_AUTH_SETTING.withPrefix(prefix);
234264
verificationMode = VERIFICATION_MODE.withPrefix(prefix);
@@ -241,6 +271,7 @@ private SSLConfigurationSettings(String prefix, boolean acceptNonSecurePasswords
241271
truststoreAlgorithm,
242272
truststoreType,
243273
trustRestrictionsPath,
274+
trustRestrictionsX509Fields,
244275
caPaths,
245276
clientAuth,
246277
verificationMode
@@ -304,7 +335,8 @@ private static Collection<SslSetting<?>> settings() {
304335
TRUSTSTORE_ALGORITHM,
305336
KEY_STORE_TYPE,
306337
TRUSTSTORE_TYPE,
307-
TRUST_RESTRICTIONS,
338+
TRUST_RESTRICTIONS_PATH,
339+
TRUST_RESTRICTIONS_X509_FIELDS,
308340
KEY_PATH,
309341
LEGACY_KEY_PASSWORD,
310342
KEY_PASSWORD,

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@
2525
import java.security.KeyStore;
2626
import java.util.List;
2727
import java.util.Map;
28+
import java.util.Set;
2829
import java.util.function.Function;
2930
import java.util.stream.Collectors;
3031

32+
import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.TRUST_RESTRICTIONS_X509_FIELDS;
33+
import static org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings.TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE;
34+
3135
/**
3236
* A configuration loader for SSL Settings
3337
*/
@@ -117,7 +121,16 @@ protected SslTrustConfig buildTrustConfig(Path basePath, SslVerificationMode ver
117121
if (trustRestrictions == null) {
118122
return trustConfig;
119123
}
120-
return new RestrictedTrustConfig(trustRestrictions, trustConfig);
124+
return new RestrictedTrustConfig(
125+
trustRestrictions,
126+
Set.copyOf(
127+
super.resolveList(
128+
TRUST_RESTRICTIONS_X509_FIELDS.rawSetting().getKey(),
129+
TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE.apply("").getDefault(settings)
130+
)
131+
),
132+
trustConfig
133+
);
121134
}
122135

123136
public SslConfiguration load(Environment env) {

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustConfigTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import java.util.ArrayList;
2020
import java.util.Collection;
2121
import java.util.List;
22+
import java.util.Set;
2223

2324
import javax.net.ssl.X509ExtendedTrustManager;
2425

26+
import static org.elasticsearch.xpack.core.ssl.RestrictedTrustConfig.SAN_OTHER_COMMON;
27+
2528
public class RestrictedTrustConfigTests extends ESTestCase {
2629

2730
public void testDelegationOfFilesToMonitor() throws Exception {
@@ -68,7 +71,7 @@ public int hashCode() {
6871
}
6972
};
7073

71-
final RestrictedTrustConfig restrictedTrustConfig = new RestrictedTrustConfig(groupConfigPath, delegate);
74+
final RestrictedTrustConfig restrictedTrustConfig = new RestrictedTrustConfig(groupConfigPath, Set.of(SAN_OTHER_COMMON), delegate);
7275
Collection<Path> filesToMonitor = restrictedTrustConfig.getDependentFiles();
7376
List<Path> expectedPathList = new ArrayList<>(otherFiles);
7477
expectedPathList.add(groupConfigPath);

0 commit comments

Comments
 (0)