Add API for SSL certificate information (elastic/x-pack-elasticsearch#3088)
Exposes the certificate location (configured path), serial number, and expiry date Closes: elastic/x-pack-elasticsearch#2795 Original commit: elastic/x-pack-elasticsearch@a0773f6840
This commit is contained in:
parent
7f553f391f
commit
628dfaa843
|
@ -177,6 +177,8 @@ import org.elasticsearch.xpack.security.user.AnonymousUser;
|
|||
import org.elasticsearch.xpack.ssl.SSLConfigurationSettings;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
import org.elasticsearch.xpack.ssl.TLSLicenseBootstrapCheck;
|
||||
import org.elasticsearch.xpack.ssl.action.GetCertificateInfoAction;
|
||||
import org.elasticsearch.xpack.ssl.rest.RestGetCertificateInfoAction;
|
||||
import org.elasticsearch.xpack.template.TemplateUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
@ -599,7 +601,8 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
|
|||
new ActionHandler<>(PutRoleMappingAction.INSTANCE, TransportPutRoleMappingAction.class),
|
||||
new ActionHandler<>(DeleteRoleMappingAction.INSTANCE, TransportDeleteRoleMappingAction.class),
|
||||
new ActionHandler<>(CreateTokenAction.INSTANCE, TransportCreateTokenAction.class),
|
||||
new ActionHandler<>(InvalidateTokenAction.INSTANCE, TransportInvalidateTokenAction.class)
|
||||
new ActionHandler<>(InvalidateTokenAction.INSTANCE, TransportInvalidateTokenAction.class),
|
||||
new ActionHandler<>(GetCertificateInfoAction.INSTANCE, GetCertificateInfoAction.TransportAction.class)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -639,7 +642,8 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
|
|||
new RestPutRoleMappingAction(settings, restController, licenseState),
|
||||
new RestDeleteRoleMappingAction(settings, restController, licenseState),
|
||||
new RestGetTokenAction(settings, restController, licenseState),
|
||||
new RestInvalidateTokenAction(settings, restController, licenseState)
|
||||
new RestInvalidateTokenAction(settings, restController, licenseState),
|
||||
new RestGetCertificateInfoAction(settings, restController)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,10 +51,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1TaggedObject;
|
||||
import org.bouncycastle.asn1.BERSequence;
|
||||
import org.bouncycastle.asn1.BERTaggedObject;
|
||||
import org.bouncycastle.asn1.DERIA5String;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.bouncycastle.asn1.DERUTF8String;
|
||||
|
@ -63,7 +59,6 @@ import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
|||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.DisplayText;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
|
|
|
@ -8,12 +8,16 @@ package org.elasticsearch.xpack.ssl;
|
|||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -36,6 +40,15 @@ class DefaultJDKTrustConfig extends TrustConfig {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* We don't return the list of JDK certificates here, because they are not managed by Elasticsearch, and the purpose
|
||||
* of this method is to obtain information about certificate (files/stores) that X-Pack directly manages.
|
||||
*/
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -7,11 +7,13 @@ package org.elasticsearch.xpack.ssl;
|
|||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.nio.file.Path;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -28,6 +30,11 @@ abstract class KeyConfig extends TrustConfig {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
return Collections.emptyList();
|
||||
|
@ -57,4 +64,5 @@ abstract class KeyConfig extends TrustConfig {
|
|||
abstract X509ExtendedKeyManager createKeyManager(@Nullable Environment environment);
|
||||
|
||||
abstract List<PrivateKey> privateKeys(@Nullable Environment environment);
|
||||
|
||||
}
|
||||
|
|
|
@ -19,16 +19,18 @@ import java.security.PrivateKey;
|
|||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* Implementation of a key configuration that is backed by a PEM encoded key file and one or more certificates
|
||||
|
@ -41,8 +43,9 @@ class PEMKeyConfig extends KeyConfig {
|
|||
|
||||
/**
|
||||
* Creates a new key configuration backed by the key and certificate chain provided
|
||||
* @param keyPath the path to the key file
|
||||
* @param keyPassword the password for the key.
|
||||
*
|
||||
* @param keyPath the path to the key file
|
||||
* @param keyPassword the password for the key.
|
||||
* @param certChainPath the path to the file containing the certificate chain
|
||||
*/
|
||||
PEMKeyConfig(String keyPath, SecureString keyPassword, String certChainPath) {
|
||||
|
@ -58,7 +61,7 @@ class PEMKeyConfig extends KeyConfig {
|
|||
if (privateKey == null) {
|
||||
throw new IllegalArgumentException("private key [" + keyPath + "] could not be loaded");
|
||||
}
|
||||
Certificate[] certificateChain = CertUtils.readCertificates(Collections.singletonList(certPath), environment);
|
||||
Certificate[] certificateChain = getCertificateChain(environment);
|
||||
|
||||
return CertUtils.keyManager(certificateChain, privateKey, keyPassword.getChars());
|
||||
} catch (IOException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
|
||||
|
@ -66,6 +69,23 @@ class PEMKeyConfig extends KeyConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private Certificate[] getCertificateChain(@Nullable Environment environment) throws CertificateException, IOException {
|
||||
return CertUtils.readCertificates(Collections.singletonList(certPath), environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws CertificateException, IOException {
|
||||
final Certificate[] chain = getCertificateChain(environment);
|
||||
final List<CertificateInfo> info = new ArrayList<>(chain.length);
|
||||
for (int i = 0; i < chain.length; i++) {
|
||||
final Certificate cert = chain[i];
|
||||
if (cert instanceof X509Certificate) {
|
||||
info.add(new CertificateInfo(certPath, "PEM", null, i == 0, (X509Certificate) cert));
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PrivateKey> privateKeys(@Nullable Environment environment) {
|
||||
try {
|
||||
|
@ -84,7 +104,7 @@ class PEMKeyConfig extends KeyConfig {
|
|||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
try {
|
||||
Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(certPath), environment);
|
||||
Certificate[] certificates = getCertificateChain(environment);
|
||||
return CertUtils.trustManager(certificates);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
|
|
|
@ -5,17 +5,23 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.nio.file.Path;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* Implementation of trust configuration that is backed by PEM encoded certificate files.
|
||||
|
@ -42,6 +48,20 @@ class PEMTrustConfig extends TrustConfig {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws CertificateException, IOException {
|
||||
final List<CertificateInfo> info = new ArrayList<>(caPaths.size());
|
||||
for (String path : caPaths) {
|
||||
Certificate[] chain = CertUtils.readCertificates(Collections.singletonList(path), environment);
|
||||
for (final Certificate cert : chain) {
|
||||
if (cert instanceof X509Certificate) {
|
||||
info.add(new CertificateInfo(path, "PEM", null, false, (X509Certificate) cert));
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
List<Path> paths = new ArrayList<>(caPaths.size());
|
||||
|
|
|
@ -8,7 +8,9 @@ package org.elasticsearch.xpack.ssl;
|
|||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -17,6 +19,7 @@ import org.elasticsearch.ElasticsearchException;
|
|||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* An implementation of {@link TrustConfig} that constructs a {@link RestrictedTrustManager}.
|
||||
|
@ -47,6 +50,11 @@ public final class RestrictedTrustConfig extends TrustConfig {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
return delegate.certificates(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
List<Path> files = new ArrayList<>(delegate.filesToMonitor(environment));
|
||||
|
|
|
@ -5,19 +5,23 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.XPackSettings;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* Represents the configuration for an SSLContext
|
||||
|
@ -187,8 +191,8 @@ public final class SSLConfiguration {
|
|||
// TODO: we should not support loading a keystore from sysprops...
|
||||
try (SecureString keystorePassword = new SecureString(System.getProperty("javax.net.ssl.keyStorePassword", ""))) {
|
||||
return new StoreKeyConfig(System.getProperty("javax.net.ssl.keyStore"), "jks", keystorePassword, keystorePassword,
|
||||
System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()),
|
||||
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
|
||||
System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()),
|
||||
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
|
||||
}
|
||||
}
|
||||
return KeyConfig.NONE;
|
||||
|
@ -248,7 +252,7 @@ public final class SSLConfiguration {
|
|||
} else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null) {
|
||||
try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) {
|
||||
return new StoreTrustConfig(System.getProperty("javax.net.ssl.trustStore"), "jks", truststorePassword,
|
||||
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
|
||||
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
|
||||
}
|
||||
} else if (global != null && keyConfig == global.keyConfig()) {
|
||||
return global.trustConfig();
|
||||
|
@ -269,4 +273,17 @@ public final class SSLConfiguration {
|
|||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about each certificate that referenced by this SSL configurations.
|
||||
* This includes certificates used for identity (with a private key) and those used for trust, but excludes
|
||||
* certificates that are provided by the JRE.
|
||||
* @see TrustConfig#certificates(Environment)
|
||||
*/
|
||||
List<CertificateInfo> getDefinedCertificates(@Nullable Environment environment) throws GeneralSecurityException, IOException {
|
||||
List<CertificateInfo> certificates = new ArrayList<>();
|
||||
certificates.addAll(keyConfig.certificates(environment));
|
||||
certificates.addAll(trustConfig.certificates(environment));
|
||||
return certificates;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,20 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.CheckedSupplier;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.XPackSettings;
|
||||
import org.elasticsearch.xpack.common.socket.SocketAccess;
|
||||
import org.elasticsearch.xpack.security.Security;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
@ -32,6 +18,7 @@ import javax.security.auth.DestroyFailedException;
|
|||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -51,6 +38,22 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.CheckedSupplier;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.XPackSettings;
|
||||
import org.elasticsearch.xpack.common.socket.SocketAccess;
|
||||
import org.elasticsearch.xpack.security.Security;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* Provides access to {@link SSLEngine} and {@link SSLSocketFactory} objects based on a provided configuration. All
|
||||
|
@ -451,6 +454,23 @@ public class SSLService extends AbstractComponent {
|
|||
return Collections.unmodifiableMap(sslConfigurations);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns information about each certificate that is referenced by any SSL configuration.
|
||||
* This includes certificates used for identity (with a private key) and those used for trust, but excludes
|
||||
* certificates that are provided by the JRE.
|
||||
* Due to the nature of KeyStores, this may include certificates that are available, but never used
|
||||
* such as a CA certificate that is no longer in use, or a server certificate for an unrelated host.
|
||||
* @see TrustConfig#certificates(Environment)
|
||||
*/
|
||||
public Set<CertificateInfo> getLoadedCertificates() throws GeneralSecurityException, IOException {
|
||||
Set<CertificateInfo> certificates = new HashSet<>();
|
||||
for (SSLConfiguration config : this.getLoadedSSLConfigurations()) {
|
||||
certificates.addAll(config.getDefinedCertificates(env));
|
||||
}
|
||||
return certificates;
|
||||
}
|
||||
|
||||
/**
|
||||
* This socket factory wraps an existing SSLSocketFactory and sets the protocols and ciphers on each SSLSocket after it is created. This
|
||||
* is needed even though the SSLContext is configured properly as the configuration does not flow down to the sockets created by the
|
||||
|
|
|
@ -5,30 +5,35 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* A key configuration that is backed by a {@link KeyStore}
|
||||
*/
|
||||
|
@ -82,6 +87,28 @@ class StoreKeyConfig extends KeyConfig {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
final Path path = CertUtils.resolvePath(keyStorePath, environment);
|
||||
final KeyStore trustStore = CertUtils.readKeyStore(path, keyStoreType, keyStorePassword.getChars());
|
||||
final List<CertificateInfo> certificates = new ArrayList<>();
|
||||
final Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
final Certificate[] chain = trustStore.getCertificateChain(alias);
|
||||
if (chain == null) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < chain.length; i++) {
|
||||
final Certificate certificate = chain[i];
|
||||
if (certificate instanceof X509Certificate) {
|
||||
certificates.add(new CertificateInfo(keyStorePath, keyStoreType, alias, i == 0, (X509Certificate) certificate));
|
||||
}
|
||||
}
|
||||
}
|
||||
return certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
return Collections.singletonList(CertUtils.resolvePath(keyStorePath, environment));
|
||||
|
|
|
@ -5,16 +5,25 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* Trust configuration that is backed by a {@link java.security.KeyStore}
|
||||
|
@ -28,8 +37,9 @@ class StoreTrustConfig extends TrustConfig {
|
|||
|
||||
/**
|
||||
* Create a new configuration based on the provided parameters
|
||||
* @param trustStorePath the path to the truststore
|
||||
* @param trustStorePassword the password for the truststore
|
||||
*
|
||||
* @param trustStorePath the path to the truststore
|
||||
* @param trustStorePassword the password for the truststore
|
||||
* @param trustStoreAlgorithm the algorithm to use for reading the truststore
|
||||
*/
|
||||
StoreTrustConfig(String trustStorePath, String trustStoreType, SecureString trustStorePassword, String trustStoreAlgorithm) {
|
||||
|
@ -50,6 +60,23 @@ class StoreTrustConfig extends TrustConfig {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
final Path path = CertUtils.resolvePath(trustStorePath, environment);
|
||||
final KeyStore trustStore = CertUtils.readKeyStore(path, trustStoreType, trustStorePassword.getChars());
|
||||
final List<CertificateInfo> certificates = new ArrayList<>();
|
||||
final Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
final Certificate certificate = trustStore.getCertificate(alias);
|
||||
if (certificate instanceof X509Certificate) {
|
||||
final boolean hasKey = trustStore.isKeyEntry(alias);
|
||||
certificates.add(new CertificateInfo(trustStorePath, trustStoreType, alias, hasKey, (X509Certificate) certificate));
|
||||
}
|
||||
}
|
||||
return certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
if (trustStorePath == null) {
|
||||
|
|
|
@ -7,13 +7,17 @@ package org.elasticsearch.xpack.ssl;
|
|||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -67,6 +71,11 @@ class TrustAllConfig extends TrustConfig {
|
|||
return TRUST_MANAGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -5,19 +5,24 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* The configuration of trust material for SSL usage
|
||||
*/
|
||||
|
@ -29,6 +34,8 @@ abstract class TrustConfig {
|
|||
*/
|
||||
abstract X509ExtendedTrustManager createTrustManager(@Nullable Environment environment);
|
||||
|
||||
abstract Collection<CertificateInfo> certificates(@Nullable Environment environment) throws GeneralSecurityException, IOException;
|
||||
|
||||
/**
|
||||
* Returns a list of files that should be monitored for changes
|
||||
* @param environment the environment to resolve files against or null in the case of running in a transport client
|
||||
|
@ -80,6 +87,15 @@ abstract class TrustConfig {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
List<CertificateInfo> certificates = new ArrayList<>();
|
||||
for (TrustConfig tc : trustConfigs) {
|
||||
certificates.addAll(tc.certificates(environment));
|
||||
}
|
||||
return certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||
return trustConfigs.stream().flatMap((tc) -> tc.filesToMonitor(environment).stream()).collect(Collectors.toList());
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.ssl.action;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
/**
|
||||
* Action to obtain information about X.509 (SSL/TLS) certificates that are being used by X-Pack.
|
||||
* The primary use case is for tracking the expiry dates of certificates.
|
||||
*/
|
||||
public class GetCertificateInfoAction
|
||||
extends Action<GetCertificateInfoAction.Request, GetCertificateInfoAction.Response, GetCertificateInfoAction.RequestBuilder> {
|
||||
|
||||
public static final GetCertificateInfoAction INSTANCE = new GetCertificateInfoAction();
|
||||
public static final String NAME = "cluster:monitor/xpack/ssl/certificates/get";
|
||||
|
||||
private GetCertificateInfoAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetCertificateInfoAction.RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new GetCertificateInfoAction.RequestBuilder(client, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetCertificateInfoAction.Response newResponse() {
|
||||
return new GetCertificateInfoAction.Response();
|
||||
}
|
||||
|
||||
public static class Request extends ActionRequest {
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Response extends ActionResponse implements ToXContentObject {
|
||||
|
||||
private Collection<CertificateInfo> certificates;
|
||||
|
||||
public Response() {
|
||||
}
|
||||
|
||||
public Response(Collection<CertificateInfo> certificates) {
|
||||
this.certificates = certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startArray();
|
||||
for (CertificateInfo cert : certificates) {
|
||||
cert.toXContent(builder, params);
|
||||
}
|
||||
return builder.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeVInt(certificates.size());
|
||||
for (CertificateInfo cert : certificates) {
|
||||
cert.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
this.certificates = new ArrayList<>();
|
||||
int count = in.readVInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
certificates.add(new CertificateInfo(in));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder> {
|
||||
|
||||
public RequestBuilder(ElasticsearchClient client, GetCertificateInfoAction action) {
|
||||
super(client, action, new Request());
|
||||
}
|
||||
|
||||
public RequestBuilder(ElasticsearchClient client) {
|
||||
this(client, GetCertificateInfoAction.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TransportAction extends HandledTransportAction<Request, Response> {
|
||||
|
||||
private final SSLService sslService;
|
||||
|
||||
@Inject
|
||||
public TransportAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
SSLService sslService) {
|
||||
super(settings, GetCertificateInfoAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
Request::new);
|
||||
this.sslService = sslService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(Request request, ActionListener<Response> listener) {
|
||||
try {
|
||||
Collection<CertificateInfo> certificates = sslService.getLoadedCertificates();
|
||||
listener.onResponse(new Response(certificates));
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.ssl.cert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xpack.ssl.CertUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/**
|
||||
* Simple model of an X.509 certificate that is known to X-Pack
|
||||
*/
|
||||
public class CertificateInfo implements ToXContentObject, Writeable {
|
||||
private final String path;
|
||||
private final String format;
|
||||
private final String alias;
|
||||
private final String subjectDn;
|
||||
private final String serialNumber;
|
||||
private final boolean hasPrivateKey;
|
||||
private final DateTime expiry;
|
||||
|
||||
public CertificateInfo(String path, String format, String alias, boolean hasPrivateKey, X509Certificate certificate) {
|
||||
Objects.requireNonNull(certificate, "Certificate cannot be null");
|
||||
this.path = Objects.requireNonNull(path, "Certificate path cannot be null");
|
||||
this.format = Objects.requireNonNull(format, "Certificate format cannot be null");
|
||||
this.alias = alias;
|
||||
this.subjectDn = Objects.requireNonNull(certificate.getSubjectDN().getName());
|
||||
this.serialNumber = certificate.getSerialNumber().toString(16);
|
||||
this.hasPrivateKey = hasPrivateKey;
|
||||
this.expiry = new DateTime(certificate.getNotAfter(), DateTimeZone.UTC);
|
||||
}
|
||||
|
||||
public CertificateInfo(StreamInput in) throws IOException {
|
||||
this.path = in.readString();
|
||||
this.format = in.readString();
|
||||
this.alias = in.readOptionalString();
|
||||
this.subjectDn = in.readString();
|
||||
this.serialNumber = in.readString();
|
||||
this.hasPrivateKey = in.readBoolean();
|
||||
this.expiry = new DateTime(in.readLong(), DateTimeZone.UTC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeString(path);
|
||||
out.writeString(format);
|
||||
out.writeOptionalString(alias);
|
||||
out.writeString(subjectDn);
|
||||
out.writeString(serialNumber);
|
||||
out.writeBoolean(hasPrivateKey);
|
||||
out.writeLong(expiry.getMillis());
|
||||
}
|
||||
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public String alias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public String subjectDn() {
|
||||
return subjectDn;
|
||||
}
|
||||
|
||||
public String serialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public DateTime expiry() {
|
||||
return expiry;
|
||||
}
|
||||
|
||||
public boolean hasPrivateKey() {
|
||||
return hasPrivateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.field("path", path)
|
||||
.field("format", format)
|
||||
.field("alias", alias)
|
||||
.field("subject_dn", subjectDn)
|
||||
.field("serial_number", serialNumber)
|
||||
.field("has_private_key", hasPrivateKey)
|
||||
.field("expiry", expiry)
|
||||
.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final CertificateInfo that = (CertificateInfo) other;
|
||||
return this.path.equals(that.path)
|
||||
&& this.format.equals(that.format)
|
||||
&& this.hasPrivateKey == that.hasPrivateKey
|
||||
&& Objects.equals(this.alias, that.alias)
|
||||
&& Objects.equals(this.serialNumber, that.serialNumber)
|
||||
&& Objects.equals(this.subjectDn, that.subjectDn)
|
||||
&& Objects.equals(this.expiry, that.expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = path.hashCode();
|
||||
result = 31 * result + (alias != null ? alias.hashCode() : 0);
|
||||
result = 31 * result + (serialNumber != null ? serialNumber.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.ssl.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.rest.BaseRestHandler;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.xpack.ssl.action.GetCertificateInfoAction;
|
||||
import org.elasticsearch.xpack.ssl.action.GetCertificateInfoAction.Response;
|
||||
|
||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||
|
||||
/**
|
||||
* A REST handler to obtain information about TLS/SSL (X.509) certificates
|
||||
* @see GetCertificateInfoAction
|
||||
*/
|
||||
public class RestGetCertificateInfoAction extends BaseRestHandler {
|
||||
|
||||
public RestGetCertificateInfoAction(Settings settings, RestController controller) {
|
||||
super(settings);
|
||||
controller.registerHandler(GET, "/_xpack/ssl/certificates", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "xpack_ssl_get_certificates";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
|
||||
return channel -> new GetCertificateInfoAction.RequestBuilder(client, GetCertificateInfoAction.INSTANCE)
|
||||
.execute(new RestBuilderListener<Response>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(Response response, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(RestStatus.OK, response.toXContent(builder, request));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,15 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.network.InetAddresses;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.math.BigInteger;
|
||||
|
@ -29,12 +20,18 @@ import java.security.cert.Certificate;
|
|||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.network.InetAddresses;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -186,4 +183,5 @@ public class CertUtilsTests extends ESTestCase {
|
|||
ECNamedCurveSpec namedCurveSpec = (ECNamedCurveSpec) ecPrivateKey.getParams();
|
||||
assertEquals("prime256v1", namedCurveSpec.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,18 +5,23 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RestrictedTrustConfigTests extends ESTestCase {
|
||||
|
||||
public void testDelegationOfFilesToMonitor() throws Exception {
|
||||
|
@ -37,6 +42,11 @@ public class RestrictedTrustConfigTests extends ESTestCase {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<Path> filesToMonitor(Environment environment) {
|
||||
return otherFiles;
|
||||
|
|
|
@ -20,9 +20,11 @@ import java.security.PrivilegedExceptionAction;
|
|||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
|
@ -34,6 +36,7 @@ import org.apache.http.impl.client.HttpClients;
|
|||
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.CheckedRunnable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.MockSecureSettings;
|
||||
|
@ -43,6 +46,8 @@ import org.elasticsearch.env.TestEnvironment;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.junit.annotations.Network;
|
||||
import org.elasticsearch.xpack.XPackSettings;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Before;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
|
@ -50,9 +55,11 @@ import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
|||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.emptyArray;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
@ -78,7 +85,7 @@ public class SSLServiceTests extends ESTestCase {
|
|||
testnodeStoreType = randomBoolean() ? "jks" : null;
|
||||
} else {
|
||||
testnodeStore = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.p12");
|
||||
testnodeStoreType = "PKCS12";
|
||||
testnodeStoreType = randomBoolean() ? "PKCS12" : null;
|
||||
}
|
||||
logger.info("Using [{}] key/truststore [{}]", testnodeStoreType, testnodeStore);
|
||||
testclientStore = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks");
|
||||
|
@ -333,7 +340,7 @@ public class SSLServiceTests extends ESTestCase {
|
|||
.put("xpack.ssl.keystore.path", testnodeStore)
|
||||
.put("xpack.ssl.keystore.type", testnodeStoreType)
|
||||
.setSecureSettings(secureSettings)
|
||||
.putList("xpack.ssl.cipher_suites", new String[]{"foo", "bar"})
|
||||
.putList("xpack.ssl.cipher_suites", new String[] { "foo", "bar" })
|
||||
.build();
|
||||
IllegalArgumentException e =
|
||||
expectThrows(IllegalArgumentException.class, () -> new SSLService(settings, env));
|
||||
|
@ -456,6 +463,98 @@ public class SSLServiceTests extends ESTestCase {
|
|||
assertEquals(message, ce.getMessage());
|
||||
}
|
||||
|
||||
public void testReadCertificateInformation() throws Exception {
|
||||
final Path jksPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks");
|
||||
final Path p12Path = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.p12");
|
||||
final Path pemPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/active-directory-ca.crt");
|
||||
|
||||
final MockSecureSettings secureSettings = new MockSecureSettings();
|
||||
secureSettings.setString("xpack.ssl.keystore.secure_password", "testnode");
|
||||
secureSettings.setString("xpack.ssl.truststore.secure_password", "testnode");
|
||||
secureSettings.setString("xpack.http.ssl.keystore.secure_password", "testnode");
|
||||
|
||||
final Settings settings = Settings.builder()
|
||||
.put("xpack.ssl.keystore.path", jksPath)
|
||||
.put("xpack.ssl.truststore.path", jksPath)
|
||||
.put("xpack.http.ssl.keystore.path", p12Path)
|
||||
.put("xpack.security.authc.realms.ad.type", "ad")
|
||||
.put("xpack.security.authc.realms.ad.ssl.certificate_authorities", pemPath)
|
||||
.setSecureSettings(secureSettings)
|
||||
.build();
|
||||
|
||||
final SSLService sslService = new SSLService(settings, env);
|
||||
final List<CertificateInfo> certificates = new ArrayList<>(sslService.getLoadedCertificates());
|
||||
assertThat(certificates, iterableWithSize(7));
|
||||
Collections.sort(certificates,
|
||||
Comparator.comparing((CertificateInfo c) -> c.alias() == null ? "" : c.alias()).thenComparing(CertificateInfo::path));
|
||||
|
||||
final Iterator<CertificateInfo> iterator = certificates.iterator();
|
||||
CertificateInfo cert = iterator.next();
|
||||
assertThat(cert.alias(), nullValue());
|
||||
assertThat(cert.path(), equalTo(pemPath.toString()));
|
||||
assertThat(cert.format(), equalTo("PEM"));
|
||||
assertThat(cert.serialNumber(), equalTo("580db8ad52bb168a4080e1df122a3f56"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=ad-ELASTICSEARCHAD-CA, DC=ad, DC=test, DC=elasticsearch, DC=com"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2029-08-27T16:32:42Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("activedir"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("580db8ad52bb168a4080e1df122a3f56"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=ad-ELASTICSEARCHAD-CA, DC=ad, DC=test, DC=elasticsearch, DC=com"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2029-08-27T16:32:42Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("openldap"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("d3850b2b1995ad5f"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=OpenLDAP, OU=Elasticsearch, O=Elastic, L=Mountain View, ST=CA, C=US"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2027-07-23T16:41:14Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testclient"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("b9d497f2924bbe29"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Client, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:55Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("b8b96c37e332cccb"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:57Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode"));
|
||||
assertThat(cert.path(), equalTo(p12Path.toString()));
|
||||
assertThat(cert.format(), equalTo("PKCS12"));
|
||||
assertThat(cert.serialNumber(), equalTo("b8b96c37e332cccb"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:57Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(true));
|
||||
|
||||
cert = iterator.next();
|
||||
assertThat(cert.alias(), equalTo("testnode-client-profile"));
|
||||
assertThat(cert.path(), equalTo(jksPath.toString()));
|
||||
assertThat(cert.format(), equalTo("jks"));
|
||||
assertThat(cert.serialNumber(), equalTo("c0ea4216e8ff0fd8"));
|
||||
assertThat(cert.subjectDn(), equalTo("CN=testnode-client-profile"));
|
||||
assertThat(cert.expiry(), equalTo(DateTime.parse("2019-09-22T18:52:56Z")));
|
||||
assertThat(cert.hasPrivateKey(), equalTo(false));
|
||||
|
||||
assertFalse(iterator.hasNext());
|
||||
}
|
||||
|
||||
@Network
|
||||
public void testThatSSLContextWithoutSettingsWorks() throws Exception {
|
||||
SSLService sslService = new SSLService(Settings.EMPTY, env);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.ssl.cert;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.ssl.CertUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class CertificateInfoTests extends ESTestCase {
|
||||
|
||||
public void testSerialization() throws Exception {
|
||||
final X500Principal principal = new X500Principal("CN=foo");
|
||||
final X509Certificate certificate = CertUtils.generateSignedCertificate(principal, new GeneralNames(new GeneralName[0]),
|
||||
getKeyPair(), null, null, 90);
|
||||
final CertificateInfo cert1 = new CertificateInfo("/path/to/cert.jks", "jks", "key", true, certificate);
|
||||
final CertificateInfo cert2 = serializeAndDeserialize(cert1);
|
||||
final CertificateInfo cert3 = serializeAndDeserialize(cert2);
|
||||
assertThat(cert1, equalTo(cert2));
|
||||
assertThat(cert1, equalTo(cert3));
|
||||
assertThat(cert2, equalTo(cert3));
|
||||
}
|
||||
|
||||
private CertificateInfo serializeAndDeserialize(CertificateInfo cert1) throws IOException {
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
cert1.writeTo(output);
|
||||
return new CertificateInfo(output.bytes().streamInput());
|
||||
}
|
||||
|
||||
private KeyPair getKeyPair() throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"xpack.ssl.certificates": {
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html",
|
||||
"methods": [ "GET" ],
|
||||
"url": {
|
||||
"path": "/_xpack/ssl/certificates",
|
||||
"paths": [ "/_xpack/ssl/certificates" ],
|
||||
"parts": {},
|
||||
"params": {}
|
||||
},
|
||||
"body": null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
"Test get SSL certificates":
|
||||
- do:
|
||||
xpack.ssl.certificates: {}
|
||||
|
||||
- length: { $body: 1 }
|
||||
- match: { $body.0.path: "test-node.jks" }
|
||||
- match: { $body.0.format: "jks" }
|
||||
- match: { $body.0.alias: "test-node" }
|
||||
- match: { $body.0.has_private_key: true }
|
Loading…
Reference in New Issue