Apps: "Cygwin", "OpenSSH", "Cmd/PowerShell", "Java 11", "Jenkins".
Задача: подключение агентов исполнения "Jenkins Slaves" в операционных системах "MS Windows" в среде "Java 11+", через SSH-соединение.
Немного о том, отчего возникла надобность в написании этой инструкции. Ранее, когда "Jenkins" запускался в среде "Java 8", на операционных системах "Microsoft Windows" посредством технологии "WebStart (JNLP)" можно было быстро и непринуждённо запускать агенты исполнения "Jenkins Slaves" прямо из браузера, в пару щелчков мыши. Но из "Java 11" поддержка устаревшей "WebStart" была вырезана и теперь запуск агентов на windows-системах приходится осуществлять также, как и для linux-систем - через сеансы SSH-подключений. Это влечёт за собой необходимость устанавливать на windows-системах службы "OpenSSH", помимо "Java", в любом случае необходимой.
Последовательность дальнейших действий такова:
1. Установка "OpenSSH Server" и "Syslog-NG";
2. Настройка подсистемы журналирования "(cygwin) Syslog-NG";
3. Настройка сервера "(cygwin) OpenSSH Server";
4. Об интеграции и пользователях "(cygwin) OpenSSH Server";
5. Наладка аутентификации пользователей "(cygwin) OpenSSH Server" по ssh-ключу;
6. Установка среды исполнения "Java/OpenJDK" в ОС "MS Windows";
7. Подключение "Jenkins Slave" через "(cygwin) OpenSSH Server" на ОС "MS Windows";
8. Проверка работоспособности "Jenkins Slave" через простейшую задачу типа "Pipeline".
2. Настройка подсистемы журналирования "(cygwin) Syslog-NG";
3. Настройка сервера "(cygwin) OpenSSH Server";
4. Об интеграции и пользователях "(cygwin) OpenSSH Server";
5. Наладка аутентификации пользователей "(cygwin) OpenSSH Server" по ssh-ключу;
6. Установка среды исполнения "Java/OpenJDK" в ОС "MS Windows";
7. Подключение "Jenkins Slave" через "(cygwin) OpenSSH Server" на ОС "MS Windows";
8. Проверка работоспособности "Jenkins Slave" через простейшую задачу типа "Pipeline".
Установка "OpenSSH Server" и "Syslog-NG".
Начиная с "MS Windows 10 (Workstation)/2019 Server" в операционную систему как компонент был встроен "OpenSSH Server". С 2020-го года первые страницы вывода поисковых машин заняты рекомендациями использовать эту возможность для наладки SSH-подключений к windows-системам. Однако, производство ставит перед нами задачу сделать решение, работоспособное не только лишь в последних версиях операционной системы. Нам требуется, чтобы агента исполнения "Jenkins" можно было запустить как на "MS Win 2019", так и на всех предыдущих версиях этой операционной системы, где встроенного SSH-сервера нет.
Решение давно найдено - в рамках проекта "Cygwin" множество утилит и приложений, изначально написанных для posix-систем (это BSD, "Linux", etc.), собраны их исходных кодов с адаптацией до возможности исполнения в среде операционных систем "MS Windows" посредством трансляции posix-вызовов через библиотеку "cygwin1.dll". В проекте "Cygwin" поддерживается и отлично работают "OpenSSH Server", а также подсистема журналирования "Syslog-NG" (применение её необязательно, но удобно для локализации возможных проблем как при первичной наладке, так и эксплуатации).
Процедура установки приложений "Cygwin" проста и очевидна. С заглавной сайта проекта "https://cygwin.com", из раздела "Installing Cygwin" скачивается утилита "setup-x86_64.exe", посредством которой в дальнейшем осуществляется загрузка и развёртывание дистрибутивов, а также обновление уже установленных приложений. Настройка функциональности приложений как таковых при этом не производится.
Настройка подсистемы журналирования "(cygwin) Syslog-NG".
Надёжнее всего работать с компонентами "Cygwin" через имеющийся в его комплекте эмулятор терминала "mintty" - в нём уже преднастроена масса специфичных переменных окружения, помогающих обеспечить совместимость между posix-приложениями и несвойственной им среде исполнения. Обычно при инсталляции "Cygwin" ярлыки запуска "mintty" выводятся на рабочий стол и в стартовое меню.
Запускаем "mintty" в контексте пользователя, обладающего административными привилегиями (например, через пункт "Run as administrator" контекстного меню ярлыка или непосредственно "mintty"):
C:\Cygwin64\bin\mintty.exe
Запускаем конфигуратор "Syslog-NG":
$ syslog-ng-config
Creating default syslog-ng configuration files in /etc/syslog-ng
....
Do you want to install syslog-ng as service?
(Say "no" if it's already installed as service) (yes/no) yes
The service has been installed under LocalSystem account.
To start the service, call `net start syslog-ng' or `cygrunsrv -S syslog-ng'.
....
....
Do you want to install syslog-ng as service?
(Say "no" if it's already installed as service) (yes/no) yes
The service has been installed under LocalSystem account.
To start the service, call `net start syslog-ng' or `cygrunsrv -S syslog-ng'.
....
Результатом деятельности конфигуратора будет регистрация подсистемы журналирования в качестве автоматически запускаемого системного сервиса.
Мне не нравится предлагаемое разработчиками отображаемое название сервиса, и я меняю его:
C:\> sc config syslog-ng DisplayName= "Syslog-NG (Cygwin64)"
Запускаем сервис подсистемы журналирования (или он стартует сам при после несущей системы):
C:\> net start syslog-ng
The Syslog-NG (Cygwin64) service is starting.
The Syslog-NG (Cygwin64) service was started successfully.
The Syslog-NG (Cygwin64) service was started successfully.
По умолчанию "Syslog-NG" сваливает записи о всех событиях в один журнал "/var/log/messages". При желании можно отфильтровать события сервиса "sshd" с выводом их в отдельный журнал:
$ vi /etc/syslog-ng/syslog-ng.conf
....
# Location of the "OpenSSH Server" event log, filter and processing rule
destination d_sshd { file("/var/log/sshd.log"); };
filter f_sshd { program("sshd"); };
log { source(s_local); filter(f_sshd); destination(d_sshd); };
....
# Additional main filter with an exception for "OpenSSH Server"
filter f_local { not filter(f_sshd); };
# Modification of the main processing rule with the addition of a filter
log {
....
filter(f_local);
....
};
# Location of the "OpenSSH Server" event log, filter and processing rule
destination d_sshd { file("/var/log/sshd.log"); };
filter f_sshd { program("sshd"); };
log { source(s_local); filter(f_sshd); destination(d_sshd); };
....
# Additional main filter with an exception for "OpenSSH Server"
filter f_local { not filter(f_sshd); };
# Modification of the main processing rule with the addition of a filter
log {
....
filter(f_local);
....
};
Для принятия изменения в фильтрах потребуется перезапустить сервис журналирования:
C:\> net restart syslog-ng
Настройка сервера "(cygwin) OpenSSH Server".
Запускаем "mintty" в контексте пользователя, обладающего административными привилегиями (например, через пункт "Run as administrator" контекстного меню ярлыка или непосредственно "mintty"):
C:\Cygwin64\bin\mintty.exe
Запускаем конфигуратор "OpenSSH Server":
$ ssh-host-config
....
*** Info: Creating default /etc/ssh_config file
*** Info: Creating default /etc/sshd_config file
....
*** Query: Should StrictModes be used? (yes/no) yes
....
*** Query: Do you want to install sshd as a service? (yes/no) yes
....
*** Query: Enter the value of CYGWIN for the daemon: []
....
*** Info: The sshd service has been installed under the LocalSystem
*** Info: account (also known as SYSTEM). To start the service now, call
*** Info: `net start cygsshd' or `cygrunsrv -S cygsshd'.
....
*** Info: Creating default /etc/ssh_config file
*** Info: Creating default /etc/sshd_config file
....
*** Query: Should StrictModes be used? (yes/no) yes
....
*** Query: Do you want to install sshd as a service? (yes/no) yes
....
*** Query: Enter the value of CYGWIN for the daemon: []
....
*** Info: The sshd service has been installed under the LocalSystem
*** Info: account (also known as SYSTEM). To start the service now, call
*** Info: `net start cygsshd' or `cygrunsrv -S cygsshd'.
....
Если в процессе что-то пошло не так и результат не устраивает, то можно удалить созданный конфигуратором сервис (и запустить настройку заново).
Запускаем "cmd" в контексте пользователя, обладающего административными привилегиями (например, через пункт "Run as administrator" контекстного меню ярлыка или непосредственно "cmd"):
C:\> sc query state= all | find "sshd"
C:\> sc delete cygsshd
C:\> sc delete cygsshd
Как вариант, можно не удалять сервис, а поменять что-то - например отображаемое имя:
C:\> sc config cygsshd DisplayName= "OpenSSH Server (Cygwin64)"
Запускам сервис:
C:\> net start cygsshd
The OpenSSH Server (Cygwin64) service is starting.
The OpenSSH Server (Cygwin64) service was started successfully.
The OpenSSH Server (Cygwin64) service was started successfully.
По умолчанию openssh-сервер пишет в журнал только события аутентификации (как успешные, так и нет), и этого вполне достаточно для начала работы. Если будет нужно увеличить детализацию, то в этом помогут опции "SysLogFacility" и "LogLevel" конфигурационного файла "/etc/sshd_config".
По умолчанию пользователям доступна как парольная, так и ключевая аутентификация, а также возможность подключения по протоколу SFTP. За это отвечают следующие опции конфигурационного файла "(cygwin) OpenSSH Server":
C\> notepad C:\Cygwin64\etc\sshd_config
....
PasswordAuthentication yes
....
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
....
Subsystem sftp /usr/sbin/sftp-server
....
PasswordAuthentication yes
....
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
....
Subsystem sftp /usr/sbin/sftp-server
....
Финальным этапом подготовки к работе "OpenSSH Server" в "MS Windows" посредством "Windows Firewall" разрешаем серверу принимать подключения на порту TCP:22.
Запускаем "cmd" в контексте пользователя, обладающего административными привилегиями (например, через пункт "Run as administrator" контекстного меню ярлыка или непосредственно "cmd"):
C:\> netsh advfirewall firewall add rule name= "OpenSSH Server (Cygwin64)" dir=in action=allow protocol=TCP localport=22
Проверка функциональности "(cygwin) OpenSSH Server".
Создаём пользователя для агента исполнения "Jenkins Slave", например с именем "jenkins-group0" и вводим его в группу "Administrators" для того, чтобы тот мог в дальнейшем инсталлировать и перезапускать сервисы.
Подключаемся к настроенному выше ssh-серверу, запущенному в операционной системе "MS-Windows":
$ ssh user@win-dev1.example.net
user@WIN-DEV1 ~
$
$
Запускаем в cygwin-ssh-сеансе командную оболочку "Windows PowerShell":
user@WIN-DEV1 ~
$ powershell
$ powershell
Windows PowerShell
....
PS C:\cygwin64\home\user>
....
PS C:\cygwin64\home\user>
При подключении из linux-систем к запущенному в "MS Windows" cygwin-openssh-серверу иногда могут наблюдаться проблемы передачи сигнальных символов, особо ярко выражающиеся в практической невозможности использования редактора "vi". Вероятная причина в несогласованности протоколов используемых псевдо-терминалов: например, на клиентской стороне это "xterm", а на стороне SSH-сервера это "mintty". Самое простое - явно указать в ssh-сеансе, какой эмулятор терминала следует использовать. Это можно сделать в уже открытом сеансе, командой "export TERM=mintty", или послав соответствующую переменную окружения при инициализации ssh-сеанса:
$ TERM=mintty ssh -o "SendEnv TERM" user@win-dev1.example.net
Об интеграции и пользователях "(cygwin) OpenSSH Server".
Большое количество популярных инструкций по настройке "OpenSSH Server", запускаемого в среде "Cygwin" в "MS Windows", говорят о том, что пользователей openssh-сервиса нужно явно регистрировать в среде "Cygwin" (как минимум внося их в конфигурационный файл "/etc/passwd").
На самом деле углубленное чтение документации в части описания методов сопоставления политик безопасности NT (MS Windows) и "Posix" (BSD, Linux) (https://cygwin.com/cygwin-ug-net/ntsec.html#ntsec-mapping) даёт понять, что современные реализации "Cygwin" уже давно (с 2015-го) избавляют нас от необходимости отдельно регистрировать уже имеющихся в несущей системе "MS Windows" пользователей:
....
Starting with Cygwin 1.7.34, Cygwin uses an automatic, internal translation from Windows SID to POSIX UID/GID.
....
If the first process in a Cygwin process tree determines that no /etc/passwd or /etc/group file is present, no other process in the entire process tree will try to read the files later on. This is done for self-preservation. It's rather bad if the uid or gid of a user changes during the lifetime of a process tree.
....
Starting with Cygwin 1.7.34, Cygwin uses an automatic, internal translation from Windows SID to POSIX UID/GID.
....
If the first process in a Cygwin process tree determines that no /etc/passwd or /etc/group file is present, no other process in the entire process tree will try to read the files later on. This is done for self-preservation. It's rather bad if the uid or gid of a user changes during the lifetime of a process tree.
....
Внутри эмулятора терминала "mintty" (используемого также и в качестве оболочки для openssh-сеансов) хорошо перехватываются обращения к файловой структуре несущей windows-системы. Например, несмотря на то, что по умолчанию файловые posix-утилиты оперируют адресами вроде "/etc/...", префикс вроде "C:" будет прозрачно транслироваться в запрос к структуре "/cygdrive/c", которая отображает в себе иерархию файловой системы диска "C:".
Наладка аутентификации пользователей "(cygwin) OpenSSH Server" по ssh-ключу.
Прежде всего на стороне, откуда будут исходить ssh-подключения, например персональном компьютере пользователя или CI/CD-сервере, должна иметься пара ssh-ключей: "приватная (закрытая)" и "публичная (открытая)". Создаём ssh-ключи, при необходимости:
$ mkdir -p -m 700 /usr/local/etc/jenkins/.ssh/jenkins && cd /usr/local/etc/jenkins/.ssh/jenkins && ssh-keygen -q -t rsa -b 2048 -f ./id_rsa -P "" -C "User jenkins"
Через ssh-сеанс, который мы настроили и открыли ранее с парольной аутентификацией, настраиваем доступ посредством ключевой аутентификации:
$ ssh jenkins@win-dev1.example.net
На целевом windows-компьютере в "домашней" директории пользователя структуры "Cygwin", через ssh-сеанс или просто в псевдо-терминале "mintty", добавляем в специальном файле открытую часть ssh-ключа пользователя "jenkins":
jenkins@WIN-DEV1 ~
$ export TERM=mintty
$ mkdir.exe ~/.ssh
$ vi.exe ~/.ssh/authorized_keys
$ export TERM=mintty
$ mkdir.exe ~/.ssh
$ vi.exe ~/.ssh/authorized_keys
ssh-rsa AAA...l2w==
Закрываем доступ к ключевой информации от посторонних:
$ chmod.exe -R o-rwx ~/
$ chmod.exe -R go-rwx ~/.ssh
$ chmod.exe -R go-rwx ~/.ssh
Теперь для подключения к ssh-серверу не потребуется вводить пароль, так как аутентификация будет производиться с более удобным и безопасным методом ssh-ключей:
$ ssh -i /usr/local/etc/jenkins/.ssh/jenkins/id_rsa jenkins@win-dev1.example.net
Установка среды исполнения "Java/OpenJDK".
Не уверен, что требование к совпадению версий среды исполнения для сервера "Jenkins" и его агентов "Slave" жёсткое, но в отсутствии других java-приложений проще и на стороне CI/CD-агентов установить ту же "Java 11".
Лично я загружаю дистрибутив "OpenJDK JRE 11 Windows 64-bit MSI", предназначенный для развёртывания в операционных системах "MS Windows", с сайта одного из разработчиков "Java" - "developers.redhat.com":
Также хорошим, и даже более простым, вариантом будет скачать немного другой вариант сборки от сообщества "AdoptOpenJDK".
Чтобы после установки "Java" и дополнения переменной "PATH" таковая обновилась в контексте cygwin-приложений последние следует перезапустить (как вариант можно в дополнение запускать службу "cygserver", которая будет следить за изменением системного окружения и обновлять среды исполнения cygwin-приложений):
C:\> net restart cygsshd
Проверяем, доступна ли свежеустановленная "Java" в эмуляторе терминала "mintty":
user@WIN-DEV1 ~
$ java -version
$ java -version
openjdk version "11.0.10" 2021-01-19 LTS
....
....
Подключение "Jenkins Slave" через "(cygwin) OpenSSH Server" на ОС "MS Windows".
Все предварительные работы в целевой windows-системе мы уже произвели, и остальное - в web-интерфейсе "Jenkins".
В "Jenkins v2" для хранения частных переменных окружения, паролей, ключей и сертификатов используется централизованная подсистема "Credentials Provider". Прежде всего добавляем подраздел для хранения "ключей", доступных только при явном указании на связь с указанным именем области:
Jenkins -> Credentials -> System -> Add domain:
Domain Name: win-dev1.example.net
Domain Name: win-dev1.example.net
Добавляем ssh-ключи в область домена подключаемой "ноды" ("закрытый ключ" достаём из файла "./id_rsa", полученного в примере выше):
Jenkins -> Credentials -> System -> win-dev1.example.net -> Add Credentials:
Kind: SSH Username with private key
Scope: System (Jenkins and nodes only)
Description: User jenkins for node win-dev1.example.net
Username: jenkins
Private Key:
Enter directly:
Key: -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA3nh9A9eCwl03...
....
Kind: SSH Username with private key
Scope: System (Jenkins and nodes only)
Description: User jenkins for node win-dev1.example.net
Username: jenkins
Private Key:
Enter directly:
Key: -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA3nh9A9eCwl03...
....
Теперь настроим подключение "Jenkins Server" к "Jenkins Slave":
Jenkins -> Manage Jenkins -> Manage Nodes -> Permanent Agent:
Node name: win-dev1.example.net
Remote root directory: C:/Users/jenkins/.jenkins
Labels: shell win-dev1 master
Usage: Only build jobs with label expressions matching this node
Launch method: Launch agent via SSH
Host: win-dev1.example.net
Credentials: jenkins (User for node win-dev1.example.net)
Host Key Verification Strategy: Manuality trusted key Verification Strategy
Availability: Keep this agent online
Node Properties:
Environment variables: on
Node name: win-dev1.example.net
Remote root directory: C:/Users/jenkins/.jenkins
Labels: shell win-dev1 master
Usage: Only build jobs with label expressions matching this node
Launch method: Launch agent via SSH
Host: win-dev1.example.net
Credentials: jenkins (User for node win-dev1.example.net)
Host Key Verification Strategy: Manuality trusted key Verification Strategy
Availability: Keep this agent online
Node Properties:
Environment variables: on
В соответствии с выбранным методом дополнительной проверки подлинности соединения SSH-подключение к системе "Jenkins Slave" не состоится до тех пор, пока мы не подтвердим доверие к полученному от удалённого сетевого узла "fingerprint"-у:
Jenkins -> Manage Jenkins -> Manage Nodes -> win-dev1.example.net -> Trust SSH Host Key:
Do you want to trust the SSH Host Key with fingerprint ... for future connections to this host? yes
Do you want to trust the SSH Host Key with fingerprint ... for future connections to this host? yes
По завершению настройки подключения к "Jenkins Slave" велим запустить агента, после чего "Jenkins Server" соединиться с целевой "нодой" (под управлением операционной системы "MS Windows") по SSH, загрузит туда посредством SFTP исполняемый файл агента "remoting.jar" и запустит его в режиме ожидания команд. Таких агентов в целевой системе может быть загружено неограниченно много, например в контексте отдельных пользователей, вроде "jenkins1", "jenkins2", "jenkinsX".
Проверка работоспособности "Jenkins Slave" через простейшую задачу типа "Pipeline".
Описываем "pipeline" (формат "Declarative", примеры "Pipeline), основная цель которого состоит в проверке необходимого минимума прав доступа и корректной работы в операционной системе с национальным набором символов (в нашем случае русского языка):
New Item (Job):
Item name: project0_Hello
Pipeline project:
....
Pipeline:
Definition: Pipeline script
Script:
// Jenkins Declarative Pipeline
pipeline {
agent none
stages {
stage('One') {
agent {label "shell && win-dev1 && master"}
steps {
echo 'Hello & Привет!'
sh 'printenv'
sh 'set'
sh "cmd /c echo hello & привет"
bat encoding: 'UTF-8', script: """
chcp 65001
mkdir "${USERPROFILE}/test1Тест2"
cd "${USERPROFILE}"
dir
"""
powershell encoding: 'UTF-8', script: '''
Set-WinSystemLocale en-US
New-Item -ItemType directory -Force -Path "${HOME}/test2Тест3"
Get-ChildItem
Set-Location -Path "${HOME}"
Get-ChildItem
'''
}
}
}
}
Item name: project0_Hello
Pipeline project:
....
Pipeline:
Definition: Pipeline script
Script:
// Jenkins Declarative Pipeline
pipeline {
agent none
stages {
stage('One') {
agent {label "shell && win-dev1 && master"}
steps {
echo 'Hello & Привет!'
sh 'printenv'
sh 'set'
sh "cmd /c echo hello & привет"
bat encoding: 'UTF-8', script: """
chcp 65001
mkdir "${USERPROFILE}/test1Тест2"
cd "${USERPROFILE}"
dir
"""
powershell encoding: 'UTF-8', script: '''
Set-WinSystemLocale en-US
New-Item -ItemType directory -Force -Path "${HOME}/test2Тест3"
Get-ChildItem
Set-Location -Path "${HOME}"
Get-ChildItem
'''
}
}
}
}