IT-notes     О блоге     Архив записей

Intellij idea: запуск maven-проекта на основе Spring-boot с исключением resources

Проблема: есть maven-проект, в pom-файле которого указано:

<build>
    <resources>
        <resource>
            <directory>${basedir}/src/main/resources</directory>
            <excludes>
                <exclude>*.*</exclude>
            </excludes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    <plugins>
<build> Т.е. используем плагин **spring-boot-maven-plugin**, и исключаем из проекта папку **${basedir}/src/main/resources**. Если у нас есть файл **application.properties** в котором указаны свойства для инициализации компонент, приложение из idea не запуститься. Мы получим ошибки о том, что нам не удалось инициализировать некоторые из бинов. **Решение:** В свойствах меню **Run => Edit configuration** выберем нашу конфигурацию запуска и в **VM options** укажем полный путь к каталогу где хранится файл **application.properties**:

-Dspring.config.location=path

idea

trustStore и keyStore для java-приложения

  • Создаем trustStore в формате jks, указываем для него пароль.

Переходим в раздел «Личные сертификаты», нажимаем на кнопку «Импортировать». Выбираем базу данных ключей для импорта.

ssl

ssl

Переходим в раздел «Личные сертификаты», нажимаем на кнопку «Импортировать». Выбираем базу данных ключей для импорта.

ssl

В открывшемся списке выбираем для импорта только «Подписывающие сертификаты» и нажимаем на кнопку «ОК».

«Личные сертификаты» будем импортировать в следующем пункте в keyStore.

ssl

  • По указанному пути создан trustStore.jks.
  • Создаем keyStore в формате jks, указываем для него пароль (аналогично созданию trustStore)

Далее импортируем «Личные сертификаты» аналогично импорту «Подписывающих сертификатов» в trustStore.

По указанному пути создан keyStore.jks.

Spring: механизм шифрования для конфигурационных данных

Unlimited strength Java Cryptography Extension (JCE)

Для поддержки шифрования/дешифрования необходимо установить unlimited strength Java Cryptography Extension (JCE). Это zip-файл, который содержит два jar-ника:

  • local_policy.jar
  • US_export_policy.jar

Данные библиотеки необходимо разместить в $JAVA_HOME/ jre/lib/security.

Ассиметричное шифрование

Генерируем хранилище публичного и приватного ключа, которое в дальнейшем будем использовать для шифрования/дешифрования.

keytool -genkeypair -alias devkey -keyalg RSA -dname "CN=Dev environment,OU=00CA,O=Home organization,C=RU" -keypass keypass -keystore C:\work\dev-properties-server.jks -storepass storepass

В файл bootstrap.yml config-server и всех сервисов, которые используют зашифрованную информацию добавляем следующие свойства:

encrypt:  
key-store:  
location: ${ENCRYPT_KEY_STORE_LOCATION}  
password: ${ENCRYPT_KEY_STORE_PASSWORD}  
alias: ${ENCRYPT_KEY_STORE_ALIAS}  
secret: ${ENCRYPT_KEY_STORE_SECRET}

Значение параметров ENCRYPT_KEY_STORE_LOCATION, ENCRYPT_KEY_STORE_PASSWORD, ENCRYPT_KEY_STORE_ALIAS, ENCRYPT_KEY_STORE_SECRET передаем как параметры jvm при запуске сервиса и config-server:

java -jar -Dspring.profiles.active=dev -DENCRYPT_KEY_STORE_LOCATION= file:/C:/work/dev-properties-server.jks -DENCRYPT_KEY_STORE_PASSWORD=storepass -DENCRYPT_KEY_STORE_ALIAS= devkey -DENCRYPT_KEY_STORE_SECRET=keypass service.jar

Расшифровка конфигов на стороне клиента

Чтобы конфигурационные параметры расшифровывались на стороне клиента необходимо добавить в файл bootstrap.yml значение параметров config-server:

spring.cloud.config.server.encrypt.enabled=false

Шифрование/Дешифрование

У config-server есть следующие еndpoint-ы:

  1. Http-method: POST-запрос
    Path: /encrypt
    Request Body: то, что нужно зашифровать
    Respone: зашифрованная информация
  2. Http-method: POST-запрос
    Path: /decrypt
    Request Body: то, что нужно расшифровать
    Respone: расшифрованная информация

Конфигурационные файлы

Для того, чтобы внести зашифрованную информацию в конфиги необходимо:

  1. Воспользоваться сервисом encrypt config-сервера.
  2. Полученную информацию внести в конфиги так:

    '{cipher}…….здесь..зашифрованное…сообщение…'
    

Пример:

password: '{cipher}AQBktEJNrfYF7ToKUwRJaw2/fCYhKSLH0oHUACt9Ma5rhxPhMG1dWGs7SfsbcOjxu3sDld379iJZBBGZ2YLDBBdl/3P8ceIQXUa8vdPQmnC8qXLB4GZ+RjNa0AjIHj2v1OU+6NUOpMEC26wI7MqTHm3kc2lfAD6Tcii6OtBaKTeoek/LfU6vloAdnE5ZEdY3C+XZz6PMsywZ+IpJgp+NDhqKua3b6PpYEVP1TaszvPDQaf84cjTWwkGAr9r8NNrexKOAAzxAU5nbJt4w2MfCpQCQ9fZOSLYgwh01+V3X7hjjoxchZf1FaybXWCj5j0azJ8xE3qXcF6oBQXcGMupkXUs01AqAor5vr9kANzICYy3J7Iulp/LpR0ppA9XXzvM+MKI='

Для локальной отладки сервисов в Intelij IDEA

В VM options добавляем:

-DENCRYPT_KEY_STORE_LOCATION=file:/C:/encrypt-properties/dev-properties-server.jks -DENCRYPT_KEY_STORE_PASSWORD=keypass -DENCRYPT_KEY_STORE_ALIAS=devkey -DENCRYPT_KEY_STORE_SECRET=storepass

То, что выделено жирным замените на свой путь к файлу jks.

Spring boot admin: изменение уровня логирования

Уровни логирования:

  • TRACE на этом уровне выводятся все сообщения уровня TRACE, DEBUG, INFO, WARN, ERROR.
  • DEBUG на этом уровне выводятся все сообщения уровня DEBUG, INFO, WARN, ERROR.
  • INFO на этом уровне выводятся все сообщения уровня INFO, WARN, ERROR.
  • WARN на этом уровне выводятся все сообщения уровня WARN, ERROR.
  • ERROR на этом уровне выводятся все сообщения уровня ERROR.

По умолчанию для приложения включен уровень INFO.

Для возможности динамического изменения уровня логирования (без пересборки приложения) в конфигурацию logback добавлена возможность поддержки jmx-конфигурирования:

<configuration>  
…  
<jmxConfigurator/>  
</configuration>

Настройка логирования через Spring Boot Admin осуществляется в строке соответствующего сервиса в пункте меню Details => Logging.

logging

Переключаемся, нажимая левой кнопкой мыши, на необходимый уровень и настройки сразу же вступают в силу:

logging

Настройка SSL в Windows

  • Добавить в переменные среды:

    AMQ_SSL_V3_ENABLE=1 AMQ_SSL_WEAK_CIPHER_ENABLE=RC4_MD5_US

ssl

  • Перезапустить MQ
  • Запустить KeyManager (утилита по адресу App_IBM_MQ_Install \bin\strmqikm.exe, где App_IBM_MQ_Install – установочная директория сервера IBM MQ)
  • Создать хранилище. Указать путь, где будет располагаться хранилище. Рекомендуется сохранять файлы хранилища в папке ssl менеджера очередей, к примеру: C:\Program Files (x86)\IBM\WebSphere MQ\Qmgrs\ONE!QM\ssl. При указании пароля на хранилище обязательно выбрать опцию «Сохранить пароль в файле».

ssl

ssl

  • Создать запрос на выпуск сертификата с параметрам

ssl

  • Свой сертификат загружаем в Personal Certificate путем Recieve
  • Cертификат удостоверяющего центра загружаем в Signer Certificate
  • Указываем путь к созданному хранилищу для менеджера очередей без расширения .kdb

ssl

  • Для канала настраиваем раздел MCA: указываем пользователя, у которого есть доступ к каналу и очередям менеджера очередей, при этом он должен быть заведен на уровне ОС

ssl

  • Для канала настраиваем раздел SSL:

SSL Cipher Spec: параметр определяющий какой тип шифрования используется. Параметр должен быть одинаковым на обоих сторонах, тех кто запрашивает соединение и на уровне MQ, куда приходит запрос.

ssl

  • Перезапустить MQ

Logback: 'Русские буквы' logstash-logback-encoder

Проблема: русские буквы выводятся в таком виде “:

\u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u043C \u0440\u0435\u0441\u0443\u0440\u0441\u0430 - ‘/in’ \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F”.

Решение:

По умолчанию доступна кодировка UTF-8, но jackson по умолчанию исключает все не ascii-символы. Поэтому необходимо переопределить поведение, создав класс, который имплементирует JsonFactoryDecorator и отключает эту опцию.

import com.fasterxml.jackson.core.JsonGenerator;  
import com.fasterxml.jackson.databind.MappingJsonFactory;  
import net.logstash.logback.decorate.JsonFactoryDecorator;  
  
public class NoEscapingJsonFactoryDecorator implements JsonFactoryDecorator {  
  
    @Override  
    public MappingJsonFactory decorate(MappingJsonFactory factory) {  
	    return (MappingJsonFactory) factory.disable(JsonGenerator.Feature.ESCAPE_NON_ASCII);  
    }  
}

Затем, конфигурируем encoder, используя созданный класс.

<?xml version="1.0" encoding="UTF-8"?>  
    <configuration>  
	    <include resource="org/springframework/boot/logging/logback/default.xml"/>  
	    <jmxConfigurator/>  
	    <springProperty name="springAppName" source="spring.application.name"/>  
	    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
		    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">  
			    <jsonFactoryDecorator class="ru.sbt.parus.logstash.logback.decorate.NoEscapingJsonFactoryDecorator"/>  
		    ....  
		    </encoder>  
	    </appender>  
	      
	    <root level="info">  
		    <appender-ref ref="STDOUT"/>  
	    </root>  
</configuration>

Logback: Настройка вывода в файл

Для возможности вывода логов в файл создаем файловый appender в файле logback-prod.xml:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">  
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
	    <fileNamePattern>${logDir}/${springAppName}_%d{yyyy-MM-dd}_%i.log</fileNamePattern>  
	    <maxHistory>${maxHistory}</maxHistory>  
	    <totalSizeCap>${totalSizeCap}</totalSizeCap>  
	      
	    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">  
		    <maxFileSize>${maxFileSize}</maxFileSize>  
	    </timeBasedFileNamingAndTriggeringPolicy>  
    </rollingPolicy>  
      
    <encoder>  
	    <charset>UTF-8</charset>  
	    <pattern>${filePattern}</pattern>  
    </encoder>  
</appender>

В настройках этого appender участвуют springProperty, которые указываются в репозитории конфигурационных файлов:

<springProperty name="springAppName" source="spring.application.name"/>  
<springProperty name="logDir" source="logging.logDir"/>  
<springProperty name="totalSizeCap" source="logging.totalSizeCap"/>  
<springProperty name="maxHistory" source="logging.maxHistory"/>  
<springProperty name="maxFileSize" source="logging.maxFileSize"/>  
<springProperty name="filePattern" source="logging.filePattern"/>

application.yml:

#######################################################  
# Конфигурирование логирования  
######################################################  
logging:  
config: classpath:logback-prod.xml  
# Каталог для хранения логов  
logDir: D:/LOGS  
# История в днях, максимальный объем логов распространяется на количество дней  
maxHistory: 30  
# Максимальный размер логов  
totalSizeCap: 40KB  
# Максимальный размер одного файла  
maxFileSize: 15KB  
# Шаблон выводимой информации  
filePattern: "%d %-4relative [%thread] %-5level %logger{35} - %msg%n"

Настраиваемые для логирования свойства:

  • logging.config – имя и расположение конфигурационного файла logback.
  • logging.logDir – абсолютный путь к каталогу, где размещаются конфигурационные файлы.
  • logging.maxHistory – количество дней, в которых хранится история, при условии, что не достигнуты другие предельные величины для логов (общий размер файлов). Максимальный объем логов распространяется на заданное количество дней.
  • logging.totalSizeCap – максимальный размер всех лог-файлов для сервиса.
  • logging.maxFileSize – максимальный размер одного лог-файла.
  • logging.filePattern - шаблон выводимой информации в логе.

Очищение старых логов происходит в момент, когда создается новый лог-файл, так у предыдущего превышен размер, заданный в параметре maxFileSize. В этот момент происходит проверка, что общий объем логов за число дней в maxHistory не превышает объем, указанный в totalSizeCap. Если условие проверки выполняется, очищаются лишние логи в пределах дней maxHistory до объема totalSizeCap. Поэтому, если мы хотим подчищать хвосты от логов, которые остались в периоды простоя сервсиов необходимо расширить количество дней в maxFileSize.

По предварительным настроенным параметрам хранение логов выглядит так:

logs

Пример проекта.

Jms-config с ssl

Для работы с SSL в java-приложении необходимо настроить соответствующим образом фабрику соединений.

  • Создадим утилитный класс, в котором настроим SSLContext, для которого необходимо передать пути к файлам и пароли к keyStore и trustStore.

      import javax.net.ssl.KeyManagerFactory;  
      import javax.net.ssl.SSLContext;  
      import javax.net.ssl.TrustManagerFactory;  
      import java.io.FileInputStream; 
      import java.io.IOException;  
      import java.security.*;  
    
      public class KeyUtils {  
    	  
          public static KeyStore createKeystoreFromJKS(String jksPath, String ksPassword) {  
              try {  
                  KeyStore keyStore = KeyStore.getInstance("JKS");  
                  keyStore.load(new FileInputStream(jksPath), ksPassword.toCharArray());  
                  return keyStore;  
              } catch (GeneralSecurityException | IOException e) {  
                  throw new RuntimeException(e);  
              }  
          }  
    		  
          public static SSLContext createSSLContext(String keyStore, String keyStorePassword, String trustStore, String trustStorePassword) {  
              KeyStore ks = createKeystoreFromJKS(keyStore, keyStorePassword);  
              KeyStore ts = createKeystoreFromJKS(trustStore, trustStorePassword);  
    			  
              try {  
                  KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
                  kmf.init(ks, keyStorePassword.toCharArray());  
    				  
                  TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
                  tmf.init(ts);  
    				  
                  SSLContext sslContext = SSLContext.getInstance("SSLv3");  
                  sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);  
                  return sslContext;  
              } catch (Exception e) {  
                  throw new RuntimeException(e);  
              }  
          }  
      }
    
  • Пример класса конфигурации для JMS-контекста, использующего SSL

    import com.ibm.mq.jms.MQQueueConnectionFactory;  
    import com.ibm.msg.client.wmq.WMQConstants;  
    import org.springframework.beans.factory.annotation.Value;  
    import org.springframework.context.annotation.Bean;  
    import org.springframework.context.annotation.Configuration; 
    import org.springframework.context.annotation.Primary;
    import org.springframework.jms.connection.CachingConnectionFactory;
    import org.springframework.jms.core.JmsTemplate;  
    import javax.jms.JMSException;  
    import javax.jms.QueueConnectionFactory;  
    import javax.jms.Session;  
    import javax.net.ssl.SSLContext;
    import java.security.Security; 
        
    @Configuration
    public class JmsContext { 
        @Value("${servers.mq.host}")  
        private String host;  
        @Value("${servers.mq.port}")  
        private Integer port;  
        @Value("${servers.mq.queue-manager}")  
        private String queueManager;  
        @Value("${servers.mq.channel}")  
        private String channel;  
        @Value("${servers.mq.timeout}")  
        private long timeout;  
        @Value("${servers.mq.sessionCacheSize}")  
        private int sessionCacheSize;  
        @Value("${spring.application.name}")  
        private String appName;  
        @Value("${servers.mq.CCSID}")  
        private int ccsid;  
        @Value("${servers.mq.trustStore}")  
        private String trustStore;  
        @Value("${servers.mq.trustStorePassword}")  
        private String trustStorePassword;  
        @Value("${servers.mq.keyStore}")  
        private String keyStore;  
        @Value("${servers.mq.keyStorePassword}")  
        private String keyStorePassword;  
        @Value("${servers.mq.peerName}")  
        private String peerName;  
        @Value("${servers.mq.cipherSuite}")  
        private String cipherSuite;    
      
        @Bean  
        public QueueConnectionFactory queueConnectionFactory() throws JMSException {  
            Security.setProperty("jdk.tls.disabledAlgorithms", "");  
            SSLContext sslContext = KeyUtils.createSSLContext(keyStore, keyStorePassword,  trustStore,  trustStorePassword);  
            MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
            mqQueueConnectionFactory.setHostName(host);
            mqQueueConnectionFactory.setQueueManager(queueManager);
            mqQueueConnectionFactory.setPort(port);
            mqQueueConnectionFactory.setChannel(channel);
            mqQueueConnectionFactory.setAppName(appName);
            mqQueueConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
            mqQueueConnectionFactory.setCCSID(ccsid); 
            mqQueueConnectionFactory.setSSLCipherSuite(cipherSuite);
            mqQueueConnectionFactory.setSSLSocketFactory(sslContext.getSocketFactory());
            mqQueueConnectionFactory.setSSLPeerName(peerName);
            mqQueueConnectionFactory.setSSLFipsRequired(false);
            return mqQueueConnectionFactory;
        }
    	     
        @Bean  
        @Primary  
        public CachingConnectionFactory queueCachingConnectionFactory() throws JMSException {  
            CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
            cachingConnectionFactory.setTargetConnectionFactory(queueConnectionFactory());
            cachingConnectionFactory.setSessionCacheSize(sessionCacheSize);
            cachingConnectionFactory.setReconnectOnException(true);  
            return cachingConnectionFactory;  
        }  
    		  
        @Bean  
        @Primary  
        public JmsTemplate queueTemplate(CachingConnectionFactory queueCachingConnectionFactory) {  
            JmsTemplate jmsTemplate = new JmsTemplate(queueCachingConnectionFactory);  
            jmsTemplate.setReceiveTimeout(timeout);  
            jmsTemplate.setSessionTransacted(true);  				jmsTemplate.setSessionAcknowledgeMode(Session.SESSION_TRANSACTED);  
            return jmsTemplate;  
        }  
    	  
    }  
    
  • Пример для указания настроек JMS.

    servers:  
    mq:  
    host: 192.168.0.101  
    port: 1414  
    queue-manager: OUT.QM  
    channel: OUT.SVR.CONN  
    queue: OUT_XML  
    timeout: 2000  
    sessionCacheSize: 10  
    CCSID: 1208  
    trustStore: D:\192_168_0_101\trustStore.jks  
    trustStorePassword: 123456  
    keyStore: D:\192_168_0_101\keyStore.jks  
    keyStorePassword: 123456  
    peerName: CN=home.ofedorova.ru, OU=00CA, O=Home organization, C=RU  
    cipherSuite: SSL_RSA_WITH_RC4_128_MD5
    

Конфликт соединений через Bridge docker0

Проблема: После обновления системы не работает wi-fi, единственное доступное соединение Bridge docker0. ifconfig

Решение:

  • Делаем копию файла /etc/default/grub:

    sudo cp /etc/default/grub /etc/default/grub.bak

  • В файле /etc/default/grub меняем:

    GRUB_DEFAULT=0

на

GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 4.13.0-45-generic"

вместо Ubuntu, with Linux 4.13.0-45-generic – указываем свою последнюю стабильную версию, посмотреть можно в каталоге /usr/src

  • Затем выполняем команду:

    sudo update-grub

  • Перезапускаем компьютер.
  • После перезапуска мы откатились на предыдущую версию ядра и у нас снова доступно интернет-соединение.
  • Теперь выполняем обновление системы:

    sudo apt-get update sudo apt-get upgrade sudo apt-get -f install

  • После этого возвращаем предыдущую версию файла /etc/default/grub:

    sudo cp /etc/default/grub /etc/default/grub.prev.bak sudo cp /etc/default/grub.bak /etc/default/grub sudo update-grub

  • Перезапускаем компьютер и снова выполняем обновление:

    sudo apt-get update sudo apt-get upgrade

Контейнер Kibana

Образ Kibana создан на основе официального образа docker.elastic.co/kibana/kiban:5.6.9.

В существующий образ внесены изменения согласно прилагаемому docker-файлy:

FROM docker.elastic.co/kibana/kibana:5.6.9  
ADD ./config/kibana.yml /usr/share/kibana/config/kibana.yml    
EXPOSE 5601

Где kibana.yml – это конфигурационный файл. Пример, конфигурационного файла.

Стартуем командой:

docker run --rm -d --net=host --name=kibana kibana:5.6.9