Щоб не підключатися до MultiOTP на сервері Ubuntu через SSH я вирішив написати три PowerShell скрипти для автоматизації синхронізації, заміни файлу БД користувача і запуску скрипту генерації QR-кодів.
Скрипт підключення до сервера та синхронізація MultiOTP з AD за LDAP
# 1. Встановлення та імпорт модуля Posh-SSH
if (!(Get-Module Posh-SSH -ListAvailable)) {
Write-Host "Встановлення модулю Posh-SSH..." -ForegroundColor Yellow
Install-Module -Name Posh-SSH -Force -Scope CurrentUser
}
Import-Module Posh-SSH
# 2. Налаштування підключення
$sshHost = "1.2.3.4"
$sshUser = "root"
$sshPass = "password"
$securePass = ConvertTo-SecureString $sshPass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($sshUser, $securePass)
# 3. Підключення до серверу
Write-Host "Підключення к $sshHost..." -ForegroundColor Cyan
$session = New-SSHSession -ComputerName $sshHost -Credential $credential -AcceptKey
if ($session.Connected) {
Write-Host "Успішно підключено. Виконання синхронізації LDAP..." -ForegroundColor Green
# Додано 2>/dev/null для приховання попереджень bash про термінал
$remoteCommand = "echo '$sshPass' | sudo -S docker exec suspicious_goldberg bash -ic 'multiotp -debug -display-log -ldap-users-sync 2>/dev/null'"
$result = Invoke-SSHCommand -SessionId $session.SessionId -Command $remoteCommand
# 4. Висновок результату
Write-Host "`n--- РЕЗУЛЬТАТ ВИКОНАННЯ ---" -ForegroundColor Cyan
if ($result.Output) {
# Виводимо лише успішний лог
$result.Output
}
# 5. Закриття сесії (з придушенням слова True)
Remove-SSHSession -SessionId $session.SessionId | Out-Null
Write-Host "`nСесію закрито." -ForegroundColor Cyan
} else {
Write-Host "Не вдалося підключитись до сервера!" -ForegroundColor Red
}
Скрипт для заміни файлу БД користувача
Якщо ви раніше додали користувача до групи 2FAUsers і генерували йому QR-код, а потім перемістили користувача до групи without2fa і знову повернули до 2FAUsers. При таких маніпуляціях QR-код буде новим і щоб повернути старий ми заздалегідь скопіювали файл БД користувача і зараз підмінимо.
# 1. Перевірка та імпорт модуля Posh-SSH
if (!(Get-Module Posh-SSH -ListAvailable)) {
Write-Host "Встановлення модуля Posh-SSH..." -ForegroundColor Yellow
Install-Module -Name Posh-SSH -Force -Scope CurrentUser
}
Import-Module Posh-SSH
# 2. Налаштування підключення
$sshHost = "1.2.3.4"
$sshUser = "root"
$sshPass = "password"
$securePass = ConvertTo-SecureString $sshPass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($sshUser, $securePass)
# 3. Підключення до сервера
Write-Host "Підключення до $sshHost..." -ForegroundColor Cyan
$session = New-SSHSession -ComputerName $sshHost -Credential $credential -AcceptKey
if ($session.Connected) {
Write-Host "Успішно підключено. Копіювання файлів та налаштування прав..." -ForegroundColor Green
# Формуємо команду:
# Запускаємо bash від імені root (через sudo -S) і виконуємо відразу дві команди через &&
# Прапорець -it прибрано, оскільки скрипт працює у фоновому режимі
$remoteCommand = "echo '$sshPass' | sudo -S bash -c 'docker cp /tmp/users/. suspicious_goldberg:/etc/multiotp/users/ && docker exec suspicious_goldberg chmod -R 777 /etc/multiotp/users/'"
# Виконуємо команду
$result = Invoke-SSHCommand -SessionId $session.SessionId -Command $remoteCommand
# 4. Виведення результату
Write-Host "`n--- РЕЗУЛЬТАТ ---" -ForegroundColor Cyan
# Команди cp та chmod зазвичай нічого не виводять при успіху, тому додамо повідомлення
if (-not $result.Error -or $result.Error -match "\[sudo\] password for user:") {
Write-Host "Файли успішно скопійовано, права 777 призначено." -ForegroundColor Green
}
if ($result.Output) {
$result.Output
}
# Фільтруємо виведення помилок, щоб не показувати стандартний запит пароля від sudo
if ($result.Error) {
$filteredError = $result.Error -replace '\[sudo\] password for user:', ''
if ($filteredError.Trim() -ne '') {
Write-Host "`n--- ПОМИЛКИ / ПОПЕРЕДЖЕННЯ ---" -ForegroundColor Red
$filteredError.Trim()
}
}
# 5. Закриття сесії
Remove-SSHSession -SessionId $session.SessionId | Out-Null
Write-Host "`nСесію закрито." -ForegroundColor Cyan
} else {
Write-Host "Не вдалося підключитися до сервера!" -ForegroundColor Red
}
Скрипт для запуску скрипту генерації QR-коду та копіювання його на розшарину папку користувача
Сам скрипт генерації QR-коду,еа та копіювання “generate_and_copy_qr.sh”:
#!/bin/bash
CONTAINER="suspicious_goldberg"
SHARE_DIR="/mnt/shara"
TEMP_EXPORT="/tmp/qr_export"
# Перевірка, чи доступна мережева папка
if [ ! -d "$SHARE_DIR" ]; then
echo "Помилка: Мережеву папку $SHARE_DIR не знайдено."
exit 1
fi
echo "1. Запускаємо генерацію QR-кодів усередині контейнера..."
docker exec $CONTAINER bash -c '
USER_DIR="/etc/multiotp/users"
WORK_DIR="/usr/local/bin/multiotp"
mkdir -p /tmp/qrcodes
# Переходимо в робочу папку MultiOTP (інакше він не хоче зберігати файли)
cd "$WORK_DIR"
for file in "$USER_DIR"/*.db; do
[ -e "$file" ] || continue
USER=$(basename "$file" .db)
GROUP=$(grep -i "^group=" "$file" | cut -d"=" -f2 | tr -d " " | tr -d "\r")
if [[ "$GROUP" == "multiotp_2fa" ]]; then
# Перевіряємо, чи не генерували ми його вже раніше
if [ ! -f "/tmp/qrcodes/${USER}_QR.png" ]; then
echo "Генеруємо QR для користувача: $USER"
# Генеруємо картинку (вона збережеться прямо в $WORK_DIR)
php multiotp.php -qrcode "$USER" "${USER}_QR.png" >/dev/null 2>&1
# Одразу переміщуємо готовий файл у папку для експорту
mv "${USER}_QR.png" "/tmp/qrcodes/" 2>/dev/null
fi
fi
done
'
echo "2. Копіюємо готові QR-коди з контейнера на сервер..."
mkdir -p "$TEMP_EXPORT"
docker cp $CONTAINER:/tmp/qrcodes/. "$TEMP_EXPORT/"
echo "3. Розкладаємо файли по особистих папках користувачів..."
shopt -s nullglob
for file in "$TEMP_EXPORT"/*.png; do
FILENAME=$(basename "$file")
USERNAME="${FILENAME%_QR.png}"
USER_FOLDER="$SHARE_DIR/$USERNAME"
if [ -d "$USER_FOLDER" ]; then
cp "$file" "$USER_FOLDER/"
echo "Успішно скопійовано: $FILENAME -> $USER_FOLDER/"
else
echo "Увага: Особисту папку $USER_FOLDER не знайдено для $USERNAME, пропуск."
fi
done
shopt -u nullglob
echo "Готово!"
Щоб можна було копіювати в мережеві папки користувачів, потрібно спочатку змонтувати її:
sudo apt update sudo apt install cifs-utils -y sudo mkdir -p /mnt/shara sudo nano /root/.smbcredentials username=login password=password domain=it-homyak.com.ua sudo chmod 600 /root/.smbcredentials sudo mount -t cifs -o credentials=/root/.smbcredentials //1.2.3.2/users_shara /mnt/shara
Створення віконного інтерфейсу (GUI) у PowerShell. Складання трьох скриптів у програму.
Для зручності роботи зі скриптами скомпілюємо їх в одну програму. І для початку зробимо PowerShell-скрипт “MultiOTP_Manager.ps1” з графічним інтерфейсом, який об’єднає всі три скрипти:
# Завантажуємо збірки для створення графічного інтерфейсу
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# --- ГОЛОВНА ФУНКЦІЯ ПІДКЛЮЧЕННЯ ТА ВИКОНАННЯ ---
function Run-SSHAction {
param(
[string]$ActionName,
[string]$Command
)
# Виводимо статус у текстове поле вікна
$txtOutput.AppendText("`r`n[$(Get-Date -Format 'HH:mm:ss')] $ActionName`r`n")
$txtOutput.AppendText("Підключення до сервера...`r`n")
$form.Update() # Примусово оновлюємо інтерфейс, щоб він не зависав
# Встановлення та імпорт модуля
if (!(Get-Module Posh-SSH -ListAvailable)) {
$txtOutput.AppendText("Встановлення Posh-SSH...`r`n")
$form.Update()
Install-Module -Name Posh-SSH -Force -Scope CurrentUser
}
Import-Module Posh-SSH
# Налаштування
$sshHost = "1.2.3.4"
$sshUser = "root"
$sshPass = "passwod"
$securePass = ConvertTo-SecureString $sshPass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($sshUser, $securePass)
try {
$session = New-SSHSession -ComputerName $sshHost -Credential $credential -AcceptKey -ErrorAction Stop
if ($session.Connected) {
$txtOutput.AppendText("Успішно. Виконання команди...`r`n")
$form.Update()
# Формуємо підсумкову команду через sudo bash -c
$fullCommand = "echo '$sshPass' | sudo -S bash -c `"$Command`""
$result = Invoke-SSHCommand -SessionId $session.SessionId -Command $fullCommand
# Обробка успішного виводу
if ($result.Output) {
$txtOutput.AppendText("$($result.Output)`r`n")
} else {
$txtOutput.AppendText("Команду виконано (без текстового виводу).`r`n")
}
# Обробка помилок (фільтруємо запит пароля)
if ($result.Error) {
$filteredError = $result.Error -replace '\[sudo\] password for user:', ''
if ($filteredError.Trim() -ne '') {
$txtOutput.AppendText("ПОПЕРЕДЖЕННЯ: $($filteredError.Trim())`r`n")
}
}
Remove-SSHSession -SessionId $session.SessionId | Out-Null
$txtOutput.AppendText("Сесію закрито.`r`n-----------------------------`r`n")
} else {
$txtOutput.AppendText("ПОМИЛКА: Не вдалося підключитися!`r`n")
}
} catch {
$txtOutput.AppendText("ПОМИЛКА: $_`r`n")
}
# Прокручуємо текстове поле в самий низ
$txtOutput.SelectionStart = $txtOutput.Text.Length
$txtOutput.ScrollToCaret()
$form.Update()
}
# --- НАЛАШТУВАННЯ ВІКНА (GUI) ---
# Створюємо головну форму
$form = New-Object System.Windows.Forms.Form
$form.Text = "Управління MultiOTP (1.2.3.4)"
$form.Size = New-Object System.Drawing.Size(550, 450)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
# Кнопка 1: Синхронізація LDAP
$btnSync = New-Object System.Windows.Forms.Button
$btnSync.Location = New-Object System.Drawing.Point(20, 20)
$btnSync.Size = New-Object System.Drawing.Size(150, 40)
$btnSync.Text = "Синхронізація LDAP"
$btnSync.Add_Click({
$cmd = "docker exec suspicious_goldberg bash -ic 'multiotp -debug -display-log -ldap-users-sync 2>/dev/null'"
Run-SSHAction -ActionName "Запуск синхронізації LDAP..." -Command $cmd
})
$form.Controls.Add($btnSync)
# Кнопка 2: Права та копіювання
$btnCopy = New-Object System.Windows.Forms.Button
$btnCopy.Location = New-Object System.Drawing.Point(190, 20)
$btnCopy.Size = New-Object System.Drawing.Size(150, 40)
$btnCopy.Text = "Копіювати користувачів"
$btnCopy.Add_Click({
$cmd = "docker cp /tmp/users/. suspicious_goldberg:/etc/multiotp/users/ && docker exec suspicious_goldberg chmod -R 777 /etc/multiotp/users/"
Run-SSHAction -ActionName "Копіювання користувачів та налаштування прав..." -Command $cmd
})
$form.Controls.Add($btnCopy)
# Кнопка 3: Генерація QR
$btnQR = New-Object System.Windows.Forms.Button
$btnQR.Location = New-Object System.Drawing.Point(360, 20)
$btnQR.Size = New-Object System.Drawing.Size(150, 40)
$btnQR.Text = "Згенерувати QR-коди"
$btnQR.Add_Click({
$cmd = "/opt/generate_and_copy_qr.sh"
Run-SSHAction -ActionName "Генерація QR-кодів..." -Command $cmd
})
$form.Controls.Add($btnQR)
# Текстове поле для виводу логів
$txtOutput = New-Object System.Windows.Forms.TextBox
$txtOutput.Location = New-Object System.Drawing.Point(20, 80)
$txtOutput.Size = New-Object System.Drawing.Size(490, 310)
$txtOutput.Multiline = $true
$txtOutput.ScrollBars = "Vertical"
$txtOutput.ReadOnly = $true
$txtOutput.Font = New-Object System.Drawing.Font("Consolas", 9)
$txtOutput.Text = "Готово до роботи. Виберіть дію...`r`n-----------------------------`r`n"
$form.Controls.Add($txtOutput)
# Запускаємо вікно
$form.ShowDialog() | Out-Null
Скомпілюємо цей скрипт у .exe (без чорного вікна на фоні):
Install-Module ps2exe -Scope CurrentUser Invoke-ps2exe -inputFile .\MultiOTP_Manager.ps1 -outputFile .\MultiOTP_Manager.exe -noConsole -iconFile .\multiotp_logo.ico
Ось як виглядає фінальний результат:

