Spring WebFlux: обновление SSL сертификата

Нужно время от времени, при изменении файла сертификата (он обновляется внешней системой), подхватывать этот новый файл и применять без перезапуска приложения. Вроде бы тривиальная задача, но нигде не могу найти рабочий пример. В моем приложении используется spring-cloud-gateway, соответственно стек Spring + WebFlux. При запуске сервера, первоначально он правильно подхватывает сертификат и успешно его применяет, однако после обновления самого файла сертификата, он не видит обновления. Код конфигурации такой:

@Configuration
public class ContainerConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
    private final ApplicationContext applicationContext;
    private final boolean sslEnabled;
    private final String sslKeyStoreType;
    private final String sslKeyStoreFilepath;
    private final String sslKeyStorePassword;
    private final String sslKeyAlias;


public ContainerConfiguration(ApplicationContext applicationContext,
        @Value("${app.ssl.enabled}") String appSslEnabledStr,
        @Value("${server.ssl.key-store-type}") String sslKeyStoreType,
        @Value("${server.ssl.key-store}") String sslKeyStoreFilepath,
        @Value("${server.ssl.key-store-password}") String sslKeyStorePassword,
        @Value("${server.ssl.key-alias}") String sslKeyAlias)
{
    this.applicationContext = applicationContext;
    this.sslEnabled = "1".equals(appSslEnabledStr);
    this.sslKeyStoreType = sslKeyStoreType;
    this.sslKeyStoreFilepath = sslKeyStoreFilepath;
    this.sslKeyStorePassword = sslKeyStorePassword;
    this.sslKeyAlias = sslKeyAlias;
}


@Override
public void customize(NettyReactiveWebServerFactory factory) {
    Ssl ssl = new Ssl();
    ssl.setEnabled(sslEnabled);

    if (sslEnabled) {
        ssl.setKeyStoreType(sslKeyStoreType);
        ssl.setKeyStore(sslKeyStoreFilepath);
        ssl.setKeyStorePassword(sslKeyStorePassword);
        ssl.setKeyAlias(sslKeyAlias);

        WatchService watchService;
        Path certPath = Paths.get(sslKeyStoreFilepath);

        try {
            watchService = FileSystems.getDefault().newWatchService();
            certPath.getParent().register(watchService, ENTRY_MODIFY);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    WatchKey key = watchService.take();// Ждем событие обновления файла

                    try {
                        for (WatchEvent<?> event : key.pollEvents()) {
                            if (event.kind() == ENTRY_MODIFY && event.context().equals(certPath.getFileName())) {
                                log.warn("SSL certificate is changed. Reloading the context...");
                                reloadSSLConfig(factory);
                            }
                        }
                    } finally {
                        key.reset();    // готовимся дать следующее событие
                    }
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
            }
        }).start();
    }

    factory.setPort(sslEnabled ? 8443 : 8080);
    factory.setSsl(ssl);
}


private void reloadSSLConfig(@NonNull NettyReactiveWebServerFactory factory) {
    Ssl ssl = factory.getSsl();
    //        Ssl ssl = new Ssl();
//        ssl.setEnabled(sslEnabled);
//        ssl.setKeyStoreType(sslKeyStoreType);
        ssl.setKeyStore(sslKeyStoreFilepath);
        ssl.setKeyStorePassword(sslKeyStorePassword);
//        ssl.setKeyAlias(sslKeyAlias);
//        factory.setPort(sslEnabled ? 8443 : 8080);
        factory.setSsl(ssl);    // important: update ssl
        // Перезагрузка контекста Spring Boot
//        ((AbstractApplicationContext) applicationContext).stop();
//        ((AbstractApplicationContext) applicationContext).start();
    }
}

Т.е. там после WatchService watchService происходит ожидание обновления файла сертификата, после его обновления применяется код из reloadSSLConfig - это работает.

А вот в самом методе reloadSSLConfig я прописываю новый factory.setSsl(ssl); и, ожидаемо, сервер не перезагружает файл сертификата, а использует старую конфигурацию. И даже после перезагрузки applicationContext (закомменчено внизу), конфигуация SSL по-прежнему используется старая.

Подскажите, как сказать серверу чтобы он обновил конфигурацию?


Ответы (1 шт):

Автор решения: Alexander Kozlov

Пошел наиболее простым путем и решил просто перезапускать приложение после обновления сертификата через actuator. Для этого убедился, что в application.properties включен restart endpoint:

management.endpoint.restart.access=read_only

В своем ContainerConfiguration.java:

  • autowire RestartEndpoint

  • метод reloadSSLConfig получился таким:

    private void reloadSSLConfig() { // Перезагрузка приложения

    restartEndpoint.restart(); }

PS: еще нашел статью о Hot reload, может кому-нибудь поможет: SSL hot reload in Spring Boot 3.2.0

→ Ссылка