Автоматизація керуванням MultiOTP

Щоб не підключатися до 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

Ось як виглядає фінальний результат:

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *