Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #5019 - hot-reload SSL certificates if keystore file changed #5042

Merged
merged 12 commits into from Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions jetty-bom/pom.xml
Expand Up @@ -344,6 +344,11 @@
<artifactId>jetty-webapp</artifactId>
<version>9.4.31-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ssl-reload</artifactId>
<version>9.4.31-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-client-impl</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions jetty-home/pom.xml
Expand Up @@ -768,6 +768,11 @@
<artifactId>jetty-nosql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ssl-reload</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<profiles>
Expand Down
28 changes: 28 additions & 0 deletions jetty-ssl-reload/pom.xml
@@ -0,0 +1,28 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.4.31-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-ssl-reload</artifactId>
<name>Jetty :: SSL Reload</name>

<properties>
<bundle-symbolic-name>${project.groupId}.ssl.reload</bundle-symbolic-name>
</properties>

<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
13 changes: 13 additions & 0 deletions jetty-ssl-reload/src/main/config/etc/jetty-ssl-reload.xml
@@ -0,0 +1,13 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure id="Server" class="org.eclipse.jetty.server.Server">

<Call name="addBean">
<Arg>
<New id="sslKeyStoreScanner" class="org.eclipse.jetty.ssl.reload.SslKeyStoreScanner">
<Arg><Ref refid="sslContextFactory"/></Arg>
<Set name="scanInterval"><Property name="jetty.ssl.reload.scanInterval" default="1"/></Set>
</New>
</Arg>
</Call>
</Configure>
21 changes: 21 additions & 0 deletions jetty-ssl-reload/src/main/config/modules/ssl-reload.mod
@@ -0,0 +1,21 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html

[description]
Enables the SSL keystore to be reloaded after any changes are detected on the file system.

[tags]
connector
ssl

[depend]
ssl

[lib]
lib/jetty-ssl-reload-${jetty.version}.jar

[xml]
etc/jetty-ssl-reload.xml

[ini-template]
# Monitored directory scan period (seconds)
# jetty.ssl.reload.scanInterval=1
@@ -0,0 +1,146 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//

package org.eclipse.jetty.ssl.reload;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;

public class SslKeyStoreScanner extends AbstractLifeCycle implements Scanner.DiscreteListener
{
private static final Logger LOG = Log.getLogger(SslKeyStoreScanner.class);

private final SslContextFactory sslContextFactory;
private final File keystoreFile;
private final List<File> files = new ArrayList<>();
private Scanner _scanner;
private int _scanInterval = 1;

public SslKeyStoreScanner(SslContextFactory sslContextFactory)
{
this.sslContextFactory = sslContextFactory;
this.keystoreFile = new File(URI.create(sslContextFactory.getKeyStorePath())); // getKeyStorePath is giving url instead of path?
if (!keystoreFile.exists())
throw new IllegalArgumentException("keystore file does not exist");
if (keystoreFile.isDirectory())
throw new IllegalArgumentException("expected keystore file not directory");

File parentFile = keystoreFile.getParentFile();
if (!parentFile.exists() || !parentFile.isDirectory())
throw new IllegalArgumentException("error obtaining keystore dir");

files.add(parentFile);
if (LOG.isDebugEnabled())
LOG.debug("created {}", this);
}

@Override
protected void doStart() throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug(this.getClass().getSimpleName() + ".doStart()");

_scanner = new Scanner();
_scanner.setScanDirs(files);
_scanner.setScanInterval(_scanInterval);
_scanner.setReportDirs(false);
_scanner.setReportExistingFilesOnStartup(false);
_scanner.setScanDepth(1);
_scanner.addListener(this);
_scanner.start();
}

@Override
protected void doStop() throws Exception
{
if (_scanner != null)
{
_scanner.stop();
_scanner.removeListener(this);
_scanner = null;
}
}

@Override
public void fileAdded(String filename) throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("added {}", filename);

if (keystoreFile.toPath().toString().equals(filename))
reload();
}

@Override
public void fileChanged(String filename) throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("changed {}", filename);

if (keystoreFile.toPath().toString().equals(filename))
reload();
}

@Override
public void fileRemoved(String filename) throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("removed {}", filename);

// TODO: do we want to do this?
if (keystoreFile.toPath().toString().equals(filename))
reload();
}

@ManagedOperation(value = "Reload the SSL Keystore", impact = "ACTION")
public void reload() throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("reloading keystore file {}", keystoreFile);

try
{
sslContextFactory.reload(scf -> {});
}
catch (Throwable t)
{
LOG.warn("Keystore Reload Failed", t);
}
}

@ManagedAttribute("scanning interval to detect changes which need reloaded")
public int getScanInterval()
{
return _scanInterval;
}

public void setScanInterval(int scanInterval)
{
_scanInterval = scanInterval;
}
}