feat(dlp): add enforcement — USB write-block, print cancel, clipboard clear
Phase 2.5: when DLP policy rule has action="block", the collector now actively prevents the action instead of just logging: - USB: Set-Disk -IsReadOnly via Get-Partition/Get-Disk pipeline - Print: Remove-CimInstance Win32_PrintJob for matching jobs - Clipboard: Set-Clipboard -Value $null to clear sensitive content Each enforcement adds enforced=true/false to incident telemetry. Windows balloon notification shown to user on every block action. Backward-compatible: existing action="alert" rules unchanged. Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# DLP Enforcement (action: "block")
|
||||
|
||||
## Обзор
|
||||
|
||||
Phase 2.5 расширяет DLP endpoint collector функциями **активного предотвращения** (enforcement).
|
||||
При `action: "block"` в правиле DLP-политики коллектор не только регистрирует инцидент, но и выполняет блокирующее действие:
|
||||
|
||||
| Канал | Действие при `block` |
|
||||
|-----------|-----------------------------------------------------------|
|
||||
| clipboard | Очистка буфера обмена (`Set-Clipboard -Value $null`) |
|
||||
| usb | Перевод USB-диска в read-only (`Set-Disk -IsReadOnly`) |
|
||||
| print | Отмена задания печати (`Remove-CimInstance Win32_PrintJob`)|
|
||||
|
||||
Во всех случаях пользователь получает Windows-уведомление (balloon notification) с описанием причины блокировки.
|
||||
|
||||
## Конфигурация политики
|
||||
|
||||
Формат `dlp-policy.json` не изменился — поле `action` в правиле теперь поддерживает значение `"block"` наряду с `"alert"` (по умолчанию).
|
||||
|
||||
### Пример: блокировка USB записи
|
||||
|
||||
```json
|
||||
{
|
||||
"defaults": {
|
||||
"enabled": true,
|
||||
"action": "alert",
|
||||
"severity": "medium",
|
||||
"cooldownSeconds": 300
|
||||
},
|
||||
"endpoint": {
|
||||
"usb": [
|
||||
{
|
||||
"id": "block-all-usb-write",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"message": "Запись на USB-носитель заблокирована политикой DLP"
|
||||
}
|
||||
],
|
||||
"clipboard": [
|
||||
{
|
||||
"id": "block-pdn-clipboard",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"regexPatterns": [
|
||||
"\\b\\d{3}-\\d{3}-\\d{3}\\s?\\d{2}\\b",
|
||||
"\\b\\d{4}\\s?\\d{6}\\b"
|
||||
],
|
||||
"minLength": 8,
|
||||
"message": "Буфер обмена очищен: обнаружены персональные данные (СНИЛС/паспорт)"
|
||||
}
|
||||
],
|
||||
"print": [
|
||||
{
|
||||
"id": "block-confidential-print",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"documentRegex": "(?i)(конфиденциально|секретно|confidential|restricted)",
|
||||
"message": "Печать заблокирована: документ содержит метку конфиденциальности"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Пример: только мониторинг (без блокировки)
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoint": {
|
||||
"usb": [
|
||||
{
|
||||
"id": "monitor-usb",
|
||||
"action": "alert",
|
||||
"severity": "medium",
|
||||
"message": "Обнаружено подключение USB-носителя"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Телеметрия
|
||||
|
||||
Каждый инцидент с enforcement записывается в bucket `aw-dlp-incidents_<host>` с дополнительным полем:
|
||||
|
||||
```json
|
||||
{
|
||||
"ruleId": "block-all-usb-write",
|
||||
"action": "block",
|
||||
"severity": "high",
|
||||
"signalType": "usb_insert",
|
||||
"enforced": true,
|
||||
"driveLetter": "E:",
|
||||
"volumeName": "FLASH_DRIVE"
|
||||
}
|
||||
```
|
||||
|
||||
- `enforced: true` — блокировка выполнена успешно
|
||||
- `enforced: false` — блокировка не удалась (недостаточно прав, устройство недоступно и т.д.)
|
||||
|
||||
## Требования
|
||||
|
||||
- **Clipboard block**: Не требует повышенных прав.
|
||||
- **USB write-block**: Требует запуск от имени администратора (для `Set-Disk -IsReadOnly`). При запуске без прав блокировка не сработает, но инцидент будет зарегистрирован с `enforced: false`.
|
||||
- **Print block**: Требует права на отмену заданий печати (обычно — SYSTEM или администратор принт-сервера).
|
||||
|
||||
## Уведомления
|
||||
|
||||
При каждой блокировке пользователю показывается Windows balloon notification:
|
||||
|
||||
| Канал | Заголовок |
|
||||
|-----------|--------------------------------------|
|
||||
| clipboard | `DLP: буфер обмена очищен` |
|
||||
| usb | `DLP: USB заблокирован для записи` |
|
||||
| print | `DLP: печать заблокирована` |
|
||||
|
||||
Текст уведомления берётся из поля `message` правила политики.
|
||||
|
||||
## Rollback
|
||||
|
||||
Для отключения enforcement без изменения кода — смените `action` с `"block"` на `"alert"` в `dlp-policy.json`. Все правила продолжат мониторинг без блокировки.
|
||||
|
||||
Для USB, переведённого в read-only, восстановление:
|
||||
```powershell
|
||||
Get-Disk | Where-Object { $_.BusType -eq 'USB' -and $_.IsReadOnly } | Set-Disk -IsReadOnly $false
|
||||
```
|
||||
@@ -32,6 +32,15 @@
|
||||
- File-operation telemetry (create/copy/archive/upload hints) — в backlog.
|
||||
- Central incident aggregation/export — в backlog.
|
||||
|
||||
### Phase 2.5 — Enforcement (внедрено)
|
||||
|
||||
- USB write-block (`Set-Disk -IsReadOnly`) при `action: "block"` — внедрено.
|
||||
- Print job cancel (`Remove-CimInstance Win32_PrintJob`) при `action: "block"` — внедрено.
|
||||
- Clipboard clear (`Set-Clipboard -Value $null`) при `action: "block"` — внедрено.
|
||||
- Windows balloon notification пользователю при блокировке — внедрено.
|
||||
- Телеметрия enforcement (`enforced: true/false` в incident heartbeat) — внедрено.
|
||||
- Документация: `docs/dlp-enforcement.md`.
|
||||
|
||||
### Phase 3
|
||||
|
||||
- Policy engine service (server-side), versioned policies, approval workflow.
|
||||
|
||||
@@ -215,6 +215,99 @@ function Capture-IncidentScreenshot {
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enforcement functions (action = "block")
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Show-EnforcementNotification {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Title,
|
||||
[Parameter(Mandatory = $true)][string]$Body
|
||||
)
|
||||
try {
|
||||
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue
|
||||
$icon = New-Object System.Windows.Forms.NotifyIcon
|
||||
$icon.Icon = [System.Drawing.SystemIcons]::Warning
|
||||
$icon.BalloonTipTitle = $Title
|
||||
$icon.BalloonTipText = $Body
|
||||
$icon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Warning
|
||||
$icon.Visible = $true
|
||||
$icon.ShowBalloonTip(5000)
|
||||
Start-Sleep -Milliseconds 200
|
||||
$icon.Dispose()
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("notification failed: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ClipboardEnforcement {
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
try {
|
||||
Set-Clipboard -Value $null -ErrorAction Stop
|
||||
Write-EndpointLog "enforcement: clipboard cleared"
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("enforcement: clipboard clear failed: {0}" -f $_.Exception.Message)
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-UsbWriteBlockEnforcement {
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$DriveLetter
|
||||
)
|
||||
try {
|
||||
$partition = Get-Partition -DriveLetter ($DriveLetter.TrimEnd(':')) -ErrorAction Stop
|
||||
$disk = Get-Disk -Number $partition.DiskNumber -ErrorAction Stop
|
||||
if ($disk.BusType -ne 'USB') {
|
||||
Write-EndpointLog ("enforcement: skip non-USB disk {0} bus={1}" -f $disk.Number, $disk.BusType)
|
||||
return $false
|
||||
}
|
||||
if (-not $disk.IsReadOnly) {
|
||||
Set-Disk -Number $disk.Number -IsReadOnly $true -ErrorAction Stop
|
||||
Write-EndpointLog ("enforcement: USB disk {0} ({1}) set read-only" -f $disk.Number, $DriveLetter)
|
||||
}
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("enforcement: USB write-block failed drive={0}: {1}" -f $DriveLetter, $_.Exception.Message)
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-PrintJobEnforcement {
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$PrinterName,
|
||||
[string]$DocumentName,
|
||||
[string]$Owner
|
||||
)
|
||||
$cancelled = $false
|
||||
try {
|
||||
$jobs = Get-CimInstance Win32_PrintJob -ErrorAction SilentlyContinue
|
||||
foreach ($job in @($jobs)) {
|
||||
$jobPrinter = [string]$job.Name
|
||||
$jobOwner = [string]$job.Owner
|
||||
$jobDoc = [string]$job.Document
|
||||
$matchPrinter = ($jobPrinter -like "*$PrinterName*")
|
||||
$matchOwner = (-not $Owner) -or ($jobOwner -like "*$Owner*") -or ($jobOwner -like "*$env:USERNAME*")
|
||||
if ($matchPrinter -and $matchOwner) {
|
||||
Remove-CimInstance -InputObject $job -ErrorAction Stop
|
||||
Write-EndpointLog ("enforcement: print job cancelled id={0} printer={1} doc={2}" -f $job.JobId, $jobPrinter, $jobDoc)
|
||||
$cancelled = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("enforcement: print cancel failed printer={0}: {1}" -f $PrinterName, $_.Exception.Message)
|
||||
}
|
||||
return $cancelled
|
||||
}
|
||||
|
||||
function Get-StringHash {
|
||||
param([AllowNull()][string]$Value)
|
||||
if ($null -eq $Value) { return $null }
|
||||
@@ -321,11 +414,18 @@ function Evaluate-ClipboardRules {
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Clipboard rule matched: $ruleId" }
|
||||
|
||||
$enforced = $false
|
||||
if ($action -eq 'block') {
|
||||
$enforced = Invoke-ClipboardEnforcement
|
||||
Show-EnforcementNotification -Title 'DLP: буфер обмена очищен' -Body $message
|
||||
}
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'clipboard' -Data @{
|
||||
clipboardHash = $ClipboardHash
|
||||
clipboardLength = $ClipboardText.Length
|
||||
enforced = $enforced
|
||||
}
|
||||
Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2}" -f $ruleId, $action, $severity)
|
||||
Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2} enforced={3}" -f $ruleId, $action, $severity, $enforced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,11 +449,18 @@ function Evaluate-UsbRules {
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "USB rule matched: $ruleId" }
|
||||
|
||||
$enforced = $false
|
||||
if ($action -eq 'block') {
|
||||
$enforced = Invoke-UsbWriteBlockEnforcement -DriveLetter $DriveLetter
|
||||
Show-EnforcementNotification -Title 'DLP: USB заблокирован для записи' -Body $message
|
||||
}
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'usb_insert' -Data @{
|
||||
driveLetter = $DriveLetter
|
||||
volumeName = $VolumeName
|
||||
enforced = $enforced
|
||||
}
|
||||
Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3}" -f $ruleId, $action, $severity, $DriveLetter)
|
||||
Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3} enforced={4}" -f $ruleId, $action, $severity, $DriveLetter, $enforced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,12 +494,19 @@ function Evaluate-PrintRules {
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Print rule matched: $ruleId" }
|
||||
|
||||
$enforced = $false
|
||||
if ($action -eq 'block') {
|
||||
$enforced = Invoke-PrintJobEnforcement -PrinterName $PrinterName -DocumentName $DocumentName -Owner $Owner
|
||||
Show-EnforcementNotification -Title 'DLP: печать заблокирована' -Body $message
|
||||
}
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'print_job' -Data @{
|
||||
printerName = $PrinterName
|
||||
documentName = $DocumentName
|
||||
owner = $Owner
|
||||
enforced = $enforced
|
||||
}
|
||||
Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3}" -f $ruleId, $action, $severity, $PrinterName)
|
||||
Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3} enforced={4}" -f $ruleId, $action, $severity, $PrinterName, $enforced)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,99 @@ function Capture-IncidentScreenshot {
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enforcement functions (action = "block")
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Show-EnforcementNotification {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Title,
|
||||
[Parameter(Mandatory = $true)][string]$Body
|
||||
)
|
||||
try {
|
||||
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue
|
||||
$icon = New-Object System.Windows.Forms.NotifyIcon
|
||||
$icon.Icon = [System.Drawing.SystemIcons]::Warning
|
||||
$icon.BalloonTipTitle = $Title
|
||||
$icon.BalloonTipText = $Body
|
||||
$icon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Warning
|
||||
$icon.Visible = $true
|
||||
$icon.ShowBalloonTip(5000)
|
||||
Start-Sleep -Milliseconds 200
|
||||
$icon.Dispose()
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("notification failed: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ClipboardEnforcement {
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
try {
|
||||
Set-Clipboard -Value $null -ErrorAction Stop
|
||||
Write-EndpointLog "enforcement: clipboard cleared"
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("enforcement: clipboard clear failed: {0}" -f $_.Exception.Message)
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-UsbWriteBlockEnforcement {
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$DriveLetter
|
||||
)
|
||||
try {
|
||||
$partition = Get-Partition -DriveLetter ($DriveLetter.TrimEnd(':')) -ErrorAction Stop
|
||||
$disk = Get-Disk -Number $partition.DiskNumber -ErrorAction Stop
|
||||
if ($disk.BusType -ne 'USB') {
|
||||
Write-EndpointLog ("enforcement: skip non-USB disk {0} bus={1}" -f $disk.Number, $disk.BusType)
|
||||
return $false
|
||||
}
|
||||
if (-not $disk.IsReadOnly) {
|
||||
Set-Disk -Number $disk.Number -IsReadOnly $true -ErrorAction Stop
|
||||
Write-EndpointLog ("enforcement: USB disk {0} ({1}) set read-only" -f $disk.Number, $DriveLetter)
|
||||
}
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("enforcement: USB write-block failed drive={0}: {1}" -f $DriveLetter, $_.Exception.Message)
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-PrintJobEnforcement {
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$PrinterName,
|
||||
[string]$DocumentName,
|
||||
[string]$Owner
|
||||
)
|
||||
$cancelled = $false
|
||||
try {
|
||||
$jobs = Get-CimInstance Win32_PrintJob -ErrorAction SilentlyContinue
|
||||
foreach ($job in @($jobs)) {
|
||||
$jobPrinter = [string]$job.Name
|
||||
$jobOwner = [string]$job.Owner
|
||||
$jobDoc = [string]$job.Document
|
||||
$matchPrinter = ($jobPrinter -like "*$PrinterName*")
|
||||
$matchOwner = (-not $Owner) -or ($jobOwner -like "*$Owner*") -or ($jobOwner -like "*$env:USERNAME*")
|
||||
if ($matchPrinter -and $matchOwner) {
|
||||
Remove-CimInstance -InputObject $job -ErrorAction Stop
|
||||
Write-EndpointLog ("enforcement: print job cancelled id={0} printer={1} doc={2}" -f $job.JobId, $jobPrinter, $jobDoc)
|
||||
$cancelled = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-EndpointLog ("enforcement: print cancel failed printer={0}: {1}" -f $PrinterName, $_.Exception.Message)
|
||||
}
|
||||
return $cancelled
|
||||
}
|
||||
|
||||
function Get-StringHash {
|
||||
param([AllowNull()][string]$Value)
|
||||
if ($null -eq $Value) { return $null }
|
||||
@@ -321,11 +414,18 @@ function Evaluate-ClipboardRules {
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Clipboard rule matched: $ruleId" }
|
||||
|
||||
$enforced = $false
|
||||
if ($action -eq 'block') {
|
||||
$enforced = Invoke-ClipboardEnforcement
|
||||
Show-EnforcementNotification -Title 'DLP: буфер обмена очищен' -Body $message
|
||||
}
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'clipboard' -Data @{
|
||||
clipboardHash = $ClipboardHash
|
||||
clipboardLength = $ClipboardText.Length
|
||||
enforced = $enforced
|
||||
}
|
||||
Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2}" -f $ruleId, $action, $severity)
|
||||
Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2} enforced={3}" -f $ruleId, $action, $severity, $enforced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,11 +449,18 @@ function Evaluate-UsbRules {
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "USB rule matched: $ruleId" }
|
||||
|
||||
$enforced = $false
|
||||
if ($action -eq 'block') {
|
||||
$enforced = Invoke-UsbWriteBlockEnforcement -DriveLetter $DriveLetter
|
||||
Show-EnforcementNotification -Title 'DLP: USB заблокирован для записи' -Body $message
|
||||
}
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'usb_insert' -Data @{
|
||||
driveLetter = $DriveLetter
|
||||
volumeName = $VolumeName
|
||||
enforced = $enforced
|
||||
}
|
||||
Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3}" -f $ruleId, $action, $severity, $DriveLetter)
|
||||
Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3} enforced={4}" -f $ruleId, $action, $severity, $DriveLetter, $enforced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,12 +494,19 @@ function Evaluate-PrintRules {
|
||||
$severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity }
|
||||
$message = if ($rule.message) { [string]$rule.message } else { "Print rule matched: $ruleId" }
|
||||
|
||||
$enforced = $false
|
||||
if ($action -eq 'block') {
|
||||
$enforced = Invoke-PrintJobEnforcement -PrinterName $PrinterName -DocumentName $DocumentName -Owner $Owner
|
||||
Show-EnforcementNotification -Title 'DLP: печать заблокирована' -Body $message
|
||||
}
|
||||
|
||||
Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'print_job' -Data @{
|
||||
printerName = $PrinterName
|
||||
documentName = $DocumentName
|
||||
owner = $Owner
|
||||
enforced = $enforced
|
||||
}
|
||||
Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3}" -f $ruleId, $action, $severity, $PrinterName)
|
||||
Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3} enforced={4}" -f $ruleId, $action, $severity, $PrinterName, $enforced)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user