Merge pull request #8787 from eclipse/jetty-10.0.x-8786-KeyStoreScanner-Symlink
Issue #8786 - add configuration for KeyStoreScanner to not resolve aliases
This commit is contained in:
commit
690220fc40
|
@ -22,3 +22,6 @@ The module properties are:
|
||||||
----
|
----
|
||||||
include::{JETTY_HOME}/modules/ssl-reload.mod[tags=documentation]
|
include::{JETTY_HOME}/modules/ssl-reload.mod[tags=documentation]
|
||||||
----
|
----
|
||||||
|
|
||||||
|
The `followLinks` property is used to specify whether symlinks should be resolved in the path of the KeyStore.
|
||||||
|
If set to false and the path of the KeyStore is a symbolic link, the scanner will monitor the symbolic link file for changes instead of its target.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<Arg>
|
<Arg>
|
||||||
<New id="keyStoreScanner" class="org.eclipse.jetty.util.ssl.KeyStoreScanner">
|
<New id="keyStoreScanner" class="org.eclipse.jetty.util.ssl.KeyStoreScanner">
|
||||||
<Arg><Ref refid="sslContextFactory"/></Arg>
|
<Arg><Ref refid="sslContextFactory"/></Arg>
|
||||||
|
<Arg type="boolean"><Property name="jetty.sslContext.reload.followLinks" default="true"/></Arg>
|
||||||
<Set name="scanInterval"><Property name="jetty.sslContext.reload.scanInterval" default="1"/></Set>
|
<Set name="scanInterval"><Property name="jetty.sslContext.reload.scanInterval" default="1"/></Set>
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
|
|
|
@ -15,4 +15,7 @@ etc/jetty-ssl-context-reload.xml
|
||||||
# tag::documentation[]
|
# tag::documentation[]
|
||||||
# Monitored directory scan period, in seconds.
|
# Monitored directory scan period, in seconds.
|
||||||
# jetty.sslContext.reload.scanInterval=1
|
# jetty.sslContext.reload.scanInterval=1
|
||||||
|
|
||||||
|
# Whether to resolve symbolic links in the KeyStore path.
|
||||||
|
# jetty.sslContext.reload.followLinks=true
|
||||||
# end::documentation[]
|
# end::documentation[]
|
||||||
|
|
|
@ -43,6 +43,11 @@ public class KeyStoreScanner extends ContainerLifeCycle implements Scanner.Discr
|
||||||
private final Scanner _scanner;
|
private final Scanner _scanner;
|
||||||
|
|
||||||
public KeyStoreScanner(SslContextFactory sslContextFactory)
|
public KeyStoreScanner(SslContextFactory sslContextFactory)
|
||||||
|
{
|
||||||
|
this(sslContextFactory, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyStoreScanner(SslContextFactory sslContextFactory, boolean followLinks)
|
||||||
{
|
{
|
||||||
this.sslContextFactory = sslContextFactory;
|
this.sslContextFactory = sslContextFactory;
|
||||||
try
|
try
|
||||||
|
@ -54,9 +59,9 @@ public class KeyStoreScanner extends ContainerLifeCycle implements Scanner.Discr
|
||||||
if (monitoredFile.isDirectory())
|
if (monitoredFile.isDirectory())
|
||||||
throw new IllegalArgumentException("expected keystore file not directory");
|
throw new IllegalArgumentException("expected keystore file not directory");
|
||||||
|
|
||||||
if (keystoreResource.getAlias() != null)
|
if (followLinks && keystoreResource.isAlias())
|
||||||
{
|
{
|
||||||
// this resource has an alias, use the alias, as that's what's returned in the Scanner
|
// This resource has an alias, so monitor the target of the alias.
|
||||||
monitoredFile = new File(keystoreResource.getAlias());
|
monitoredFile = new File(keystoreResource.getAlias());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +78,7 @@ public class KeyStoreScanner extends ContainerLifeCycle implements Scanner.Discr
|
||||||
if (!parentFile.exists() || !parentFile.isDirectory())
|
if (!parentFile.exists() || !parentFile.isDirectory())
|
||||||
throw new IllegalArgumentException("error obtaining keystore dir");
|
throw new IllegalArgumentException("error obtaining keystore dir");
|
||||||
|
|
||||||
_scanner = new Scanner();
|
_scanner = new Scanner(null, followLinks);
|
||||||
_scanner.addDirectory(parentFile.toPath());
|
_scanner.addDirectory(parentFile.toPath());
|
||||||
_scanner.setScanInterval(1);
|
_scanner.setScanInterval(1);
|
||||||
_scanner.setReportDirs(false);
|
_scanner.setReportDirs(false);
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class KeyStoreScannerTest
|
||||||
public WorkDir testdir;
|
public WorkDir testdir;
|
||||||
private Server server;
|
private Server server;
|
||||||
private Path keystoreDir;
|
private Path keystoreDir;
|
||||||
private KeyStoreScanner keystoreScanner;
|
private KeyStoreScanner keyStoreScanner;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void before()
|
public void before()
|
||||||
|
@ -79,7 +79,7 @@ public class KeyStoreScannerTest
|
||||||
{
|
{
|
||||||
start(sslContextFactory ->
|
start(sslContextFactory ->
|
||||||
{
|
{
|
||||||
String keystorePath = useKeystore("oldKeystore").toString();
|
String keystorePath = useKeystore("oldKeyStore").toString();
|
||||||
sslContextFactory.setKeyStorePath(keystorePath);
|
sslContextFactory.setKeyStorePath(keystorePath);
|
||||||
sslContextFactory.setKeyStorePassword("storepwd");
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
sslContextFactory.setKeyManagerPassword("keypwd");
|
sslContextFactory.setKeyManagerPassword("keypwd");
|
||||||
|
@ -87,6 +87,11 @@ public class KeyStoreScannerTest
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(Configuration configuration) throws Exception
|
public void start(Configuration configuration) throws Exception
|
||||||
|
{
|
||||||
|
start(configuration, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(Configuration configuration, boolean resolveAlias) throws Exception
|
||||||
{
|
{
|
||||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
configuration.configure(sslContextFactory);
|
configuration.configure(sslContextFactory);
|
||||||
|
@ -100,9 +105,9 @@ public class KeyStoreScannerTest
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
|
||||||
// Configure Keystore Reload.
|
// Configure Keystore Reload.
|
||||||
keystoreScanner = new KeyStoreScanner(sslContextFactory);
|
keyStoreScanner = new KeyStoreScanner(sslContextFactory, resolveAlias);
|
||||||
keystoreScanner.setScanInterval(0);
|
keyStoreScanner.setScanInterval(0);
|
||||||
server.addBean(keystoreScanner);
|
server.addBean(keyStoreScanner);
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
|
@ -122,9 +127,9 @@ public class KeyStoreScannerTest
|
||||||
X509Certificate cert1 = getCertificateFromServer();
|
X509Certificate cert1 = getCertificateFromServer();
|
||||||
assertThat(getExpiryYear(cert1), is(2015));
|
assertThat(getExpiryYear(cert1), is(2015));
|
||||||
|
|
||||||
// Switch to use newKeystore which has a later expiry date.
|
// Switch to use newKeyStore which has a later expiry date.
|
||||||
useKeystore("newKeystore");
|
useKeystore("newKeyStore");
|
||||||
assertTrue(keystoreScanner.scan(5000));
|
assertTrue(keyStoreScanner.scan(5000));
|
||||||
|
|
||||||
// The scanner should have detected the updated keystore, expiry should be renewed.
|
// The scanner should have detected the updated keystore, expiry should be renewed.
|
||||||
X509Certificate cert2 = getCertificateFromServer();
|
X509Certificate cert2 = getCertificateFromServer();
|
||||||
|
@ -144,7 +149,7 @@ public class KeyStoreScannerTest
|
||||||
try (StacklessLogging ignored = new StacklessLogging(KeyStoreScanner.class))
|
try (StacklessLogging ignored = new StacklessLogging(KeyStoreScanner.class))
|
||||||
{
|
{
|
||||||
useKeystore("badKeystore");
|
useKeystore("badKeystore");
|
||||||
keystoreScanner.scan(5000);
|
keyStoreScanner.scan(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The good keystore is removed, now the bad keystore now causes an exception.
|
// The good keystore is removed, now the bad keystore now causes an exception.
|
||||||
|
@ -165,15 +170,15 @@ public class KeyStoreScannerTest
|
||||||
{
|
{
|
||||||
Path keystorePath = keystoreDir.resolve("keystore");
|
Path keystorePath = keystoreDir.resolve("keystore");
|
||||||
assertTrue(Files.deleteIfExists(keystorePath));
|
assertTrue(Files.deleteIfExists(keystorePath));
|
||||||
keystoreScanner.scan(5000);
|
keyStoreScanner.scan(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The good keystore is removed, having no keystore causes an exception.
|
// The good keystore is removed, having no keystore causes an exception.
|
||||||
assertThrows(Throwable.class, this::getCertificateFromServer);
|
assertThrows(Throwable.class, this::getCertificateFromServer);
|
||||||
|
|
||||||
// Switch to use keystore2 which has a later expiry date.
|
// Switch to use keystore2 which has a later expiry date.
|
||||||
useKeystore("newKeystore");
|
useKeystore("newKeyStore");
|
||||||
keystoreScanner.scan(5000);
|
keyStoreScanner.scan(5000);
|
||||||
X509Certificate cert2 = getCertificateFromServer();
|
X509Certificate cert2 = getCertificateFromServer();
|
||||||
assertThat(getExpiryYear(cert2), is(2020));
|
assertThat(getExpiryYear(cert2), is(2020));
|
||||||
}
|
}
|
||||||
|
@ -182,23 +187,26 @@ public class KeyStoreScannerTest
|
||||||
public void testReloadChangingSymbolicLink() throws Exception
|
public void testReloadChangingSymbolicLink() throws Exception
|
||||||
{
|
{
|
||||||
assumeFileSystemSupportsSymlink();
|
assumeFileSystemSupportsSymlink();
|
||||||
Path keystorePath = keystoreDir.resolve("symlinkKeystore");
|
Path newKeyStore = useKeystore("newKeyStore", "newKeyStore");
|
||||||
|
Path oldKeyStore = useKeystore("oldKeyStore", "oldKeyStore");
|
||||||
|
|
||||||
|
Path symlinkKeystorePath = keystoreDir.resolve("symlinkKeystore");
|
||||||
start(sslContextFactory ->
|
start(sslContextFactory ->
|
||||||
{
|
{
|
||||||
Files.createSymbolicLink(keystorePath, useKeystore("oldKeystore"));
|
Files.createSymbolicLink(symlinkKeystorePath, oldKeyStore);
|
||||||
sslContextFactory.setKeyStorePath(keystorePath.toString());
|
sslContextFactory.setKeyStorePath(symlinkKeystorePath.toString());
|
||||||
sslContextFactory.setKeyStorePassword("storepwd");
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
sslContextFactory.setKeyManagerPassword("keypwd");
|
sslContextFactory.setKeyManagerPassword("keypwd");
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
// Check the original certificate expiry.
|
// Check the original certificate expiry.
|
||||||
X509Certificate cert1 = getCertificateFromServer();
|
X509Certificate cert1 = getCertificateFromServer();
|
||||||
assertThat(getExpiryYear(cert1), is(2015));
|
assertThat(getExpiryYear(cert1), is(2015));
|
||||||
|
|
||||||
// Change the symlink to point to the newKeystore file location which has a later expiry date.
|
// Change the symlink to point to the newKeyStore file location which has a later expiry date.
|
||||||
Files.delete(keystorePath);
|
Files.delete(symlinkKeystorePath);
|
||||||
Files.createSymbolicLink(keystorePath, useKeystore("newKeystore"));
|
Files.createSymbolicLink(symlinkKeystorePath, newKeyStore);
|
||||||
keystoreScanner.scan(5000);
|
keyStoreScanner.scan(5000);
|
||||||
|
|
||||||
// The scanner should have detected the updated keystore, expiry should be renewed.
|
// The scanner should have detected the updated keystore, expiry should be renewed.
|
||||||
X509Certificate cert2 = getCertificateFromServer();
|
X509Certificate cert2 = getCertificateFromServer();
|
||||||
|
@ -210,13 +218,13 @@ public class KeyStoreScannerTest
|
||||||
{
|
{
|
||||||
assumeFileSystemSupportsSymlink();
|
assumeFileSystemSupportsSymlink();
|
||||||
Path keystoreLink = keystoreDir.resolve("symlinkKeystore");
|
Path keystoreLink = keystoreDir.resolve("symlinkKeystore");
|
||||||
Path oldKeystoreSrc = MavenTestingUtils.getTestResourcePathFile("oldKeystore");
|
Path oldKeyStoreSrc = MavenTestingUtils.getTestResourcePathFile("oldKeyStore");
|
||||||
Path newKeystoreSrc = MavenTestingUtils.getTestResourcePathFile("newKeystore");
|
Path newKeyStoreSrc = MavenTestingUtils.getTestResourcePathFile("newKeyStore");
|
||||||
Path target = keystoreDir.resolve("keystore");
|
Path target = keystoreDir.resolve("keystore");
|
||||||
|
|
||||||
start(sslContextFactory ->
|
start(sslContextFactory ->
|
||||||
{
|
{
|
||||||
Files.copy(oldKeystoreSrc, target);
|
Files.copy(oldKeyStoreSrc, target);
|
||||||
Files.createSymbolicLink(keystoreLink, target);
|
Files.createSymbolicLink(keystoreLink, target);
|
||||||
sslContextFactory.setKeyStorePath(keystoreLink.toString());
|
sslContextFactory.setKeyStorePath(keystoreLink.toString());
|
||||||
sslContextFactory.setKeyStorePassword("storepwd");
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
@ -227,16 +235,34 @@ public class KeyStoreScannerTest
|
||||||
X509Certificate cert1 = getCertificateFromServer();
|
X509Certificate cert1 = getCertificateFromServer();
|
||||||
assertThat(getExpiryYear(cert1), is(2015));
|
assertThat(getExpiryYear(cert1), is(2015));
|
||||||
|
|
||||||
// Change the target file of the symlink to the newKeystore which has a later expiry date.
|
// Change the target file of the symlink to the newKeyStore which has a later expiry date.
|
||||||
Files.copy(newKeystoreSrc, target, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(newKeyStoreSrc, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
System.err.println("### Triggering scan");
|
System.err.println("### Triggering scan");
|
||||||
keystoreScanner.scan(5000);
|
keyStoreScanner.scan(5000);
|
||||||
|
|
||||||
// The scanner should have detected the updated keystore, expiry should be renewed.
|
// The scanner should have detected the updated keystore, expiry should be renewed.
|
||||||
X509Certificate cert2 = getCertificateFromServer();
|
X509Certificate cert2 = getCertificateFromServer();
|
||||||
assertThat(getExpiryYear(cert2), is(2020));
|
assertThat(getExpiryYear(cert2), is(2020));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path useKeystore(String keystoreToUse, String keystorePath) throws Exception
|
||||||
|
{
|
||||||
|
return useKeystore(MavenTestingUtils.getTestResourcePath(keystoreToUse), keystoreDir.resolve(keystorePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path useKeystore(Path keystoreToUse, Path keystorePath) throws Exception
|
||||||
|
{
|
||||||
|
if (Files.exists(keystorePath))
|
||||||
|
Files.delete(keystorePath);
|
||||||
|
|
||||||
|
Files.copy(keystoreToUse, keystorePath);
|
||||||
|
|
||||||
|
if (!Files.exists(keystorePath))
|
||||||
|
throw new IllegalStateException("keystore file was not created");
|
||||||
|
|
||||||
|
return keystorePath.toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
public Path useKeystore(String keystore) throws Exception
|
public Path useKeystore(String keystore) throws Exception
|
||||||
{
|
{
|
||||||
Path keystorePath = keystoreDir.resolve("keystore");
|
Path keystorePath = keystoreDir.resolve("keystore");
|
||||||
|
|
Loading…
Reference in New Issue