mirror of https://github.com/grpc/grpc-java.git
security: Stabilize AdvancedTlsX509KeyManager. (#11139)
* Clean up and de-experimentalization of KeyManager * Unit tests for API validity.
This commit is contained in:
parent
df8cfe9ddc
commit
781b4c4575
|
|
@ -45,14 +45,11 @@ import io.grpc.util.AdvancedTlsX509TrustManager.Verification;
|
||||||
import io.grpc.util.CertificateUtils;
|
import io.grpc.util.CertificateUtils;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
@ -65,13 +62,13 @@ import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class AdvancedTlsTest {
|
public class AdvancedTlsTest {
|
||||||
public static final String SERVER_0_KEY_FILE = "server0.key";
|
private static final String SERVER_0_KEY_FILE = "server0.key";
|
||||||
public static final String SERVER_0_PEM_FILE = "server0.pem";
|
private static final String SERVER_0_PEM_FILE = "server0.pem";
|
||||||
public static final String CLIENT_0_KEY_FILE = "client.key";
|
private static final String CLIENT_0_KEY_FILE = "client.key";
|
||||||
public static final String CLIENT_0_PEM_FILE = "client.pem";
|
private static final String CLIENT_0_PEM_FILE = "client.pem";
|
||||||
public static final String CA_PEM_FILE = "ca.pem";
|
private static final String CA_PEM_FILE = "ca.pem";
|
||||||
public static final String SERVER_BAD_KEY_FILE = "badserver.key";
|
private static final String SERVER_BAD_KEY_FILE = "badserver.key";
|
||||||
public static final String SERVER_BAD_PEM_FILE = "badserver.pem";
|
private static final String SERVER_BAD_PEM_FILE = "badserver.pem";
|
||||||
|
|
||||||
private ScheduledExecutorService executor;
|
private ScheduledExecutorService executor;
|
||||||
private Server server;
|
private Server server;
|
||||||
|
|
@ -92,7 +89,7 @@ public class AdvancedTlsTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
throws NoSuchAlgorithmException, IOException, CertificateException, InvalidKeySpecException {
|
throws Exception {
|
||||||
executor = Executors.newSingleThreadScheduledExecutor();
|
executor = Executors.newSingleThreadScheduledExecutor();
|
||||||
caCertFile = TestUtils.loadCert(CA_PEM_FILE);
|
caCertFile = TestUtils.loadCert(CA_PEM_FILE);
|
||||||
serverKey0File = TestUtils.loadCert(SERVER_0_KEY_FILE);
|
serverKey0File = TestUtils.loadCert(SERVER_0_KEY_FILE);
|
||||||
|
|
@ -285,11 +282,11 @@ public class AdvancedTlsTest {
|
||||||
new SslSocketAndEnginePeerVerifier() {
|
new SslSocketAndEnginePeerVerifier() {
|
||||||
@Override
|
@Override
|
||||||
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
||||||
Socket socket) throws CertificateException { }
|
Socket socket) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
||||||
SSLEngine engine) throws CertificateException { }
|
SSLEngine engine) { }
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
serverTrustManager.updateTrustCredentials(caCert);
|
serverTrustManager.updateTrustCredentials(caCert);
|
||||||
|
|
@ -310,11 +307,11 @@ public class AdvancedTlsTest {
|
||||||
new SslSocketAndEnginePeerVerifier() {
|
new SslSocketAndEnginePeerVerifier() {
|
||||||
@Override
|
@Override
|
||||||
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
||||||
Socket socket) throws CertificateException { }
|
Socket socket) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
public void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType,
|
||||||
SSLEngine engine) throws CertificateException { }
|
SSLEngine engine) { }
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
clientTrustManager.updateTrustCredentials(caCert);
|
clientTrustManager.updateTrustCredentials(caCert);
|
||||||
|
|
@ -419,7 +416,7 @@ public class AdvancedTlsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onFileReloadingKeyManagerBadInitialContentTest() throws Exception {
|
public void onFileReloadingKeyManagerBadInitialContentTest() {
|
||||||
AdvancedTlsX509KeyManager keyManager = new AdvancedTlsX509KeyManager();
|
AdvancedTlsX509KeyManager keyManager = new AdvancedTlsX509KeyManager();
|
||||||
// We swap the order of key and certificates to intentionally create an exception.
|
// We swap the order of key and certificates to intentionally create an exception.
|
||||||
assertThrows(GeneralSecurityException.class,
|
assertThrows(GeneralSecurityException.class,
|
||||||
|
|
@ -439,7 +436,7 @@ public class AdvancedTlsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void keyManagerAliasesTest() throws Exception {
|
public void keyManagerAliasesTest() {
|
||||||
AdvancedTlsX509KeyManager km = new AdvancedTlsX509KeyManager();
|
AdvancedTlsX509KeyManager km = new AdvancedTlsX509KeyManager();
|
||||||
assertArrayEquals(
|
assertArrayEquals(
|
||||||
new String[] {"default"}, km.getClientAliases("", null));
|
new String[] {"default"}, km.getClientAliases("", null));
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package io.grpc.util;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import io.grpc.ExperimentalApi;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -26,7 +25,6 @@ import java.net.Socket;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
@ -39,20 +37,15 @@ import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AdvancedTlsX509KeyManager is an {@code X509ExtendedKeyManager} that allows users to configure
|
* AdvancedTlsX509KeyManager is an {@code X509ExtendedKeyManager} that allows users to configure
|
||||||
* advanced TLS features, such as private key and certificate chain reloading, etc.
|
* advanced TLS features, such as private key and certificate chain reloading.
|
||||||
*/
|
*/
|
||||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
|
|
||||||
public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
|
public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
|
||||||
private static final Logger log = Logger.getLogger(AdvancedTlsX509KeyManager.class.getName());
|
private static final Logger log = Logger.getLogger(AdvancedTlsX509KeyManager.class.getName());
|
||||||
|
// Minimum allowed period for refreshing files with credential information.
|
||||||
// The credential information sent to peers to prove our identity.
|
private static final int MINIMUM_REFRESH_PERIOD_IN_MINUTES = 1 ;
|
||||||
|
// The credential information to be sent to peers to prove our identity.
|
||||||
private volatile KeyInfo keyInfo;
|
private volatile KeyInfo keyInfo;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an AdvancedTlsX509KeyManager.
|
|
||||||
*/
|
|
||||||
public AdvancedTlsX509KeyManager() throws CertificateException { }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PrivateKey getPrivateKey(String alias) {
|
public PrivateKey getPrivateKey(String alias) {
|
||||||
if (alias.equals("default")) {
|
if (alias.equals("default")) {
|
||||||
|
|
@ -107,14 +100,17 @@ public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
|
||||||
* @param certs the certificate chain that is going to be used
|
* @param certs the certificate chain that is going to be used
|
||||||
*/
|
*/
|
||||||
public void updateIdentityCredentials(PrivateKey key, X509Certificate[] certs) {
|
public void updateIdentityCredentials(PrivateKey key, X509Certificate[] certs) {
|
||||||
// TODO(ZhenLian): explore possibilities to do a crypto check here.
|
|
||||||
this.keyInfo = new KeyInfo(checkNotNull(key, "key"), checkNotNull(certs, "certs"));
|
this.keyInfo = new KeyInfo(checkNotNull(key, "key"), checkNotNull(certs, "certs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a {@code ScheduledExecutorService} to read private key and certificate chains from
|
* Schedules a {@code ScheduledExecutorService} to read private key and certificate chains from
|
||||||
* the local file paths periodically, and update the cached identity credentials if they are both
|
* the local file paths periodically, and update the cached identity credentials if they are both
|
||||||
* updated.
|
* updated. You must close the returned Closeable before calling this method again or other update
|
||||||
|
* methods ({@link AdvancedTlsX509KeyManager#updateIdentityCredentials}, {@link
|
||||||
|
* AdvancedTlsX509KeyManager#updateIdentityCredentialsFromFile(File, File)}).
|
||||||
|
* Before scheduling the task, the method synchronously executes {@code readAndUpdate} once. The
|
||||||
|
* minimum refresh period of 1 minute is enforced.
|
||||||
*
|
*
|
||||||
* @param keyFile the file on disk holding the private key
|
* @param keyFile the file on disk holding the private key
|
||||||
* @param certFile the file on disk holding the certificate chain
|
* @param certFile the file on disk holding the certificate chain
|
||||||
|
|
@ -131,14 +127,17 @@ public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
|
||||||
throw new GeneralSecurityException(
|
throw new GeneralSecurityException(
|
||||||
"Files were unmodified before their initial update. Probably a bug.");
|
"Files were unmodified before their initial update. Probably a bug.");
|
||||||
}
|
}
|
||||||
|
if (checkNotNull(unit, "unit").toMinutes(period) < MINIMUM_REFRESH_PERIOD_IN_MINUTES) {
|
||||||
|
log.log(Level.FINE,
|
||||||
|
"Provided refresh period of {0} {1} is too small. Default value of {2} minute(s) "
|
||||||
|
+ "will be used.", new Object[] {period, unit.name(), MINIMUM_REFRESH_PERIOD_IN_MINUTES});
|
||||||
|
period = MINIMUM_REFRESH_PERIOD_IN_MINUTES;
|
||||||
|
unit = TimeUnit.MINUTES;
|
||||||
|
}
|
||||||
final ScheduledFuture<?> future =
|
final ScheduledFuture<?> future =
|
||||||
executor.scheduleWithFixedDelay(
|
checkNotNull(executor, "executor").scheduleWithFixedDelay(
|
||||||
new LoadFilePathExecution(keyFile, certFile), period, period, unit);
|
new LoadFilePathExecution(keyFile, certFile), period, period, unit);
|
||||||
return new Closeable() {
|
return () -> future.cancel(false);
|
||||||
@Override public void close() {
|
|
||||||
future.cancel(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -190,8 +189,9 @@ public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
|
||||||
this.currentCertTime = newResult.certTime;
|
this.currentCertTime = newResult.certTime;
|
||||||
}
|
}
|
||||||
} catch (IOException | GeneralSecurityException e) {
|
} catch (IOException | GeneralSecurityException e) {
|
||||||
log.log(Level.SEVERE, "Failed refreshing private key and certificate chain from files. "
|
log.log(Level.SEVERE, e, () -> String.format("Failed refreshing private key and certificate"
|
||||||
+ "Using previous ones", e);
|
+ " chain from files. Using previous ones (keyFile lastModified = %s, certFile "
|
||||||
|
+ "lastModified = %s)", keyFile.lastModified(), certFile.lastModified()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,8 +220,8 @@ public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
|
||||||
*/
|
*/
|
||||||
private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime, long oldCertTime)
|
private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime, long oldCertTime)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
long newKeyTime = keyFile.lastModified();
|
long newKeyTime = checkNotNull(keyFile, "keyFile").lastModified();
|
||||||
long newCertTime = certFile.lastModified();
|
long newCertTime = checkNotNull(certFile, "certFile").lastModified();
|
||||||
// We only update when both the key and the certs are updated.
|
// We only update when both the key and the certs are updated.
|
||||||
if (newKeyTime != oldKeyTime && newCertTime != oldCertTime) {
|
if (newKeyTime != oldKeyTime && newCertTime != oldCertTime) {
|
||||||
FileInputStream keyInputStream = new FileInputStream(keyFile);
|
FileInputStream keyInputStream = new FileInputStream(keyFile);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import io.grpc.internal.FakeClock;
|
||||||
|
import io.grpc.internal.testing.TestUtils;
|
||||||
|
import io.grpc.testing.TlsTesting;
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link AdvancedTlsX509KeyManager}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class AdvancedTlsX509KeyManagerTest {
|
||||||
|
private static final String SERVER_0_KEY_FILE = "server0.key";
|
||||||
|
private static final String SERVER_0_PEM_FILE = "server0.pem";
|
||||||
|
private static final String CLIENT_0_KEY_FILE = "client.key";
|
||||||
|
private static final String CLIENT_0_PEM_FILE = "client.pem";
|
||||||
|
private static final String ALIAS = "default";
|
||||||
|
|
||||||
|
private ScheduledExecutorService executor;
|
||||||
|
|
||||||
|
private File serverKey0File;
|
||||||
|
private File serverCert0File;
|
||||||
|
private File clientKey0File;
|
||||||
|
private File clientCert0File;
|
||||||
|
|
||||||
|
private PrivateKey serverKey0;
|
||||||
|
private X509Certificate[] serverCert0;
|
||||||
|
private PrivateKey clientKey0;
|
||||||
|
private X509Certificate[] clientCert0;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
executor = new FakeClock().getScheduledExecutorService();
|
||||||
|
serverKey0File = TestUtils.loadCert(SERVER_0_KEY_FILE);
|
||||||
|
serverCert0File = TestUtils.loadCert(SERVER_0_PEM_FILE);
|
||||||
|
clientKey0File = TestUtils.loadCert(CLIENT_0_KEY_FILE);
|
||||||
|
clientCert0File = TestUtils.loadCert(CLIENT_0_PEM_FILE);
|
||||||
|
serverKey0 = CertificateUtils.getPrivateKey(TlsTesting.loadCert(SERVER_0_KEY_FILE));
|
||||||
|
serverCert0 = CertificateUtils.getX509Certificates(TlsTesting.loadCert(SERVER_0_PEM_FILE));
|
||||||
|
clientKey0 = CertificateUtils.getPrivateKey(TlsTesting.loadCert(CLIENT_0_KEY_FILE));
|
||||||
|
clientCert0 = CertificateUtils.getX509Certificates(TlsTesting.loadCert(CLIENT_0_PEM_FILE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void credentialSetting() throws Exception {
|
||||||
|
// Overall happy path checking of public API.
|
||||||
|
AdvancedTlsX509KeyManager serverKeyManager = new AdvancedTlsX509KeyManager();
|
||||||
|
serverKeyManager.updateIdentityCredentials(serverKey0, serverCert0);
|
||||||
|
assertEquals(serverKey0, serverKeyManager.getPrivateKey(ALIAS));
|
||||||
|
assertArrayEquals(serverCert0, serverKeyManager.getCertificateChain(ALIAS));
|
||||||
|
|
||||||
|
serverKeyManager.updateIdentityCredentialsFromFile(clientKey0File, clientCert0File);
|
||||||
|
assertEquals(clientKey0, serverKeyManager.getPrivateKey(ALIAS));
|
||||||
|
assertArrayEquals(clientCert0, serverKeyManager.getCertificateChain(ALIAS));
|
||||||
|
|
||||||
|
serverKeyManager.updateIdentityCredentialsFromFile(serverKey0File, serverCert0File, 1,
|
||||||
|
TimeUnit.MINUTES, executor);
|
||||||
|
assertEquals(serverKey0, serverKeyManager.getPrivateKey(ALIAS));
|
||||||
|
assertArrayEquals(serverCert0, serverKeyManager.getCertificateChain(ALIAS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void credentialSettingParameterValidity() throws Exception {
|
||||||
|
// Checking edge cases of public API parameter setting.
|
||||||
|
AdvancedTlsX509KeyManager serverKeyManager = new AdvancedTlsX509KeyManager();
|
||||||
|
NullPointerException npe = assertThrows(NullPointerException.class, () -> serverKeyManager
|
||||||
|
.updateIdentityCredentials(null, serverCert0));
|
||||||
|
assertEquals("key", npe.getMessage());
|
||||||
|
|
||||||
|
npe = assertThrows(NullPointerException.class, () -> serverKeyManager
|
||||||
|
.updateIdentityCredentials(serverKey0, null));
|
||||||
|
assertEquals("certs", npe.getMessage());
|
||||||
|
|
||||||
|
npe = assertThrows(NullPointerException.class, () -> serverKeyManager
|
||||||
|
.updateIdentityCredentialsFromFile(null, serverCert0File));
|
||||||
|
assertEquals("keyFile", npe.getMessage());
|
||||||
|
|
||||||
|
npe = assertThrows(NullPointerException.class, () -> serverKeyManager
|
||||||
|
.updateIdentityCredentialsFromFile(serverKey0File, null));
|
||||||
|
assertEquals("certFile", npe.getMessage());
|
||||||
|
|
||||||
|
npe = assertThrows(NullPointerException.class, () -> serverKeyManager
|
||||||
|
.updateIdentityCredentialsFromFile(serverKey0File, serverCert0File, 1, null,
|
||||||
|
executor));
|
||||||
|
assertEquals("unit", npe.getMessage());
|
||||||
|
|
||||||
|
npe = assertThrows(NullPointerException.class, () -> serverKeyManager
|
||||||
|
.updateIdentityCredentialsFromFile(serverKey0File, serverCert0File, 1,
|
||||||
|
TimeUnit.MINUTES, null));
|
||||||
|
assertEquals("executor", npe.getMessage());
|
||||||
|
|
||||||
|
Logger log = Logger.getLogger(AdvancedTlsX509KeyManager.class.getName());
|
||||||
|
TestHandler handler = new TestHandler();
|
||||||
|
log.addHandler(handler);
|
||||||
|
log.setUseParentHandlers(false);
|
||||||
|
log.setLevel(Level.FINE);
|
||||||
|
serverKeyManager.updateIdentityCredentialsFromFile(serverKey0File, serverCert0File, -1,
|
||||||
|
TimeUnit.SECONDS, executor);
|
||||||
|
log.removeHandler(handler);
|
||||||
|
for (LogRecord record : handler.getRecords()) {
|
||||||
|
if (record.getMessage().contains("Default value of ")) {
|
||||||
|
assertTrue(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("Log message related to setting default values not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class TestHandler extends Handler {
|
||||||
|
private final List<LogRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void publish(LogRecord record) {
|
||||||
|
records.add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SecurityException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LogRecord> getRecords() {
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue