- Tại sao cần Alert System thay vì chỉ log
- Kiến trúc alert system dựa trên hook
- Module 1: Discord Webhook Integration
- Module 2: Email Alert với HTML template
- Module 3: Slack Integration
- Module 4: Telegram Bot Alert
- Alert Router: Quyết định gửi alert nào, khi nào
- Rate Limiting cho Alert: Tránh spam notification
- Cấu hình webhook URLs qua Settings API
- Testing Alert System: Gửi test notification
- Kết luận
Bài viết này xây dựng hệ thống Alert & Notification hoàn chỉnh, tích hợp Discord, Email, Slack và Telegram, cho phép admin nhận cảnh báo tức thì khi có hành vi nguy hiểm mà không cần ngồi trước dashboard suốt ngày.
Tại sao cần Alert System thay vì chỉ log
Log chỉ là dữ liệu tĩnh. Alert là phản ứng chủ động.
- Admin không thể ngồi F5 dashboard 24/7.
- Brute force attack xảy ra trong vài phút, không phải vài giờ.
- Một số endpoint nhạy cảm (delete-admin, bulk-delete) cần cảnh báo ngay lập tức.
- Pattern detection chỉ có ý nghĩa khi có ai đó hành động dựa trên nó.
Hệ thống alert biến Init Sentinel từ công cụ ghi chép thành công cụ phòng thủ tích cực.
Kiến trúc alert system dựa trên hook
Init Sentinel đã chuẩn bị sẵn hook init_html_security_event ngay trong hàm core. Đây là điểm móc lý tưởng để triển khai alert mà không làm phình to hàm gốc.
// Trong hàm core init_html_log_security_event
do_action( 'init_html_security_event', $log_id, [
'user_id' => $user_id,
'ip_address' => $ip,
'endpoint' => (string) $endpoint,
'action' => (string) $action,
'status_code' => (int) $status_code,
'user_agent' => $user_agent,
'created_at' => gmdate('Y-m-d H:i:s'),
] );
Alert system sẽ hook vào đây và quyết định có gửi thông báo hay không dựa trên severity level, frequency và blacklist/whitelist.
Module 1: Discord Webhook Integration
Discord là lựa chọn phổ biến vì miễn phí, setup nhanh và hỗ trợ rich embed với màu sắc phân loại mức độ nguy hiểm.
/**
* Gửi alert qua Discord webhook
* @param array $data Log data từ init_html_security_event
*/
function init_html_alert_discord( $data ) {
$webhook_url = apply_filters('init_html_discord_webhook_url', '');
if ( empty($webhook_url) ) {
return;
}
// Xác định màu sắc theo severity
$color = 15158332; // red (default)
if ( $data['status_code'] < 500 ) { $color = 16776960; // yellow (4xx) } $embed = [ 'embeds' => [
[
'title' => '🚨 Security Event Detected',
'description' => sprintf(
"**Endpoint:** `%s`\n**Action:** `%s`\n**IP:** `%s`\n**Status:** `%d`",
$data['endpoint'],
$data['action'],
$data['ip_address'],
$data['status_code']
),
'color' => $color,
'timestamp' => gmdate('c'),
'footer' => [
'text' => sprintf('User ID: %s | UA: %s',
$data['user_id'] ?: 'Guest',
substr($data['user_agent'], 0, 50)
)
]
]
]
];
wp_remote_post( $webhook_url, [
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode($embed),
'timeout' => 5,
'blocking' => false, // async để không chặn request
] );
}
Module 2: Email Alert với HTML template
Email phù hợp cho những alert không cần xử lý tức thì nhưng cần lưu vết audit trail.
/**
* Gửi email alert với template HTML đẹp
* @param array $data Log data
*/
function init_html_alert_email( $data ) {
$to = apply_filters('init_html_alert_email_recipients', get_option('admin_email'));
if ( empty($to) ) {
return;
}
$subject = sprintf(
'[%s] Security Alert: %s',
get_bloginfo('name'),
$data['action']
);
$user = $data['user_id'] ? get_userdata($data['user_id']) : null;
$username = $user ? $user->user_login : 'Guest';
// HTML template
$message = sprintf('
<div style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: #dc3545; color: white; padding: 20px; border-radius: 8px 8px 0 0;">
<h2 style="margin: 0;">🚨 Security Event Detected</h2>
</div>
<div style="background: #f8f9fa; padding: 20px; border: 1px solid #dee2e6;">
<table style="width: 100%%; border-collapse: collapse;">
<tr>
<td style="padding: 8px; font-weight: bold; width: 120px;">Endpoint:</td>
<td style="padding: 8px; font-family: monospace;">%s</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Action:</td>
<td style="padding: 8px; font-family: monospace;">%s</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Status Code:</td>
<td style="padding: 8px;"><span style="background: #dc3545; color: white; padding: 4px 8px; border-radius: 4px;">%d</span></td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">IP Address:</td>
<td style="padding: 8px; font-family: monospace;">%s</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">User:</td>
<td style="padding: 8px;">%s (ID: %d)</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Time:</td>
<td style="padding: 8px;">%s UTC</td>
</tr>
</table>
<div style="margin-top: 20px; padding: 12px; background: white; border-left: 4px solid #0d6efd;">
<strong>User Agent:</strong><br>
<code style="font-size: 12px; word-break: break-all;">%s</code>
</div>
<div style="margin-top: 20px; text-align: center;">
<a href="%s" style="display: inline-block; padding: 12px 24px; background: #0d6efd; color: white; text-decoration: none; border-radius: 4px;">View All Logs</a>
</div>
</div>
</div>
',
esc_html($data['endpoint']),
esc_html($data['action']),
(int) $data['status_code'],
esc_html($data['ip_address']),
esc_html($username),
(int) $data['user_id'],
esc_html($data['created_at']),
esc_html($data['user_agent']),
admin_url('admin.php?page=init-html-security-logs')
);
$headers = ['Content-Type: text/html; charset=UTF-8'];
wp_mail( $to, $subject, $message, $headers );
}
Module 3: Slack Integration
Slack phù hợp cho team làm việc, cho phép discussion ngay dưới alert.
/**
* Gửi alert qua Slack webhook
* @param array $data Log data
*/
function init_html_alert_slack( $data ) {
$webhook_url = apply_filters('init_html_slack_webhook_url', '');
if ( empty($webhook_url) ) {
return;
}
// Màu sắc theo severity
$color = ($data['status_code'] >= 500) ? 'danger' : 'warning';
$payload = [
'text' => '🚨 Security Event Detected',
'attachments' => [
[
'color' => $color,
'fields' => [
[
'title' => 'Endpoint',
'value' => '`' . $data['endpoint'] . '`',
'short' => true
],
[
'title' => 'Action',
'value' => '`' . $data['action'] . '`',
'short' => true
],
[
'title' => 'IP Address',
'value' => $data['ip_address'],
'short' => true
],
[
'title' => 'Status Code',
'value' => (string) $data['status_code'],
'short' => true
],
[
'title' => 'User Agent',
'value' => substr($data['user_agent'], 0, 100),
'short' => false
]
],
'footer' => 'Init Sentinel',
'ts' => strtotime($data['created_at'])
]
]
];
wp_remote_post( $webhook_url, [
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode($payload),
'timeout' => 5,
'blocking' => false,
] );
}
Module 4: Telegram Bot Alert
Telegram cho phép nhận cảnh báo trên mobile ngay lập tức, phù hợp cho admin di động.
/**
* Gửi alert qua Telegram Bot API
* @param array $data Log data
*/
function init_html_alert_telegram( $data ) {
$bot_token = apply_filters('init_html_telegram_bot_token', '');
$chat_id = apply_filters('init_html_telegram_chat_id', '');
if ( empty($bot_token) || empty($chat_id) ) {
return;
}
$emoji = ($data['status_code'] >= 500) ? '🔴' : '🟡';
$message = sprintf(
"%s *Security Event Detected*\n\n" .
"*Endpoint:* `%s`\n" .
"*Action:* `%s`\n" .
"*IP:* `%s`\n" .
"*Status:* `%d`\n" .
"*User:* %s\n" .
"*Time:* %s UTC",
$emoji,
$data['endpoint'],
$data['action'],
$data['ip_address'],
$data['status_code'],
$data['user_id'] ?: 'Guest',
$data['created_at']
);
$url = sprintf(
'https://api.telegram.org/bot%s/sendMessage',
$bot_token
);
wp_remote_post( $url, [
'body' => [
'chat_id' => $chat_id,
'text' => $message,
'parse_mode' => 'Markdown'
],
'timeout' => 5,
'blocking' => false,
] );
}
Alert Router: Quyết định gửi alert nào, khi nào
Không phải mọi log đều cần alert. Alert Router là lớp logic quyết định severity và channel phù hợp.
/**
* Router: quyết định có gửi alert không và gửi qua kênh nào
*/
add_action('init_html_security_event', function( $log_id, $data ) {
// 1) Whitelist: bỏ qua một số IP/user đáng tin cậy
$whitelist_ips = apply_filters('init_html_alert_whitelist_ips', []);
if ( in_array($data['ip_address'], $whitelist_ips, true) ) {
return;
}
// 2) Critical endpoints → alert ngay qua tất cả kênh
$critical_endpoints = apply_filters('init_html_alert_critical_endpoints', [
'inkstone/delete-manga',
'inkstone/create-admin',
'inkstone/bulk-delete',
]);
foreach ($critical_endpoints as $ep) {
if ( strpos($data['endpoint'], $ep) !== false ) {
init_html_alert_discord($data);
init_html_alert_telegram($data);
init_html_alert_email($data);
return;
}
}
// 3) Brute force detection: cùng IP, >5 lần trong 10 phút
global $wpdb;
$table = $wpdb->prefix . 'init_sentinel_security_log';
$count = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$table}
WHERE ip_address = %s
AND status_code = 403
AND created_at > (UTC_TIMESTAMP() - INTERVAL 10 MINUTE)",
$data['ip_address']
) );
if ( $count >= 5 ) {
$data['action'] .= ' (Brute Force Pattern Detected)';
init_html_alert_discord($data);
init_html_alert_slack($data);
// Auto-block IP (cần implement riêng)
do_action('init_html_auto_block_ip', $data['ip_address'], '24 hours');
return;
}
// 4) 5xx errors → email only (không spam chat)
if ( $data['status_code'] >= 500 ) {
init_html_alert_email($data);
return;
}
// 5) Các trường hợp còn lại: không alert, chỉ log
}, 10, 2);
Rate Limiting cho Alert: Tránh spam notification
Nếu attacker spam 100 requests/giây, admin sẽ nhận 100 alerts. Cần throttle.
/**
* Throttle alert: chỉ gửi tối đa 1 alert/IP trong 5 phút
*/
function init_html_should_send_alert( $ip_address ) {
$cache_key = 'init_sentinel_alert_sent_' . md5($ip_address);
$cache_group = 'init_html_sentinel';
$last_sent = wp_cache_get($cache_key, $cache_group);
if ( $last_sent !== false ) {
return false; // Đã gửi alert cho IP này gần đây
}
// Mark là đã gửi, cache 5 phút
wp_cache_set($cache_key, time(), $cache_group, 5 * MINUTE_IN_SECONDS);
return true;
}
// Sử dụng trong router
add_action('init_html_security_event', function( $log_id, $data ) {
if ( ! init_html_should_send_alert($data['ip_address']) ) {
return; // Throttled
}
// ... logic gửi alert như bình thường
}, 5, 2); // Priority 5 để chạy trước router chính
Cấu hình webhook URLs qua Settings API
Hardcode webhook URL trong code là bad practice. Cần UI để admin config.
// Đăng ký settings
add_action('admin_init', function() {
register_setting('init_html_sentinel_alerts', 'init_html_discord_webhook');
register_setting('init_html_sentinel_alerts', 'init_html_slack_webhook');
register_setting('init_html_sentinel_alerts', 'init_html_telegram_bot_token');
register_setting('init_html_sentinel_alerts', 'init_html_telegram_chat_id');
register_setting('init_html_sentinel_alerts', 'init_html_alert_email');
});
// Thêm settings page
add_action('admin_menu', function() {
add_submenu_page(
'init-html-security-logs',
__('Alert Settings', 'init-html'),
__('Alert Settings', 'init-html'),
'manage_options',
'init-html-alert-settings',
'init_html_render_alert_settings_page'
);
});
function init_html_render_alert_settings_page() {
if ( ! current_user_can('manage_options') ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html__('Alert & Notification Settings', 'init-html'); ?></h1>
<form method="post" action="options.php">
<?php settings_fields('init_html_sentinel_alerts'); ?>
<table class="form-table">
<tr>
<th><label for="discord_webhook">Discord Webhook URL</label></th>
<td>
<input type="url" id="discord_webhook" name="init_html_discord_webhook"
value="<?php echo esc_attr(get_option('init_html_discord_webhook', '')); ?>"
class="regular-text" placeholder="https://discord.com/api/webhooks/..."/>
<p class="description">Get webhook URL from Server Settings → Integrations → Webhooks</p>
</td>
</tr>
<tr>
<th><label for="slack_webhook">Slack Webhook URL</label></th>
<td>
<input type="url" id="slack_webhook" name="init_html_slack_webhook"
value="<?php echo esc_attr(get_option('init_html_slack_webhook', '')); ?>"
class="regular-text" placeholder="https://hooks.slack.com/services/..."/>
</td>
</tr>
<tr>
<th><label for="telegram_token">Telegram Bot Token</label></th>
<td>
<input type="text" id="telegram_token" name="init_html_telegram_bot_token"
value="<?php echo esc_attr(get_option('init_html_telegram_bot_token', '')); ?>"
class="regular-text" placeholder="123456:ABC-DEF..."/>
</td>
</tr>
<tr>
<th><label for="telegram_chat">Telegram Chat ID</label></th>
<td>
<input type="text" id="telegram_chat" name="init_html_telegram_chat_id"
value="<?php echo esc_attr(get_option('init_html_telegram_chat_id', '')); ?>"
class="regular-text" placeholder="-100123456789"/>
<p class="description">Use @userinfobot to get your chat ID</p>
</td>
</tr>
<tr>
<th><label for="alert_email">Alert Email</label></th>
<td>
<input type="email" id="alert_email" name="init_html_alert_email"
value="<?php echo esc_attr(get_option('init_html_alert_email', get_option('admin_email'))); ?>"
class="regular-text"/>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
// Filter webhook URLs từ settings
add_filter('init_html_discord_webhook_url', function() {
return get_option('init_html_discord_webhook', '');
});
add_filter('init_html_slack_webhook_url', function() {
return get_option('init_html_slack_webhook', '');
});
add_filter('init_html_telegram_bot_token', function() {
return get_option('init_html_telegram_bot_token', '');
});
add_filter('init_html_telegram_chat_id', function() {
return get_option('init_html_telegram_chat_id', '');
});
add_filter('init_html_alert_email_recipients', function() {
return get_option('init_html_alert_email', get_option('admin_email'));
});
Testing Alert System: Gửi test notification
Admin cần test xem webhook có hoạt động không trước khi triển khai thực tế.
// Thêm test button vào settings page
function init_html_render_alert_settings_page() {
// ... form như trên
// Xử lý test request
if ( isset($_POST['test_discord']) && check_admin_referer('init_html_test_alert') ) {
$test_data = [
'endpoint' => 'test/endpoint',
'action' => 'test_alert',
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1',
'status_code' => 403,
'user_id' => get_current_user_id(),
'user_agent' => 'Init Sentinel Test',
'created_at' => gmdate('Y-m-d H:i:s'),
];
init_html_alert_discord($test_data);
echo '<div class="notice notice-success"><p>Discord test notification sent!</p></div>';
}
// Test buttons
echo '<hr><h2>Test Notifications</h2>';
echo '<form method="post">';
wp_nonce_field('init_html_test_alert');
echo '<button type="submit" name="test_discord" class="button">Test Discord</button> ';
echo '<button type="submit" name="test_slack" class="button">Test Slack</button> ';
echo '<button type="submit" name="test_telegram" class="button">Test Telegram</button> ';
echo '<button type="submit" name="test_email" class="button">Test Email</button>';
echo '</form>';
}
Kết luận
Alert & Notification System biến Init Sentinel từ passive logger thành active defender. Kết hợp với pattern detection và auto-blocking, hệ thống này cho phép admin phản ứng trong vòng vài phút thay vì vài giờ hoặc vài ngày.
Bình luận