Automate OpenCanary definitions, localize times

This commit is contained in:
2026-05-08 16:11:47 -04:00
parent ec4853b008
commit ba2c63acdc

View File

@ -234,6 +234,7 @@ INSERT OR IGNORE INTO settings(key, value) VALUES('active_profile', '');
INSERT OR IGNORE INTO settings(key, value) VALUES('admin_port', '');
INSERT OR IGNORE INTO settings(key, value) VALUES('admin_expires_at', '');
INSERT OR IGNORE INTO settings(key, value) VALUES('appliance_name', 'BaldCanary Appliance');
INSERT OR IGNORE INTO settings(key, value) VALUES('timezone', '');
SQL
chown -R "$APP_USER:$APP_GROUP" "$APP_ROOT/db"
@ -241,6 +242,73 @@ chmod 775 "$APP_ROOT/db"
chmod 660 "$APP_DB"
}
write_opencanary_event_map() {
log "Generating OpenCanary event label map..."
"$APP_ROOT/venv/bin/python" <<'PY'
import inspect
import json
import re
from pathlib import Path
from opencanary import logger
out = {}
def humanize(name: str) -> str:
name = re.sub(r'^LOG_', '', name)
parts = name.split('_')
pretty = []
acronyms = {
'FTP', 'HTTP', 'HTTPS', 'SSH', 'SMB', 'MYSQL', 'MSSQL',
'RDP', 'VNC', 'NTP', 'SNMP', 'TFTP', 'TCP', 'UDP',
'DNS', 'LDAP', 'REDIS', 'SIP'
}
for part in parts:
if part in acronyms:
pretty.append(part)
elif part == 'LOGIN':
pretty.append('Login')
elif part == 'ATTEMPT':
pretty.append('Attempt')
elif part == 'CONNECTION':
pretty.append('Connection')
elif part == 'NEW':
pretty.append('New')
else:
pretty.append(part.capitalize())
label = ' '.join(pretty)
# A few nicer aliases.
label = label.replace('RDP Connection Made', 'RDP Connection')
label = label.replace('HTTP GET', 'HTTP GET Request')
label = label.replace('HTTP POST', 'HTTP POST Request')
return label
for cls_name in ('CanaryLogger', 'PyLogger'):
cls = getattr(logger, cls_name, None)
if not cls:
continue
for name, value in inspect.getmembers(cls):
if not name.startswith('LOG_'):
continue
if isinstance(value, int):
out[str(value)] = humanize(name)
Path('/opt/baldcanary/config/opencanary_event_labels.json').write_text(
json.dumps(out, indent=2, sort_keys=True)
)
PY
chown "$APP_USER:$APP_GROUP" "$APP_ROOT/config/opencanary_event_labels.json"
chmod 664 "$APP_ROOT/config/opencanary_event_labels.json"
}
write_common_php() {
log "Writing shared PHP helpers..."
@ -273,6 +341,78 @@ function bc_set_setting(string $key, string $value): void {
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=CURRENT_TIMESTAMP');
$stmt->execute([$key, $value]);
}
function bc_local_timezone(): string {
$tz = bc_setting('timezone', '');
if ($tz !== '') {
return $tz;
}
$systemTz = trim((string)@shell_exec('timedatectl show -p Timezone --value 2>/dev/null'));
if ($systemTz !== '') {
return $systemTz;
}
return date_default_timezone_get() ?: 'UTC';
}
function bc_local_time(?string $utcTime): string {
$utcTime = trim((string)$utcTime);
if ($utcTime === '') {
return '';
}
try {
$dt = new DateTime($utcTime, new DateTimeZone('UTC'));
$dt->setTimezone(new DateTimeZone(bc_local_timezone()));
return $dt->format('F j, Y g:i:s A');
} catch (Throwable $e) {
return $utcTime;
}
}
function bc_event_label(?string $type): string {
$type = trim((string)$type);
$labels = [
// BaldCanary web events
'page_view' => 'Page View',
'form_submit' => 'Form Submission',
'xss_probe' => 'XSS Probe',
'sql_injection_probe' => 'SQL Injection Probe',
'command_injection_probe' => 'Command Injection Probe',
'path_traversal_probe' => 'Path Traversal Probe',
'sensitive_file_probe' => 'Sensitive File Probe',
'session_file_probe' => 'Session File Probe',
'exposed_session_directory' => 'Exposed Session Directory',
'php_session_file' => 'PHP Session File Access',
'backup_directory' => 'Backup Directory Access',
'mysql_backup_directory' => 'MySQL Backup Directory Access',
'api_docs' => 'API Documentation Access',
'swagger_docs' => 'Swagger Documentation Access',
'phpinfo_probe' => 'PHP Info Probe',
'env_file_probe' => 'Environment File Probe',
'config_file_probe' => 'Config File Probe',
];
$mapFile = '/opt/baldcanary/config/opencanary_event_labels.json';
if (is_readable($mapFile)) {
$oc = json_decode((string)file_get_contents($mapFile), true);
if (is_array($oc)) {
$labels = $labels + $oc;
}
}
if (isset($labels[$type])) {
return $labels[$type];
}
if (preg_match('/^[a-z0-9_\-\.]+$/i', $type)) {
return ucwords(str_replace(['_', '-', '.'], ' ', $type));
}
return $type !== '' ? $type : 'Unknown Event';
}
PHP
cat > "$APP_ROOT/app/common/functions.php" <<'PHP'
@ -596,7 +736,7 @@ $webhooks = $db->query('SELECT * FROM webhook_targets ORDER BY id DESC')->fetchA
<tbody>
<?php foreach ($events as $event): ?>
<tr>
<td><?=h($event['event_time'])?></td>
<td><?=h(bc_local_time($event['event_time']))?></td>
<td><span class="badge text-bg-secondary"><?=h($event['severity'])?></span></td>
<td><?=h(bc_event_label($event['event_type']))?></td>
<td><?=h($event['src_ip'])?></td>
@ -628,7 +768,7 @@ $profile = bc_setting('active_profile', 'Not set');
body{font-family:Arial,sans-serif;margin:40px;color:#222}.cover{border-bottom:4px solid #222;margin-bottom:24px;padding-bottom:24px}.card{display:inline-block;border:1px solid #ddd;border-radius:10px;padding:16px;margin:8px 8px 8px 0;min-width:160px}.small{color:#666;font-size:12px}table{border-collapse:collapse;width:100%;margin-top:20px}th,td{border-bottom:1px solid #ddd;text-align:left;padding:8px;font-size:12px}th{background:#f1f1f1}@media print{button{display:none}}
</style></head><body>
<button onclick="window.print()">Print / Save as PDF</button>
<div class="cover"><h1>BaldCanary Pentest Validation Report</h1><p>Generated: <?=h(date('F j, Y g:i A'))?></p><p>Active profile: <strong><?=h($profile)?></strong></p></div>
<div class="cover"><h1>BaldCanary Pentest Validation Report</h1><p>Generated: <?=h((new DateTime('now', new DateTimeZone(bc_local_timezone())))->format('F j, Y g:i A'))?></p><p>Active profile: <strong><?=h($profile)?></strong></p></div>
<div class="card"><div class="small">Total Events</div><h2><?=count($events)?></h2></div>
<div class="card"><div class="small">Unique Source IPs</div><h2><?=count(array_unique(array_filter(array_column($events,'src_ip'))))?></h2></div>
<div class="card"><div class="small">High Interest Events</div><h2><?=count(array_filter($events, fn($e)=>in_array($e['severity'],['high','critical'],true)))?></h2></div>
@ -636,7 +776,7 @@ body{font-family:Arial,sans-serif;margin:40px;color:#222}.cover{border-bottom:4p
<p>BaldCanary recorded interaction with the selected deception profile and exposed canary services. Events below may indicate penetration test activity, unauthorized curiosity, automated scanning, or attempted exploitation.</p>
<h2>Event Timeline</h2>
<table><thead><tr><th>Time</th><th>Severity</th><th>Type</th><th>Source IP</th><th>Method</th><th>Path</th><th>Bait</th></tr></thead><tbody>
<?php foreach ($events as $e): ?><tr><td><?=h($e['event_time'])?></td><td><?=h($e['severity'])?></td><td><?=h(bc_event_label($e['event_type']))?></td><td><?=h($e['src_ip'])?></td><td><?=h($e['method'])?></td><td><?=h($e['path'])?></td><td><?=h($e['matched_bait'])?></td></tr><?php endforeach; ?>
<?php foreach ($events as $e): ?><tr><td><?=h(bc_local_time($e['event_time']))?></td><td><?=h($e['severity'])?></td><td><?=h(bc_event_label($e['event_type']))?></td><td><?=h($e['src_ip'])?></td><td><?=h($e['method'])?></td><td><?=h($e['path'])?></td><td><?=h($e['matched_bait'])?></td></tr><?php endforeach; ?>
</tbody></table>
</body></html>
PHP
@ -1415,6 +1555,7 @@ main() {
install_packages
create_user_and_dirs
install_python_env
write_opencanary_event_map
init_sqlite
write_common_php
write_admin_app