Improve errors when TLS files cannot be read (#45122)
This change improves the exception messages that are thrown when the system cannot read TLS resources such as keystores, truststores, certificates, keys or certificate-chains (CAs). This change specifically handles: - Files that do not exist - Files that cannot be read due to file-system permissions - Files that cannot be read due to the ES security-manager Backport of: #44787
This commit is contained in:
parent
590777150f
commit
e21d58541a
|
@ -63,6 +63,10 @@ public class CertParsingUtils {
|
|||
return PathUtils.get(path).normalize();
|
||||
}
|
||||
|
||||
static List<Path> resolvePaths(List<String> certPaths, @Nullable Environment environment) {
|
||||
return certPaths.stream().map(p -> resolvePath(p, environment)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static KeyStore readKeyStore(Path path, String type, char[] password)
|
||||
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
|
@ -82,7 +86,7 @@ public class CertParsingUtils {
|
|||
*/
|
||||
public static Certificate[] readCertificates(List<String> certPaths, @Nullable Environment environment)
|
||||
throws CertificateException, IOException {
|
||||
final List<Path> resolvedPaths = certPaths.stream().map(p -> resolvePath(p, environment)).collect(Collectors.toList());
|
||||
final List<Path> resolvedPaths = resolvePaths(certPaths, environment);
|
||||
return readCertificates(resolvedPaths);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.core.ssl;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
||||
|
@ -12,7 +13,10 @@ import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
|||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -64,6 +68,31 @@ abstract class KeyConfig extends TrustConfig {
|
|||
|
||||
abstract X509ExtendedKeyManager createKeyManager(@Nullable Environment environment);
|
||||
|
||||
/**
|
||||
* generate a new exception caused by a missing file, that is required for this key config
|
||||
*/
|
||||
static ElasticsearchException missingKeyConfigFile(IOException cause, String fileType, Path path) {
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL KeyManager - " + fileType + " file [{}] does not exist", cause, path.toAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a new exception caused by an unreadable file (i.e. file-system access denied), that is required for this key config
|
||||
*/
|
||||
static ElasticsearchException unreadableKeyConfigFile(AccessDeniedException cause, String fileType, Path path) {
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL KeyManager - not permitted to read " + fileType + " file [{}]", cause, path.toAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a new exception caused by a blocked file (i.e. security-manager access denied), that is required for this key config
|
||||
*/
|
||||
static ElasticsearchException blockedKeyConfigFile(AccessControlException cause, Environment environment, String fileType, Path path) {
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL KeyManager - access to read {} file [{}] is blocked;" +
|
||||
" SSL resources should be placed in the [{}] directory", cause, fileType, path, environment.configFile());
|
||||
}
|
||||
|
||||
abstract List<PrivateKey> privateKeys(@Nullable Environment environment);
|
||||
|
||||
}
|
||||
|
|
|
@ -14,9 +14,13 @@ import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
|||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
|
@ -35,6 +39,9 @@ import java.util.Objects;
|
|||
*/
|
||||
class PEMKeyConfig extends KeyConfig {
|
||||
|
||||
private static final String CERTIFICATE_FILE = "certificate";
|
||||
private static final String KEY_FILE = "key";
|
||||
|
||||
private final String keyPath;
|
||||
private final SecureString keyPassword;
|
||||
private final String certPath;
|
||||
|
@ -55,7 +62,7 @@ class PEMKeyConfig extends KeyConfig {
|
|||
@Override
|
||||
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
|
||||
try {
|
||||
PrivateKey privateKey = readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword);
|
||||
PrivateKey privateKey = readPrivateKey(keyPath, keyPassword, environment);
|
||||
if (privateKey == null) {
|
||||
throw new IllegalArgumentException("private key [" + keyPath + "] could not be loaded");
|
||||
}
|
||||
|
@ -63,12 +70,21 @@ class PEMKeyConfig extends KeyConfig {
|
|||
|
||||
return CertParsingUtils.keyManager(certificateChain, privateKey, keyPassword.getChars());
|
||||
} catch (IOException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
|
||||
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
|
||||
throw new ElasticsearchException("failed to initialize SSL KeyManagerFactory", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Certificate[] getCertificateChain(@Nullable Environment environment) throws CertificateException, IOException {
|
||||
return CertParsingUtils.readCertificates(Collections.singletonList(certPath), environment);
|
||||
final Path certificate = CertParsingUtils.resolvePath(certPath, environment);
|
||||
try {
|
||||
return CertParsingUtils.readCertificates(Collections.singletonList(certificate));
|
||||
} catch (FileNotFoundException | NoSuchFileException fileException) {
|
||||
throw missingKeyConfigFile(fileException, CERTIFICATE_FILE, certificate);
|
||||
} catch (AccessDeniedException accessException) {
|
||||
throw unreadableKeyConfigFile(accessException, CERTIFICATE_FILE, certificate);
|
||||
} catch (AccessControlException securityException) {
|
||||
throw blockedKeyConfigFile(securityException, environment, CERTIFICATE_FILE, certificate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,14 +103,23 @@ class PEMKeyConfig extends KeyConfig {
|
|||
@Override
|
||||
List<PrivateKey> privateKeys(@Nullable Environment environment) {
|
||||
try {
|
||||
return Collections.singletonList(readPrivateKey(CertParsingUtils.resolvePath(keyPath, environment), keyPassword));
|
||||
return Collections.singletonList(readPrivateKey(keyPath, keyPassword, environment));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("failed to read key", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static PrivateKey readPrivateKey(Path keyPath, SecureString keyPassword) throws IOException {
|
||||
return PemUtils.readPrivateKey(keyPath, keyPassword::getChars);
|
||||
private static PrivateKey readPrivateKey(String keyPath, SecureString keyPassword, Environment environment) throws IOException {
|
||||
final Path key = CertParsingUtils.resolvePath(keyPath, environment);
|
||||
try {
|
||||
return PemUtils.readPrivateKey(key, keyPassword::getChars);
|
||||
} catch (FileNotFoundException | NoSuchFileException fileException) {
|
||||
throw missingKeyConfigFile(fileException, KEY_FILE, key);
|
||||
} catch (AccessDeniedException accessException) {
|
||||
throw unreadableKeyConfigFile(accessException, KEY_FILE, key);
|
||||
} catch (AccessControlException securityException) {
|
||||
throw blockedKeyConfigFile(securityException, environment, KEY_FILE, key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,7 +14,10 @@ import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
|||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
@ -29,10 +32,13 @@ import java.util.Objects;
|
|||
*/
|
||||
class PEMTrustConfig extends TrustConfig {
|
||||
|
||||
private static final String CA_FILE = "certificate_authorities";
|
||||
|
||||
private final List<String> caPaths;
|
||||
|
||||
/**
|
||||
* Create a new trust configuration that is built from the certificate files
|
||||
*
|
||||
* @param caPaths the paths to the certificate files to trust
|
||||
*/
|
||||
PEMTrustConfig(List<String> caPaths) {
|
||||
|
@ -44,8 +50,17 @@ class PEMTrustConfig extends TrustConfig {
|
|||
try {
|
||||
Certificate[] certificates = CertParsingUtils.readCertificates(caPaths, environment);
|
||||
return CertParsingUtils.trustManager(certificates);
|
||||
} catch (NoSuchFileException noSuchFileException) {
|
||||
final Path missingPath = CertParsingUtils.resolvePath(noSuchFileException.getFile(), environment);
|
||||
throw missingTrustConfigFile(noSuchFileException, CA_FILE, missingPath);
|
||||
} catch (AccessDeniedException accessDeniedException) {
|
||||
final Path missingPath = CertParsingUtils.resolvePath(accessDeniedException.getFile(), environment);
|
||||
throw unreadableTrustConfigFile(accessDeniedException, CA_FILE, missingPath);
|
||||
} catch (AccessControlException accessControlException) {
|
||||
final List<Path> paths = CertParsingUtils.resolvePaths(caPaths, environment);
|
||||
throw blockedTrustConfigFile(accessControlException, environment, CA_FILE, paths);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
|
@ -72,7 +73,7 @@ public class PemUtils {
|
|||
* @param passwordSupplier A password supplier for the potentially encrypted (password protected) key
|
||||
* @return a private key from the contents of the file
|
||||
*/
|
||||
public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordSupplier) {
|
||||
public static PrivateKey readPrivateKey(Path keyPath, Supplier<char[]> passwordSupplier) throws IOException {
|
||||
try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
|
||||
String line = bReader.readLine();
|
||||
while (null != line && line.startsWith(HEADER) == false){
|
||||
|
@ -103,7 +104,7 @@ public class PemUtils {
|
|||
throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString() + ". File did not contain a " +
|
||||
"supported key format");
|
||||
}
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalStateException("Error parsing Private Key from: " + keyPath.toString(), e);
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +177,7 @@ public class PemUtils {
|
|||
line = bReader.readLine();
|
||||
}
|
||||
if (null == line || PKCS8_FOOTER.equals(line.trim()) == false) {
|
||||
throw new IOException("Malformed PEM file, PEM footer is invalid or missing");
|
||||
throw new KeyException("Malformed PEM file, PEM footer is invalid or missing");
|
||||
}
|
||||
byte[] keyBytes = Base64.getDecoder().decode(sb.toString());
|
||||
String keyAlgo = getKeyAlgorithmIdentifier(keyBytes);
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.apache.logging.log4j.LogManager;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.CheckedSupplier;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
@ -420,27 +421,33 @@ public class SSLService {
|
|||
sslSettingsMap.putAll(getRealmsSSLSettings(settings));
|
||||
sslSettingsMap.putAll(getMonitoringExporterSettings(settings));
|
||||
|
||||
sslSettingsMap.forEach((key, sslSettings) -> {
|
||||
final SSLConfiguration configuration = new SSLConfiguration(sslSettings);
|
||||
storeSslConfiguration(key, configuration);
|
||||
sslContextHolders.computeIfAbsent(configuration, this::createSslContext);
|
||||
});
|
||||
sslSettingsMap.forEach((key, sslSettings) -> loadConfiguration(key, sslSettings, sslContextHolders));
|
||||
|
||||
final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX);
|
||||
final SSLConfiguration transportSSLConfiguration = new SSLConfiguration(transportSSLSettings);
|
||||
final SSLConfiguration transportSSLConfiguration =
|
||||
loadConfiguration(XPackSettings.TRANSPORT_SSL_PREFIX, transportSSLSettings, sslContextHolders);
|
||||
this.transportSSLConfiguration.set(transportSSLConfiguration);
|
||||
storeSslConfiguration(XPackSettings.TRANSPORT_SSL_PREFIX, transportSSLConfiguration);
|
||||
Map<String, Settings> profileSettings = getTransportProfileSSLSettings(settings);
|
||||
sslContextHolders.computeIfAbsent(transportSSLConfiguration, this::createSslContext);
|
||||
profileSettings.forEach((key, profileSetting) -> {
|
||||
final SSLConfiguration configuration = new SSLConfiguration(profileSetting);
|
||||
storeSslConfiguration(key, configuration);
|
||||
sslContextHolders.computeIfAbsent(configuration, this::createSslContext);
|
||||
});
|
||||
profileSettings.forEach((key, profileSetting) -> loadConfiguration(key, profileSetting, sslContextHolders));
|
||||
|
||||
return Collections.unmodifiableMap(sslContextHolders);
|
||||
}
|
||||
|
||||
private SSLConfiguration loadConfiguration(String key, Settings settings, Map<SSLConfiguration, SSLContextHolder> contextHolders) {
|
||||
if (key.endsWith(".")) {
|
||||
// Drop trailing '.' so that any exception messages are consistent
|
||||
key = key.substring(0, key.length() - 1);
|
||||
}
|
||||
try {
|
||||
final SSLConfiguration configuration = new SSLConfiguration(settings);
|
||||
storeSslConfiguration(key, configuration);
|
||||
contextHolders.computeIfAbsent(configuration, this::createSslContext);
|
||||
return configuration;
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchSecurityException("failed to load SSL configuration [{}]", e, key);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeSslConfiguration(String key, SSLConfiguration configuration) {
|
||||
if (key.endsWith(".")) {
|
||||
key = key.substring(0, key.length() - 1);
|
||||
|
|
|
@ -14,17 +14,18 @@ import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
|||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
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;
|
||||
|
@ -38,6 +39,8 @@ import java.util.Objects;
|
|||
*/
|
||||
class StoreKeyConfig extends KeyConfig {
|
||||
|
||||
private static final String KEYSTORE_FILE = "keystore";
|
||||
|
||||
final String keyStorePath;
|
||||
final String keyStoreType;
|
||||
final SecureString keyStorePassword;
|
||||
|
@ -68,28 +71,42 @@ class StoreKeyConfig extends KeyConfig {
|
|||
|
||||
@Override
|
||||
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
|
||||
Path ksPath = keyStorePath == null ? null : CertParsingUtils.resolvePath(keyStorePath, environment);
|
||||
try {
|
||||
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
|
||||
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
|
||||
checkKeyStore(ks);
|
||||
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
|
||||
} catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
|
||||
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
|
||||
} catch (FileNotFoundException | NoSuchFileException e) {
|
||||
throw missingKeyConfigFile(e, KEYSTORE_FILE, ksPath);
|
||||
} catch (AccessDeniedException e) {
|
||||
throw unreadableKeyConfigFile(e, KEYSTORE_FILE, ksPath);
|
||||
} catch (AccessControlException e) {
|
||||
throw blockedKeyConfigFile(e, environment, KEYSTORE_FILE, ksPath);
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new ElasticsearchException("failed to initialize SSL KeyManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
final Path ksPath = CertParsingUtils.resolvePath(keyStorePath, environment);
|
||||
try {
|
||||
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
|
||||
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
|
||||
return CertParsingUtils.trustManager(ks, trustStoreAlgorithm);
|
||||
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
} catch (FileNotFoundException | NoSuchFileException e) {
|
||||
throw missingTrustConfigFile(e, KEYSTORE_FILE, ksPath);
|
||||
} catch (AccessDeniedException e) {
|
||||
throw missingTrustConfigFile(e, KEYSTORE_FILE, ksPath);
|
||||
} catch (AccessControlException e) {
|
||||
throw blockedTrustConfigFile(e, environment, KEYSTORE_FILE, Collections.singletonList(ksPath));
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
|
||||
final KeyStore trustStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
|
||||
final KeyStore trustStore = getStore(CertParsingUtils.resolvePath(keyStorePath, environment), keyStoreType, keyStorePassword);
|
||||
final List<CertificateInfo> certificates = new ArrayList<>();
|
||||
final Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
|
|
|
@ -13,8 +13,12 @@ import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
|||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
|
@ -31,6 +35,8 @@ import java.util.Objects;
|
|||
*/
|
||||
class StoreTrustConfig extends TrustConfig {
|
||||
|
||||
private static final String TRUSTSTORE_FILE = "truststore";
|
||||
|
||||
final String trustStorePath;
|
||||
final String trustStoreType;
|
||||
final SecureString trustStorePassword;
|
||||
|
@ -54,11 +60,18 @@ class StoreTrustConfig extends TrustConfig {
|
|||
|
||||
@Override
|
||||
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
|
||||
final Path storePath = CertParsingUtils.resolvePath(trustStorePath, environment);
|
||||
try {
|
||||
KeyStore trustStore = getStore(environment, trustStorePath, trustStoreType, trustStorePassword);
|
||||
KeyStore trustStore = getStore(storePath, trustStoreType, trustStorePassword);
|
||||
return CertParsingUtils.trustManager(trustStore, trustStoreAlgorithm);
|
||||
} catch (FileNotFoundException | NoSuchFileException e) {
|
||||
throw missingTrustConfigFile(e, TRUSTSTORE_FILE, storePath);
|
||||
} catch (AccessDeniedException e) {
|
||||
throw unreadableTrustConfigFile(e, TRUSTSTORE_FILE, storePath);
|
||||
} catch (AccessControlException e) {
|
||||
throw blockedTrustConfigFile(e, environment, TRUSTSTORE_FILE, Collections.singletonList(storePath));
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||
throw new ElasticsearchException("failed to initialize SSL TrustManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,12 @@ import org.elasticsearch.env.Environment;
|
|||
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
|
@ -65,12 +66,20 @@ abstract class TrustConfig {
|
|||
*/
|
||||
public abstract int hashCode();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getStore(Path, String, SecureString)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
KeyStore getStore(@Nullable Environment environment, @Nullable String storePath, String storeType, SecureString storePassword)
|
||||
throws GeneralSecurityException, IOException {
|
||||
return getStore(CertParsingUtils.resolvePath(storePath, environment), storeType, storePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and returns the appropriate {@link KeyStore} for the given configuration. The KeyStore can be backed by a file
|
||||
* in any format that the Security Provider might support, or a cryptographic software or hardware token in the case
|
||||
* of a PKCS#11 Provider.
|
||||
*
|
||||
* @param environment the environment to resolve files against or null in the case of running in a transport client
|
||||
* @param storePath the path to the {@link KeyStore} to load, or null if a PKCS11 token is configured as the keystore/truststore
|
||||
* of the JVM
|
||||
* @param storeType the type of the {@link KeyStore}
|
||||
|
@ -81,10 +90,9 @@ abstract class TrustConfig {
|
|||
* @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
|
||||
* @throws IOException if there is an I/O issue with the KeyStore data or the password is incorrect
|
||||
*/
|
||||
KeyStore getStore(@Nullable Environment environment, @Nullable String storePath, String storeType, SecureString storePassword)
|
||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||
KeyStore getStore(@Nullable Path storePath, String storeType, SecureString storePassword) throws IOException, GeneralSecurityException {
|
||||
if (null != storePath) {
|
||||
try (InputStream in = Files.newInputStream(CertParsingUtils.resolvePath(storePath, environment))) {
|
||||
try (InputStream in = Files.newInputStream(storePath)) {
|
||||
KeyStore ks = KeyStore.getInstance(storeType);
|
||||
ks.load(in, storePassword.getChars());
|
||||
return ks;
|
||||
|
@ -97,6 +105,44 @@ abstract class TrustConfig {
|
|||
throw new IllegalArgumentException("keystore.path or truststore.path can only be empty when using a PKCS#11 token");
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a new exception caused by a missing file, that is required for this trust config
|
||||
*/
|
||||
protected ElasticsearchException missingTrustConfigFile(IOException cause, String fileType, Path path) {
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL TrustManager - " + fileType + " file [{}] does not exist", cause, path.toAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a new exception caused by an unreadable file (i.e. file-system access denied), that is required for this trust config
|
||||
*/
|
||||
protected ElasticsearchException unreadableTrustConfigFile(AccessDeniedException cause, String fileType, Path path) {
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL TrustManager - not permitted to read " + fileType + " file [{}]", cause, path.toAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a new exception caused by a blocked file (i.e. security-manager access denied), that is required for this trust config
|
||||
* @param paths A list of possible files. Depending on the context, it may not be possible to know exactly which file caused the
|
||||
* exception, so this method accepts multiple paths.
|
||||
*/
|
||||
protected ElasticsearchException blockedTrustConfigFile(AccessControlException cause, Environment environment,
|
||||
String fileType, List<Path> paths) {
|
||||
if (paths.size() == 1) {
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL TrustManager - access to read {} file [{}] is blocked;" +
|
||||
" SSL resources should be placed in the [{}] directory",
|
||||
cause, fileType, paths.get(0).toAbsolutePath(), environment.configFile());
|
||||
} else {
|
||||
final String pathString = paths.stream().map(Path::toAbsolutePath).map(Path::toString).collect(Collectors.joining(", "));
|
||||
return new ElasticsearchException(
|
||||
"failed to initialize SSL TrustManager - access to read one or more of the {} files [{}] is blocked;" +
|
||||
" SSL resources should be placed in the [{}] directory",
|
||||
cause, fileType, pathString, environment.configFile());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A trust configuration that is a combination of a trust configuration with the default JDK trust configuration. This trust
|
||||
* configuration returns a trust manager verifies certificates against both the default JDK trusted configurations and the specific
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
*/
|
||||
package org.elasticsearch.test;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.CustomMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
|
@ -26,6 +29,39 @@ public class TestMatchers extends Matchers {
|
|||
};
|
||||
}
|
||||
|
||||
public static Matcher<Throwable> throwableWithMessage(String message) {
|
||||
return throwableWithMessage(CoreMatchers.equalTo(message));
|
||||
}
|
||||
|
||||
public static Matcher<Throwable> throwableWithMessage(Matcher<String> messageMatcher) {
|
||||
return new BaseMatcher<Throwable>() {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a throwable with message of ").appendDescriptionOf(messageMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object actual) {
|
||||
if (actual instanceof Throwable) {
|
||||
final Throwable throwable = (Throwable) actual;
|
||||
return messageMatcher.matches(throwable.getMessage());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description) {
|
||||
super.describeMismatch(item, description);
|
||||
if (item instanceof Throwable) {
|
||||
Throwable e = (Throwable) item;
|
||||
final StackTraceElement at = e.getStackTrace()[0];
|
||||
description.appendText(" at ").appendText(at.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Matcher<Predicate<T>> predicateMatches(T value) {
|
||||
return new CustomMatcher<Predicate<T>>("Matches " + value) {
|
||||
@Override
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.core.ssl;
|
|||
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -86,7 +87,7 @@ public class CertParsingUtilsTests extends ESTestCase {
|
|||
assertEquals("EC", cert.getPublicKey().getAlgorithm());
|
||||
}
|
||||
|
||||
private void verifyPrime256v1ECKey(Path keyPath) {
|
||||
private void verifyPrime256v1ECKey(Path keyPath) throws IOException {
|
||||
PrivateKey privateKey = PemUtils.readPrivateKey(keyPath, () -> null);
|
||||
assertEquals("EC", privateKey.getAlgorithm());
|
||||
assertThat(privateKey, instanceOf(ECPrivateKey.class));
|
||||
|
|
|
@ -69,6 +69,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
|
||||
|
@ -353,7 +354,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
|||
|
||||
latch.await();
|
||||
assertNotNull(exceptionRef.get());
|
||||
assertThat(exceptionRef.get().getMessage(), containsString("failed to initialize a KeyManagerFactory"));
|
||||
assertThat(exceptionRef.get(), throwableWithMessage(containsString("failed to initialize SSL KeyManager")));
|
||||
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||
}
|
||||
|
||||
|
@ -451,7 +452,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
|||
|
||||
latch.await();
|
||||
assertNotNull(exceptionRef.get());
|
||||
assertThat(exceptionRef.get().getMessage(), containsString("failed to initialize a TrustManagerFactory"));
|
||||
assertThat(exceptionRef.get(), throwableWithMessage(containsString("failed to initialize SSL TrustManager")));
|
||||
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||
}
|
||||
|
||||
|
@ -497,7 +498,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
|||
|
||||
latch.await();
|
||||
assertNotNull(exceptionRef.get());
|
||||
assertThat(exceptionRef.get().getMessage(), containsString("failed to initialize a TrustManagerFactory"));
|
||||
assertThat(exceptionRef.get(), throwableWithMessage(containsString("failed to initialize SSL TrustManager")));
|
||||
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
|
||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
@ -194,7 +195,8 @@ public class SSLServiceTests extends ESTestCase {
|
|||
sslService.createSSLEngine(configuration, null, -1);
|
||||
fail("expected an exception");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), containsString("failed to initialize a KeyManagerFactory"));
|
||||
assertThat(e, throwableWithMessage("failed to load SSL configuration [xpack.security.transport.ssl]"));
|
||||
assertThat(e.getCause(), throwableWithMessage(containsString("failed to initialize SSL KeyManager")));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,7 +328,8 @@ public class SSLServiceTests extends ESTestCase {
|
|||
.build();
|
||||
ElasticsearchException e =
|
||||
expectThrows(ElasticsearchException.class, () -> new SSLService(settings, env));
|
||||
assertThat(e.getMessage(), is("failed to initialize a TrustManagerFactory"));
|
||||
assertThat(e, throwableWithMessage("failed to load SSL configuration [xpack.security.transport.ssl]"));
|
||||
assertThat(e.getCause(), throwableWithMessage(containsString("failed to initialize SSL TrustManager")));
|
||||
}
|
||||
|
||||
public void testThatKeystorePasswordIsRequired() throws Exception {
|
||||
|
@ -336,7 +339,8 @@ public class SSLServiceTests extends ESTestCase {
|
|||
.build();
|
||||
ElasticsearchException e =
|
||||
expectThrows(ElasticsearchException.class, () -> new SSLService(settings, env));
|
||||
assertThat(e.getMessage(), is("failed to create trust manager"));
|
||||
assertThat(e, throwableWithMessage("failed to load SSL configuration [xpack.security.transport.ssl]"));
|
||||
assertThat(e.getCause(), throwableWithMessage("failed to create trust manager"));
|
||||
}
|
||||
|
||||
public void testCiphersAndInvalidCiphersWork() throws Exception {
|
||||
|
@ -369,9 +373,10 @@ public class SSLServiceTests extends ESTestCase {
|
|||
.setSecureSettings(secureSettings)
|
||||
.putList("xpack.security.transport.ssl.cipher_suites", new String[] { "foo", "bar" })
|
||||
.build();
|
||||
IllegalArgumentException e =
|
||||
expectThrows(IllegalArgumentException.class, () -> new SSLService(settings, env));
|
||||
assertThat(e.getMessage(), is("none of the ciphers [foo, bar] are supported by this JVM"));
|
||||
ElasticsearchException e =
|
||||
expectThrows(ElasticsearchException.class, () -> new SSLService(settings, env));
|
||||
assertThat(e, throwableWithMessage("failed to load SSL configuration [xpack.security.transport.ssl]"));
|
||||
assertThat(e.getCause(), throwableWithMessage("none of the ciphers [foo, bar] are supported by this JVM"));
|
||||
}
|
||||
|
||||
public void testThatSSLEngineHasCipherSuitesOrderSet() throws Exception {
|
||||
|
|
|
@ -17,6 +17,7 @@ import javax.net.ssl.X509ExtendedKeyManager;
|
|||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
@ -46,7 +47,7 @@ public class StoreKeyConfigTests extends ESTestCase {
|
|||
KeyManagerFactory.getDefaultAlgorithm(), TrustManagerFactory.getDefaultAlgorithm());
|
||||
ElasticsearchException ee = expectThrows(ElasticsearchException.class, () ->
|
||||
keyConfigPkcs11.createKeyManager(TestEnvironment.newEnvironment(settings)));
|
||||
assertThat(ee.getMessage(), containsString("failed to initialize a KeyManagerFactory"));
|
||||
assertThat(ee, throwableWithMessage(containsString("failed to initialize SSL KeyManager")));
|
||||
assertThat(ee.getCause().getMessage(), containsString("PKCS11 not found"));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.lucene.util.Constants;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
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.core.ssl.SSLService;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.security.AccessControlException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static org.elasticsearch.test.SecuritySettingsSource.addSecureSettings;
|
||||
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
/**
|
||||
* This is a suite of tests to ensure that meaningful error messages are generated for defined SSL configuration problems.
|
||||
*/
|
||||
public class SSLErrorMessageTests extends ESTestCase {
|
||||
|
||||
private Environment env;
|
||||
private Map<String, Path> paths;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build());
|
||||
paths = new HashMap<>();
|
||||
|
||||
requirePath("ca1.p12");
|
||||
requirePath("ca1.jks");
|
||||
requirePath("ca1.crt");
|
||||
|
||||
requirePath("cert1a.p12");
|
||||
requirePath("cert1a.jks");
|
||||
requirePath("cert1a.crt");
|
||||
requirePath("cert1a.key");
|
||||
}
|
||||
|
||||
public void testMessageForMissingKeystore() {
|
||||
checkMissingKeyManagerResource("keystore", "keystore.path", null);
|
||||
}
|
||||
|
||||
public void testMessageForMissingPemCertificate() {
|
||||
checkMissingKeyManagerResource("certificate", "certificate", withKey("cert1a.key"));
|
||||
}
|
||||
|
||||
public void testMessageForMissingPemKey() {
|
||||
checkMissingKeyManagerResource("key", "key", withCertificate("cert1a.crt"));
|
||||
}
|
||||
|
||||
public void testMessageForMissingTruststore() {
|
||||
checkMissingTrustManagerResource("truststore", "truststore.path");
|
||||
}
|
||||
|
||||
public void testMessageForMissingCertificateAuthorities() {
|
||||
checkMissingTrustManagerResource("certificate_authorities", "certificate_authorities");
|
||||
}
|
||||
|
||||
public void testMessageForKeystoreWithoutReadAccess() throws Exception {
|
||||
checkUnreadableKeyManagerResource("cert1a.p12", "keystore", "keystore.path", null);
|
||||
}
|
||||
|
||||
public void testMessageForPemCertificateWithoutReadAccess() throws Exception {
|
||||
checkUnreadableKeyManagerResource("cert1a.crt", "certificate", "certificate", withKey("cert1a.key"));
|
||||
}
|
||||
|
||||
public void testMessageForPemKeyWithoutReadAccess() throws Exception {
|
||||
checkUnreadableKeyManagerResource("cert1a.key", "key", "key", withCertificate("cert1a.crt"));
|
||||
}
|
||||
|
||||
public void testMessageForTruststoreWithoutReadAccess() throws Exception {
|
||||
checkUnreadableTrustManagerResource("cert1a.p12", "truststore", "truststore.path");
|
||||
}
|
||||
|
||||
public void testMessageForCertificateAuthoritiesWithoutReadAccess() throws Exception {
|
||||
checkUnreadableTrustManagerResource("ca1.crt", "certificate_authorities", "certificate_authorities");
|
||||
}
|
||||
|
||||
public void testMessageForKeyStoreOutsideConfigDir() throws Exception {
|
||||
checkBlockedKeyManagerResource("keystore", "keystore.path", null);
|
||||
}
|
||||
|
||||
public void testMessageForPemCertificateOutsideConfigDir() throws Exception {
|
||||
checkBlockedKeyManagerResource("certificate", "certificate", withKey("cert1a.key"));
|
||||
}
|
||||
|
||||
public void testMessageForPemKeyOutsideConfigDir() throws Exception {
|
||||
checkBlockedKeyManagerResource("key", "key", withCertificate("cert1a.crt"));
|
||||
}
|
||||
|
||||
public void testMessageForTrustStoreOutsideConfigDir() throws Exception {
|
||||
checkBlockedTrustManagerResource("truststore", "truststore.path");
|
||||
}
|
||||
|
||||
public void testMessageForCertificateAuthoritiesOutsideConfigDir() throws Exception {
|
||||
checkBlockedTrustManagerResource("certificate_authorities", "certificate_authorities");
|
||||
}
|
||||
|
||||
private void checkMissingKeyManagerResource(String fileType, String configKey, @Nullable Settings.Builder additionalSettings) {
|
||||
checkMissingResource("KeyManager", fileType, configKey,
|
||||
(prefix, builder) -> buildKeyConfigSettings(additionalSettings, prefix, builder));
|
||||
}
|
||||
|
||||
private void buildKeyConfigSettings(@Nullable Settings.Builder additionalSettings, String prefix, Settings.Builder builder) {
|
||||
configureWorkingTruststore(prefix, builder);
|
||||
if (additionalSettings != null) {
|
||||
builder.put(additionalSettings.normalizePrefix(prefix + ".").build());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMissingTrustManagerResource(String fileType, String configKey) {
|
||||
checkMissingResource("TrustManager", fileType, configKey, this::configureWorkingKeystore);
|
||||
}
|
||||
|
||||
private void checkUnreadableKeyManagerResource(String fromResource, String fileType, String configKey,
|
||||
@Nullable Settings.Builder additionalSettings) throws Exception {
|
||||
checkUnreadableResource("KeyManager", fromResource, fileType, configKey,
|
||||
(prefix, builder) -> buildKeyConfigSettings(additionalSettings, prefix, builder));
|
||||
}
|
||||
|
||||
private void checkUnreadableTrustManagerResource(String fromResource, String fileType, String configKey) throws Exception {
|
||||
checkUnreadableResource("TrustManager", fromResource, fileType, configKey, this::configureWorkingKeystore);
|
||||
}
|
||||
|
||||
private void checkBlockedKeyManagerResource(String fileType, String configKey, Settings.Builder additionalSettings) throws Exception {
|
||||
checkBlockedResource("KeyManager", fileType, configKey,
|
||||
(prefix, builder) -> buildKeyConfigSettings(additionalSettings, prefix, builder));
|
||||
}
|
||||
|
||||
private void checkBlockedTrustManagerResource(String fileType, String configKey) throws Exception {
|
||||
checkBlockedResource("TrustManager", fileType, configKey, this::configureWorkingKeystore);
|
||||
}
|
||||
|
||||
private void checkMissingResource(String sslManagerType, String fileType, String configKey,
|
||||
BiConsumer<String, Settings.Builder> configure) {
|
||||
final String prefix = randomSslPrefix();
|
||||
final Settings.Builder settings = Settings.builder();
|
||||
configure.accept(prefix, settings);
|
||||
|
||||
final String fileName = missingFile();
|
||||
final String key = prefix + "." + configKey;
|
||||
settings.put(key, fileName);
|
||||
|
||||
Throwable exception = expectFailure(settings);
|
||||
assertThat(exception, throwableWithMessage("failed to load SSL configuration [" + prefix + "]"));
|
||||
assertThat(exception, instanceOf(ElasticsearchSecurityException.class));
|
||||
|
||||
exception = exception.getCause();
|
||||
assertThat(exception, throwableWithMessage(
|
||||
"failed to initialize SSL " + sslManagerType + " - " + fileType + " file [" + fileName + "] does not exist"));
|
||||
assertThat(exception, instanceOf(ElasticsearchException.class));
|
||||
|
||||
exception = exception.getCause();
|
||||
assertThat(exception, instanceOf(NoSuchFileException.class));
|
||||
assertThat(exception, throwableWithMessage(fileName));
|
||||
}
|
||||
|
||||
private void checkUnreadableResource(String sslManagerType, String fromResource, String fileType, String configKey,
|
||||
BiConsumer<String, Settings.Builder> configure) throws Exception {
|
||||
final String prefix = randomSslPrefix();
|
||||
final Settings.Builder settings = Settings.builder();
|
||||
configure.accept(prefix, settings);
|
||||
|
||||
final String fileName = unreadableFile(fromResource);
|
||||
final String key = prefix + "." + configKey;
|
||||
settings.put(key, fileName);
|
||||
|
||||
Throwable exception = expectFailure(settings);
|
||||
assertThat(exception, throwableWithMessage("failed to load SSL configuration [" + prefix + "]"));
|
||||
assertThat(exception, instanceOf(ElasticsearchSecurityException.class));
|
||||
|
||||
exception = exception.getCause();
|
||||
assertThat(exception, throwableWithMessage(
|
||||
"failed to initialize SSL " + sslManagerType + " - not permitted to read " + fileType + " file [" + fileName + "]"));
|
||||
assertThat(exception, instanceOf(ElasticsearchException.class));
|
||||
|
||||
exception = exception.getCause();
|
||||
assertThat(exception, instanceOf(AccessDeniedException.class));
|
||||
assertThat(exception, throwableWithMessage(fileName));
|
||||
}
|
||||
|
||||
private void checkBlockedResource(String sslManagerType, String fileType, String configKey,
|
||||
BiConsumer<String, Settings.Builder> configure) throws Exception {
|
||||
final String prefix = randomSslPrefix();
|
||||
final Settings.Builder settings = Settings.builder();
|
||||
configure.accept(prefix, settings);
|
||||
|
||||
final String fileName = blockedFile();
|
||||
final String key = prefix + "." + configKey;
|
||||
settings.put(key, fileName);
|
||||
|
||||
Throwable exception = expectFailure(settings);
|
||||
assertThat(exception, throwableWithMessage("failed to load SSL configuration [" + prefix + "]"));
|
||||
assertThat(exception, instanceOf(ElasticsearchSecurityException.class));
|
||||
|
||||
exception = exception.getCause();
|
||||
assertThat(exception, throwableWithMessage(
|
||||
"failed to initialize SSL " + sslManagerType + " - access to read " + fileType + " file [" + fileName +
|
||||
"] is blocked; SSL resources should be placed in the [" + env.configFile() + "] directory"));
|
||||
assertThat(exception, instanceOf(ElasticsearchException.class));
|
||||
|
||||
exception = exception.getCause();
|
||||
assertThat(exception, instanceOf(AccessControlException.class));
|
||||
assertThat(exception, throwableWithMessage(containsString(fileName)));
|
||||
}
|
||||
|
||||
private String missingFile() {
|
||||
return resource("cert1a.p12").replace("cert1a.p12", "file.dne");
|
||||
}
|
||||
|
||||
private String unreadableFile(String fromResource) throws IOException {
|
||||
assumeFalse("This behaviour uses POSIX file permissions", Constants.WINDOWS);
|
||||
final Path fromPath = this.paths.get(fromResource);
|
||||
if (fromPath == null) {
|
||||
throw new IllegalArgumentException("Test SSL resource " + fromResource + " has not been loaded");
|
||||
}
|
||||
return copy(fromPath, createTempFile(fromResource, "-no-read"), PosixFilePermissions.fromString("---------"));
|
||||
}
|
||||
|
||||
private String blockedFile() throws IOException {
|
||||
return "/this/path/is/outside/the/config/directory/file.error";
|
||||
}
|
||||
|
||||
/**
|
||||
* This more-or-less replicates the functionality of {@link Files#copy(Path, Path, CopyOption...)} but doing it this way allows us to
|
||||
* set the file permissions when creating the file (which helps with security manager issues)
|
||||
*/
|
||||
private String copy(Path fromPath, Path toPath, Set<PosixFilePermission> permissions) throws IOException {
|
||||
Files.deleteIfExists(toPath);
|
||||
final FileAttribute<Set<PosixFilePermission>> fileAttributes = PosixFilePermissions.asFileAttribute(permissions);
|
||||
final EnumSet<StandardOpenOption> options = EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
|
||||
try (SeekableByteChannel channel = Files.newByteChannel(toPath, options, fileAttributes);
|
||||
OutputStream out = Channels.newOutputStream(channel)) {
|
||||
Files.copy(fromPath, out);
|
||||
}
|
||||
return toPath.toString();
|
||||
}
|
||||
|
||||
private Settings.Builder withKey(String fileName) {
|
||||
assertThat(fileName, endsWith(".key"));
|
||||
return Settings.builder().put("key", resource(fileName));
|
||||
}
|
||||
|
||||
private Settings.Builder withCertificate(String fileName) {
|
||||
assertThat(fileName, endsWith(".crt"));
|
||||
return Settings.builder().put("certificate", resource(fileName));
|
||||
}
|
||||
|
||||
private Settings.Builder configureWorkingTruststore(String prefix, Settings.Builder settings) {
|
||||
settings.put(prefix + ".truststore.path", resource("cert1a.p12"));
|
||||
addSecureSettings(settings, secure -> secure.setString(prefix + ".truststore.secure_password", "cert1a-p12-password"));
|
||||
return settings;
|
||||
}
|
||||
|
||||
private Settings.Builder configureWorkingKeystore(String prefix, Settings.Builder settings) {
|
||||
settings.put(prefix + ".keystore.path", resource("cert1a.p12"));
|
||||
addSecureSettings(settings, secure -> secure.setString(prefix + ".keystore.secure_password", "cert1a-p12-password"));
|
||||
return settings;
|
||||
}
|
||||
|
||||
private ElasticsearchException expectFailure(Settings.Builder settings) {
|
||||
return expectThrows(ElasticsearchException.class, () -> new SSLService(settings.build(), env));
|
||||
}
|
||||
|
||||
private String resource(String fileName) {
|
||||
final Path path = this.paths.get(fileName);
|
||||
if (path == null) {
|
||||
throw new IllegalArgumentException("Test SSL resource " + fileName + " has not been loaded");
|
||||
}
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
private void requirePath(String fileName) throws FileNotFoundException {
|
||||
final Path path = getDataPath("/org/elasticsearch/xpack/ssl/SSLServiceErrorMessageTests/" + fileName);
|
||||
if (Files.exists(path)) {
|
||||
paths.put(fileName, path);
|
||||
} else {
|
||||
throw new FileNotFoundException("File " + path + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private String randomSslPrefix() {
|
||||
return randomFrom(
|
||||
"xpack.security.transport.ssl",
|
||||
"xpack.security.http.ssl",
|
||||
"xpack.http.ssl",
|
||||
"xpack.security.authc.realms.ldap.ldap1.ssl",
|
||||
"xpack.security.authc.realms.saml.saml1.ssl",
|
||||
"xpack.monitoring.exporters.http.ssl"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
#
|
||||
# NOTE: This readme is also an executable shell script.
|
||||
# Run it with bash ./README.txt
|
||||
#
|
||||
|
||||
#
|
||||
# Make sure we can call certutil
|
||||
#
|
||||
[ -n "$ES_HOME" ] || { printf '%s: $ES_HOME is not set\n' "$0" ; exit 1; }
|
||||
[ -d "$ES_HOME" ] || { printf '%s: $ES_HOME is not a directory\n' "$0" ; exit 1; }
|
||||
|
||||
function certutil() { "$ES_HOME/bin/elasticsearch-certutil" "$@"; }
|
||||
|
||||
#
|
||||
# Helper functions to generate files & convert file types
|
||||
#
|
||||
function new-p12-ca() {
|
||||
local P12File="$1"
|
||||
local P12Pass="$2"
|
||||
local CaDn="$3"
|
||||
|
||||
certutil ca --ca-dn="$CaDn" --days=5000 --out ${PWD}/$P12File --pass="$P12Pass"
|
||||
}
|
||||
|
||||
function new-p12-cert() {
|
||||
local CertFile="$1"
|
||||
local CertPass="$2"
|
||||
local CertName="$3"
|
||||
local CaFile="$4"
|
||||
local CaPass="$5"
|
||||
shift 5
|
||||
|
||||
certutil cert --ca="${PWD}/$CaFile" --ca-pass="$CaPass" --days=5000 --out ${PWD}/$CertFile --pass="$CertPass" --name="$CertName" "$@"
|
||||
}
|
||||
|
||||
function p12-to-jks() {
|
||||
local P12File="$1"
|
||||
local P12Pass="$2"
|
||||
local JksFile="$3"
|
||||
local JksPass="$4"
|
||||
|
||||
keytool -importkeystore -srckeystore "${PWD}/$P12File" -srcstorepass "$P12Pass" \
|
||||
-destkeystore "${PWD}/$JksFile" -deststoretype JKS -deststorepass "$JksPass"
|
||||
}
|
||||
|
||||
function p12-export-cert() {
|
||||
local P12File="$1"
|
||||
local P12Pass="$2"
|
||||
local P12Name="$3"
|
||||
local PemFile="$4"
|
||||
|
||||
keytool -exportcert -keystore "${PWD}/$P12File" -storepass "$P12Pass" -alias "$P12Name" \
|
||||
-rfc -file "${PWD}/$PemFile"
|
||||
}
|
||||
|
||||
function p12-export-pair() {
|
||||
local P12File="$1"
|
||||
local P12Pass="$2"
|
||||
local P12Name="$3"
|
||||
local CrtFile="$4"
|
||||
local KeyFile="$5"
|
||||
|
||||
local TmpFile="${PWD}/$(basename $P12File .p12).tmp.p12"
|
||||
|
||||
# OpenSSL doesn't have a way to export a single entry
|
||||
# Keytool doesn't have a way to export keys
|
||||
# So we use keytool to export the whole entry to a temporary PKCS#12 and then use openssl to export that to PEM
|
||||
|
||||
keytool -importkeystore -srckeystore "${PWD}/$P12File" -srcstorepass "$P12Pass" -srcalias "$P12Name" \
|
||||
-destkeystore "$TmpFile" -deststorepass "tmp_password"
|
||||
|
||||
# This produces an unencrypted PKCS#1 key. Use other commands to convert it if needed
|
||||
# The sed is to skip "BagAttributes" which we don't need
|
||||
openssl pkcs12 -in "$TmpFile" -nodes -nocerts -passin "pass:tmp_password" | sed -n -e'/^-----/,/^-----/p' > $KeyFile
|
||||
openssl pkcs12 -in "$TmpFile" -clcerts -nokeys -passin "pass:tmp_password" | sed -n -e'/^-----/,/^-----/p' > $CrtFile
|
||||
|
||||
rm $TmpFile
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Create a CA in PKCS#12
|
||||
#
|
||||
new-p12-ca ca1.p12 "ca1-p12-password" 'CN=Certificate Authority 1,OU=ssl-error-message-test,DC=elastic,DC=co'
|
||||
|
||||
# Make a JKS version of the CA
|
||||
p12-to-jks ca1.p12 "ca1-p12-password" ca1.jks "ca1-jks-password"
|
||||
|
||||
# Make a PEM version of the CA cert
|
||||
p12-export-cert ca1.p12 "ca1-p12-password" "ca" ca1.crt
|
||||
|
||||
#
|
||||
# Create a Cert/Key Pair in PKCS#12
|
||||
# - "cert1a" is signed by "ca1"
|
||||
# - "cert1a.p12" is password protected, and can act as a keystore or truststore
|
||||
#
|
||||
new-p12-cert cert1a.p12 "cert1a-p12-password" "cert1a" "ca1.p12" "ca1-p12-password"
|
||||
|
||||
# Convert to JKS
|
||||
# - "cert1a.jks" is password protected, and can act as a keystore or truststore
|
||||
p12-to-jks cert1a.p12 "cert1a-p12-password" cert1a.jks "cert1a-jks-password"
|
||||
|
||||
# Convert to PEM
|
||||
# - "cert1a.key" is an (unprotected) PKCS#1 key
|
||||
p12-export-pair cert1a.p12 "cert1a-p12-password" "cert1a" cert1a.crt cert1a.key
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDwTCCAqmgAwIBAgIUK67pJtem4zfhIP4SpjoukdLlwnIwDQYJKoZIhvcNAQEL
|
||||
BQAwcDESMBAGCgmSJomT8ixkARkWAmNvMRcwFQYKCZImiZPyLGQBGRYHZWxhc3Rp
|
||||
YzEfMB0GA1UECxMWc3NsLWVycm9yLW1lc3NhZ2UtdGVzdDEgMB4GA1UEAxMXQ2Vy
|
||||
dGlmaWNhdGUgQXV0aG9yaXR5IDEwHhcNMTkwNzE4MDc1NDMyWhcNMzMwMzI2MDc1
|
||||
NDMyWjBwMRIwEAYKCZImiZPyLGQBGRYCY28xFzAVBgoJkiaJk/IsZAEZFgdlbGFz
|
||||
dGljMR8wHQYDVQQLExZzc2wtZXJyb3ItbWVzc2FnZS10ZXN0MSAwHgYDVQQDExdD
|
||||
ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBAK/yaI+3JTakaGHrUBlVhvAP7Jeejkw0XevcRr4H96pO6fEZuq8Kkf73
|
||||
/J/wZkzTqE6AGxVYGJV7brzZj8QwXPnkI4fCOLYLeBBX4333lb+X20RGmEjLeTKK
|
||||
XkOA4FlpbDaecQwbUArBPF3sc2AXW62ZWEosUTR67wr3tFn3uX1RnX5OM0+7qI0Z
|
||||
gcEm8ohqlig3NC7EmkuP/50CA2OnuuD8b57u0cdgM7uFXTzIASUCz5RU7SSYiI0q
|
||||
HYOPza+CfUgevReRDc9rzIIVcxMmbab6gABysKGbUdlSTsqGqZMbEprFTv7zt7u1
|
||||
lqHeVEjhL7F9769XGaqFeGfa9b1odokCAwEAAaNTMFEwHQYDVR0OBBYEFKHpV7oS
|
||||
AMlY64s6FbHLPWYtoClTMB8GA1UdIwQYMBaAFKHpV7oSAMlY64s6FbHLPWYtoClT
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAg9Rq5QIiFpmmIN
|
||||
yMMeSVCeqZJ1l03WO8YPNcMVGzvjsm12UCHK2ppCahOerGOchnX6tdsjj2aKIL0n
|
||||
L8++LSDgxbsDA8kxkSldjcY8sJZhTMzvfUkabmWvr99UpVRDKT3EXkMmSaSIaA+U
|
||||
1xEJAz8dPPFTXyxCNwEePyvy32I3JR6e/8UnUTVNlev8sOdwGvidqHq/PJvR4/qD
|
||||
B5bn39gsQ6OgUaT6ye6Zp3iUx5uNipGwr2SzoK6ERJ2rwEC1/mQ321rDkSSdnHLT
|
||||
E+hVL/qrGkg5cl0otPDFxIkOL33/lyyOW7cHdHEUkFsl9Osi6O0HFw1z4DoR7zT3
|
||||
Ngckq2E=
|
||||
-----END CERTIFICATE-----
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIVAOsqJ9r+RWgkCe7vyZXYmF+sfBoNMA0GCSqGSIb3DQEB
|
||||
CwUAMHAxEjAQBgoJkiaJk/IsZAEZFgJjbzEXMBUGCgmSJomT8ixkARkWB2VsYXN0
|
||||
aWMxHzAdBgNVBAsTFnNzbC1lcnJvci1tZXNzYWdlLXRlc3QxIDAeBgNVBAMTF0Nl
|
||||
cnRpZmljYXRlIEF1dGhvcml0eSAxMB4XDTE5MDcxODA3NTQzNVoXDTMzMDMyNjA3
|
||||
NTQzNVowETEPMA0GA1UEAxMGY2VydDFhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAxZy4TEULFQxk988qZ4itwYo5oRnTfzrL5/Rb516Ll5Q9RrXnIFyg
|
||||
u1CtF/eX0S1BGw9MCDSFK9fpwQMbDKo0G6E9arV9hTMo2MHMOhRXux6qnQlYzxW+
|
||||
7dfpzoLQPb0ObYlvC9LclJwz2Hqz4qABSMfbHd789Y9CjHsNPb+9OZDMj6oJBT/G
|
||||
nXiZxk0bJ/O8N1BN9nDUWnhqsDuuHVW30PuF0JVwHzqMqi/FVporabGcYnur+Pbd
|
||||
kevpidWFFx6kpoAeXuYWPEeqTLiUpPyMwxNn620xUbr4bq8hEOkhswsdhZ90ffbv
|
||||
lMZezS7vL5zBOGEtnGFXh0FaHfutZwX2nwIDAQABo00wSzAdBgNVHQ4EFgQUCcIu
|
||||
5sjyXxEoFfz7zz3CVdpPGTMwHwYDVR0jBBgwFoAUoelXuhIAyVjrizoVscs9Zi2g
|
||||
KVMwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAUtXdkttqA4BVyVhfjiOg
|
||||
LA43V26gl/NsjVp/8SpEYahp/FPl2wQe3D/6Py9a0eVIdJQ7vwx15RqyeNi679Lg
|
||||
hQxyh/dvLlnxhOY1LHAwc2v4zDr96h/JA8FeqemXNPmEIwdy930rZpgA/O/9dlh/
|
||||
pVSRxg5mrNL1ekyzty44IOnbJ3URLyQmNOns3d6AKxwIxgkM041lex9NXsyw7gaz
|
||||
GnxhORukdbuxW2zqweVKoTTo2AriUWic8FnZDNeaHgJ8LP1xvYECq1cb3S1qzzEO
|
||||
LCofq9Lvxzemc/Q435khAvFAA4oB2qV37eZn3B1HT2RiPzHvM3ypjqhIAbqKX++w
|
||||
AA==
|
||||
-----END CERTIFICATE-----
|
Binary file not shown.
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAxZy4TEULFQxk988qZ4itwYo5oRnTfzrL5/Rb516Ll5Q9RrXn
|
||||
IFygu1CtF/eX0S1BGw9MCDSFK9fpwQMbDKo0G6E9arV9hTMo2MHMOhRXux6qnQlY
|
||||
zxW+7dfpzoLQPb0ObYlvC9LclJwz2Hqz4qABSMfbHd789Y9CjHsNPb+9OZDMj6oJ
|
||||
BT/GnXiZxk0bJ/O8N1BN9nDUWnhqsDuuHVW30PuF0JVwHzqMqi/FVporabGcYnur
|
||||
+PbdkevpidWFFx6kpoAeXuYWPEeqTLiUpPyMwxNn620xUbr4bq8hEOkhswsdhZ90
|
||||
ffbvlMZezS7vL5zBOGEtnGFXh0FaHfutZwX2nwIDAQABAoIBAQCfB1lVn7akcL4M
|
||||
o36HpXnXqCpqmIMY/7M67u7LCs4h5R1O+3KOG50KQYmbIRjfMKEVasEQVVvahb7L
|
||||
InmxPoQCvEbVykrCWAKGNafqEZbssmgxSmVa+jAV7k1tcN6u4KdsxU5FYKM9QVuD
|
||||
2nNLbOK7tIKEzoAaCflPXnOwfs6ENLYz5vu96RAI5u8dn7GDw9gjuOF5Ij+IpY38
|
||||
HBqrAhmZs4P7VFO03XwVXvuxEmuJ4Ng9n/bd+D8dO9WVLhRCNPY6HIyUTtRyHyvY
|
||||
hW+ejkgBjrpdCh6jmWKUz3aD1m4QY1NSmuYvXCLJjeO9PLHj8Qib7BSET4/ZvLcn
|
||||
xGmFlpDZAoGBAOLcV2PTYHeRSkgd1l3HRYV022TZLdqPsKs4OQnG0oz3OoLt8nBW
|
||||
85VhDVYIgWF7Ro6SQBJUGUtCy/8pCySGcmm2RrS9VSunuiLr1mFTyZEg6Py5LixI
|
||||
fimr8R5ZZp3yQtyDR/rX62z/F76HT87lBtAkc9gz50ceeHIbYjUK9zotAoGBAN7+
|
||||
oB7JJnFtkKd7b17wEqz7xsGaeXKrZPt9LyTG0/3hTlqRKs51+TEo11R3sH6ny/so
|
||||
VcobEnvLWJ1slRMFxbJTI2gQYjkRdxbgRKlL3D7nkXP6ZkyPTqZWHwnmL25HvkfT
|
||||
Cg5t6uuUwu7zBx8pgvSVI7+ohNbIQihOuvSzwe97AoGAPz/vnYVxf+SiMTkga0UD
|
||||
vRG3mYZzolwthY9HV9J3IZZMWfS9g1S61QsaDMKST/tu0JE2C+YmpzUYayumT6is
|
||||
fYy0ae/fryw0WUgnnTsfF7d1PgDjPrV3d2bY8v05/w9sVM7FYsqQePMmS5iuR6DT
|
||||
JXUEV2MhOFLmgengl6lXBIECgYEApaMs/LEdRRPFZyJgz3wz4xoBwL0liO+Wytdp
|
||||
tT+bJ/G7abp6uXEX8FQN3kgpaWrqMVz8nGsPh7S71fNZqA+ZkaP/oMTKsOkwNGXR
|
||||
mo8mwfLwL93HLwcjvJor5AZ/JMNVq7QuvBkZbnPJeAQ9PgBvrY06SJq8UW3e9mHE
|
||||
rQ749xECgYA84wbJYvOBDzXq9QFBVgCI46i7rDH7L8fxSU5kXYqG8hQ0DMmjXPE1
|
||||
aBD5a0qbeMbaA3yVYdtgSK2T47Kq+k/DE5Q/EFshuLShX9iEmB2WeNtLvTl4AO0i
|
||||
9pwaI9uyzZCtK4bOUMq3Anf6AFnlrPRiXdhQYMrNQlc7cPULdawAtw==
|
||||
-----END RSA PRIVATE KEY-----
|
Binary file not shown.
Loading…
Reference in New Issue